# Project layout

> The files and folders of a Qsolve workflow repository, what each one is for, which are required, what is left out of the published snapshot and the size limits.

Source: https://qsolve.omlabs.co/docs/build/project-layout
Updated: 2026-11-13

Qsolve reads `qsolve.yaml` at the root of the workflow, and the two schemas, the example inputs and the entrypoint module at the paths `qsolve.yaml` gives. `benchmarks/cases.yaml` is read at that fixed path. Everything else is yours.

The workflow does not have to be the whole repository: when you submit, you can name a subfolder (for example `workflows/max-cut`), and that folder is then the root described here. It must be a relative path of letters, digits, `.`, `_`, `-` and `/`, never absolute, never `..`, never starting with a dot. If it holds no `qsolve.yaml` at the chosen commit, the submission is refused with `No qsolve.yaml in the folder '<folder>' of this repository at that commit.`

## The layout

This is the published `max-cut` example, which every example follows:

```text
max-cut/
  qsolve.yaml               required
  README.md                 recommended
  LICENSE                   recommended
  schemas/
    input.json              required
    output.json             required
  src/
    __init__.py             keep it: the examples all have one
    workflow.py             your Workflow class
    qubo/                   your own modules (QAOA, classical solvers)
  examples/
    ring_6.json             required: at least one example input
  benchmarks/
    cases.yaml              required to publish
    inputs/                 larger benchmark inputs
  tests/                    your own tests, not published
  requirements.lock         optional
```

| Path | Required | Purpose |
|---|---|---|
| `qsolve.yaml` | Yes | The manifest: identity, schemas, parameters, methods, limits. See [The manifest](/docs/build/manifest). |
| `schemas/input.json` | Yes (path is set by `inputs.schema`) | JSON Schema (draft-07) for the input. Every input is validated against it and the input form is built from it. See [Input and output schemas](/docs/build/input-and-output-schemas). |
| `schemas/output.json` | Yes (path is set by `outputs.schema`) | JSON Schema for the answer `answer()` returns. The answer is validated and rendered from the schema's titles and units. |
| `src/` | Yes | Your code. The manifest's `entrypoint` points into it. The lint looks for the entrypoint's module both at the repository root and under `src/` (`src.workflow` is `src/workflow.py` or `src/workflow/__init__.py`); a missing module or class is `E030`. |
| `examples/*.json` | At least one (`E024`) | Example inputs listed under `inputs.examples`; the file names and folder are your choice. Each is validated against the input schema and shown to researchers as a starting point. |
| `benchmarks/cases.yaml` | To publish (`E050`) | Known-answer cases the platform runs and records. See [Benchmarks](/docs/build/benchmarks). |
| `README.md` | Recommended | What the workflow solves and its limits. Lint warns (`W060`) when missing. |
| `LICENSE` | Recommended | Lint warns (`W051`) when no `LICENSE`, `LICENSE.md`, `LICENSE.txt` or `COPYING` exists. The manifest's `license` field is required either way. |
| `tests/` | No | Your own `pytest` tests. Never shipped or run on the platform, and exempt from the import check. |
| `requirements.lock` | No | Documents your packages. It installs nothing; its only effect is that lint reports an import of a package listed there as `W042` instead of `E040`. |

## Entrypoint and package name

The manifest's `entrypoint` is `package.module:ClassName`. The examples use a package called `src`, so the entrypoint is `src.workflow:MaxCut`. Keep `src/__init__.py` and import your own modules relatively:

```python
from .qubo import Qubo, plan_qaoa, sample_qaoa
from qsolve import Context, Estimate, InvalidSolution, Outcome, Workflow, WorkflowError
```


## Importable modules

Workflows run with no network and no package installs, and your own Dockerfile is ignored. Your code may import:

- the Python standard library;
- `qsolve`;
- `numpy`, `scipy`, `sklearn`, `qiskit`, `qiskit_aer` and `pyscf`;
- `pydantic`, `jsonschema`, `yaml`, `typer` and `rich`.

Anything else is lint error `E040`. A package pinned in `requirements.lock` but unavailable produces warning `W042`, and the run rejects the import, so a workflow that needs another package cannot be published today. Files under `tests/` are exempt.

## Data files

Ship the data your workflow needs inside the repository and read it from disk, as `electrolyte-solvent-screen` does with `src/geometries.json`. A run sees only the repository and a scratch directory. Data-preparation scripts that need other packages belong outside the repository or in a folder your code never imports; the examples keep them in a separate `tools/` folder.

## Left out of the published snapshot

Publishing stores an immutable copy of the repository. These are skipped:

- directories named `__pycache__`, `.git`, `.venv`, `.pytest_cache`, `.ruff_cache`, `.mypy_cache`, `node_modules` and `tests`, wherever they are in the tree, and any file or directory whose name starts with `~`;
- `.pyc` and `.pyo` files, `.DS_Store`, `Thumbs.db` and `desktop.ini`.

Everything else, including the README, the LICENSE and your example inputs, is in the snapshot, so changing any of it needs a new version, and changing only what is skipped (for example a test) does not. See [Versioning](/docs/build/versioning).

## Limits and rejected content

| Rule | Limit | Lint code |
|---|---|---|
| Files in the repository | 500 | `E011` (the snapshot step refuses the same limit with `workflow too large`) |
| Total size of the files | 20 MB | `E011` (same) |
| Each schema file | 256 KB | `E022` |
| Repository size as fetched | 50 MB | rejected at fetch |
| Symbolic links | None | `E010` |
| Git submodules | None | rejected at fetch |
| Files that look like credentials (`.env`, `*.pem`, `*.key`, `id_rsa*`, `credentials*.json`, `.netrc`) | None | `E012` |
| Text that looks like a committed secret (private keys, cloud and GitHub tokens, `sk_live_` style keys) | None | `E013` |

The lint counts everything except `__pycache__`, `.git`, `.venv`, `.pytest_cache`, `.ruff_cache`, `.mypy_cache` and `node_modules`, so it counts `tests/`, while the snapshot step does not. The secret scan skips files over 1 MB and common binary types (`.png`, `.jpg`, `.gif`, `.pdf`, `.zip`, `.npy`, `.npz`).

Never put a provider token in the repository; your code never needs one. Every lint code is in [Lint codes](/docs/build/lint-codes).
