# The manifest

> Complete reference for qsolve.yaml: every field, its type, default, valid values and limits, the error each mistake raises, how the platform uses it, and annotated examples from published workflows.

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

`qsolve.yaml` sits at the root of a workflow repository. It names the workflow, points to its schemas and class, declares its parameters and methods, and sets its size and resource limits. It is the only place these are declared, and the platform stores a copy of it with every published version.

## How the manifest is checked

Three checks read it, in this order:

1. **Load** (`qsolve validate`, and the first thing every other command does). The file must be valid YAML and match the model below. Failure prints `Invalid workflow:` followed by the reason and exits 1. On submission and in `qsolve lint` the same failure is reported as lint error `E002` (`invalid workflow definition: ...`); a missing file is `E001`.
2. **Schema files.** The two schema paths are read. See [Input and output schemas](/docs/build/input-and-output-schemas).
3. **Lint and gates.** Further manifest rules (examples, license, hardware cases) are lint codes; see [Lint codes](/docs/build/lint-codes).

Rules that apply at every level:

- **Unknown keys are rejected**, in the top level and in every nested object. A typo such as `enveloppe:` fails the load with the validator's `Extra inputs are not permitted` message naming the key.
- **Required keys that are missing** fail with `Field required`. Wrong types fail with a message naming the field; for example, `version: 1.0` is read by YAML as a number and rejected, so write `1.0.0`.
- **Values are not coerced.** A string where a number is expected, or a number where a string is expected, is an error.

The platform also re-validates the stored manifest every time it accepts a run, with the same code.

## A complete manifest

This is the manifest of the `max-cut` example (version `1.0.2`), which uses every section. Long prose strings are shortened here; the full file is in `examples/max-cut/qsolve.yaml`.

```yaml
schema_version: 1
name: max-cut
version: 1.0.2
family: qubo_optimization
summary: Split a weighted graph into two groups to maximize the total weight of edges between them (network partitioning, clustering, circuit layout)
license: Apache-2.0
tags: [graph, partitioning, qubo, qaoa, logistics]

inputs:
  schema: schemas/input.json
  examples: [examples/ring_6.json]
parameters:
  - {name: layers, label: Circuit depth, type: int, default: 3, min: 1, max: 8, description: "More layers can find better answers but take longer to train"}
  - {name: shots, label: Final shots, type: int, default: 4000, min: 100, max: 20000, description: Final sampling shots}
  - {name: train_shots, label: Shots per training step, type: int, default: 1000, min: 100, max: 8000, description: Shots per optimizer step when training is sampled}
  - {name: optimizer_iters, label: Optimiser iterations, type: int, default: 100, min: 5, max: 400, description: COBYLA iterations}
  - {name: train_on, label: Train angles on, type: enum, default: sim, choices: [sim, backend], description: "sim trains noise-free and uses the device only to measure"}
outputs:
  schema: schemas/output.json

entrypoint: src.workflow:MaxCut

category: Networks and logistics
objective: {label: Negative cut weight, unit: "", decimals: 3}
metric: {kind: relative_gap, note: "100% means the quantum objective equals the best classical objective. It falls by the relative amount the quantum objective is worse."}
methods:
  - id: qaoa
    role: quantum
    title: QAOA (quantum approximate optimisation)
    what: A short quantum circuit that alternates between favouring low-cost answers and mixing possibilities.
    strength: Samples good answers directly from a quantum state.
    limit: On today's noisy chips only small circuits stay faithful.
  - id: exhaustive-or-annealing
    role: reference
    title: Exhaustive search (annealing beyond 22 variables)
    what: Tries every possible answer and keeps the best one.
    strength: Provably the best answer whenever it can run exhaustively.
    limit: Beyond 22 variables it is a heuristic with no guarantee.
  - id: annealing
    role: classical
    title: Simulated annealing
    what: Starts from a random answer and keeps trying small changes.
    strength: Fast, scales to large problems, and usually lands close to the best answer.
    limit: No guarantee that it finds the best answer.

envelope:
  max_qubits: 24
  max_variables: 24
  notes: "Up to 24 nodes per run"

targets:
  simulators: [cpu, gpu]
  qpu: [ibm]
  min_qubits_qpu: 4

resources:
  timeout_s: 1800
  memory_mb: 1536
  network: none

runtime:
  python: "3.12"
  dependencies: requirements.lock
```

## Top-level fields

| Field | Type | Required | Default | Valid values and errors |
|---|---|---|---|---|
| `schema_version` | integer | Yes | | Exactly `1`. Any other value fails the load. |
| `name` | string | Yes | | `^[a-z0-9][a-z0-9-]{1,62}$`: lowercase letters, digits and dashes, 2 to 63 characters, starting with a letter or digit. Error: `name must be lowercase letters, digits and dashes (2-63 chars)`. It becomes the workflow's address (its slug) and is its identity: a different `name` is a different workflow. A name already owned by another developer, or by a workflow you deleted, is refused at publish. See [Versioning](/docs/build/versioning). |
| `version` | string | Yes | | `^\d+\.\d+\.\d+$`: three whole numbers, no pre-release suffix, no leading `v`. Error: `version must be semver, e.g. 0.1.0`. |
| `family` | string | Yes | | Any string; the platform does not check it against a list and accepts an empty one. Use a stable snake_case name such as `qubo_optimization`, `electronic_structure` or `quantum_ml`. It is shown in the listing and used for filtering, and it is refreshed from the newest version at every publish. |
| `summary` | string | Yes | | Any string, no length limit in the model. One sentence on what the workflow solves; shown in the listing and refreshed at every publish. |
| `license` | string | Yes | | Any string, but blank or whitespace-only is lint error `E051`. Also add a `LICENSE` file, or lint warns `W051`. |
| `tags` | list of strings | No | `[]` | Free-form search tags. No limit on count or length is enforced. Refreshed at every publish. |
| `inputs` | object | Yes | | See [Inputs and outputs](#inputs-and-outputs). |
| `parameters` | list | No | `[]` | See [Parameters](#parameters). |
| `outputs` | object | Yes | | See [Inputs and outputs](#inputs-and-outputs). |
| `entrypoint` | string | Yes | | `^[A-Za-z_][\w.]*:[A-Za-z_]\w*$`, that is `package.module:ClassName`. Error: `entrypoint must look like 'package.module:ClassName'`. Lint `E030` if the module or class is not found, `E031` on a syntax error. |
| `category` | string | No | `""` | At most 60 characters. The field the workflow belongs to as a person would say it, for example `Finance`. Shown in the marketplace. |
| `objective` | object | No | `{label: Objective, unit: "", decimals: 4}` | See [`objective`](#objective). |
| `metric` | object | No | `{kind: relative_gap, ...}` | See [`metric`](#metric). |
| `methods` | list | Yes | | At least 2 entries; see [Methods](#methods). |
| `envelope` | object | Yes | | The largest problem the workflow accepts; see [`envelope`](#envelope). |
| `targets` | object | No | `{simulators: [cpu], qpu: []}` | See [`targets`](#targets). |
| `resources` | object | No | see below | See [`resources`](#resources). |
| `runtime` | object | No | see below | See [`runtime`](#runtime). |

### Things that are not manifest keys

Because unknown keys are rejected, these will fail the load if you add them:

- **Visibility, first-party status, ownership.** The platform sets them: a new developer workflow starts `unlisted` and becomes public when an admin approves it (or when auto-publish is on). There is nothing to declare.
- **Pricing and cost hints.** There is no price field. The quote for a run comes from what your `estimate()` returns and the platform's rate card; see [Credits and pricing](/docs/use/credits-and-pricing) and [Writing the workflow](/docs/build/writing-the-workflow).
- **Error mitigation.** Mitigation is code you write, and you report its result at run time with `ctx.report_mitigation(...)`. There is no manifest switch; see [Error mitigation and fidelity](/docs/build/error-mitigation-and-fidelity).
- **Benchmark cases and hardware cases.** They live in `benchmarks/cases.yaml`; see [Benchmarks](/docs/build/benchmarks).

## Inputs and outputs

Both have the same shape:

| Field | Type | Required | Default | Meaning |
|---|---|---|---|---|
| `schema` | string | Yes | | Path to a JSON file inside the repository, relative to the repository root. A path that resolves outside the repository fails with `schema path escapes workflow directory: <path>`. A missing file fails with `schema file not found: <path>`. A file that is not a JSON object fails with `<path> must contain a JSON object`. |
| `examples` | list of strings | No | `[]` | Paths to JSON example files. |

For `inputs`, at least one example is required (lint `E024`), and each must read and validate against the input schema (lint `E023`). The example files are copied into the version's record and shown to researchers as starting points. For `outputs`, `examples` is accepted but not used. The schema files themselves are checked by lint `E020` to `E022` (see [Input and output schemas](/docs/build/input-and-output-schemas)).

## Parameters

Each entry declares one setting a user can change. Every field, the type rules and how values reach your code are in [Parameters](/docs/build/parameters). In short:

| Field | Type | Required | Default | Constraints |
|---|---|---|---|---|
| `name` | string | Yes | | Unique across the list: `parameter names must be unique`. |
| `type` | string | Yes | | `int`, `float`, `bool`, `str` or `enum`. |
| `default` | any | Yes | | Must pass the parameter's own type, range and choices checks, or the manifest does not load. |
| `label` | string | No | `""` | At most 60 characters. |
| `description` | string | No | `""` | No length limit. |
| `min`, `max` | number | No | none | Applied to `int` and `float`; ignored for other types. |
| `choices` | list | For `enum` | none | The allowed values; ignored for other types. |

## `objective`

Describes the number each method reaches, so every value is shown with the right label and unit. The platform never writes this text itself.

| Field | Type | Default | Constraints |
|---|---|---|---|
| `label` | string | `Objective` | No length limit. |
| `unit` | string | `""` | For example `Ha`. |
| `decimals` | integer | `4` | 0 to 10 inclusive. |

## `metric`

Picks one of three accuracy formulas. You choose a kind and explain it in words; you do not supply a formula. [Benchmarks](/docs/build/benchmarks) gives the formulas and the rule for when a result matches the baseline.

| Field | Type | Default | Constraints |
|---|---|---|---|
| `kind` | string | `relative_gap` | `relative_gap` (lower-is-better objective, only a worse result loses accuracy), `relative_error` (distance to the reference, with the quantum value's error bar), `error_rate` (the value is a share of mistakes, with a 95% interval). Any other value fails the load. |
| `note` | string | `""` | At most 600 characters. Says what accuracy means for this workflow. |
| `tolerance` | number | `0.0` | 0 to 1 inclusive. Accuracy within this of 100% counts as matching the reference. |
| `tolerance_abs` | number or null | `null` | At least 0. A difference to the reference up to this, in the objective's own units, counts as matching, for example `0.0016` Ha (chemical accuracy). |

## Methods

`methods` lists every method Qsolve runs and compares on each input. You need exactly one `quantum` and exactly one `reference`, any number of `classical`, and at least two methods in total. The platform runs the classical methods first and the quantum method last.

| Field | Type | Required | Default | Constraints |
|---|---|---|---|---|
| `id` | string | Yes | | Same pattern as `name` (`method id must be lowercase letters, digits and dashes`). Unique within the list. |
| `role` | string | Yes | | `quantum`, `reference` or `classical`. |
| `title` | string | Yes | | At most 80 characters. |
| `what` | string | No | `""` | At most 500 characters. What the method does, for a reader who does not know it. |
| `strength` | string | No | `""` | At most 300 characters. |
| `limit` | string | No | `""` | At most 300 characters. |

List-level errors: `method ids must be unique`, and `declare exactly one method with role 'quantum'` (or `'reference'`). A list with fewer than two methods also fails the load.

Each method is implemented by a method of your `Workflow` class. The role decides the name, and the `id` only matters for `classical`:

| Role | Python method it calls |
|---|---|
| `quantum` | `quantum(self, ctx)` |
| `reference` | `reference(self, ctx)` |
| `classical` | `classical_<id>(self, ctx)`, with dashes in the id turned into underscores: id `annealing` calls `classical_annealing`, id `greedy-swap` calls `classical_greedy_swap` |

A declared method with no matching Python method stops the run with `method 'x' is declared in the manifest but the workflow has no `name` method`. A `reference` method that touches the quantum backend fails the run with `QuantumNotAllowed`; a `classical` one that does is shown as unavailable. Qsolve shows `title`, `what`, `strength` and `limit` as written and never writes its own text about your workflow, so write for someone who does not know the method.

## `envelope`

The largest problem the workflow accepts.

| Field | Type | Required | Default | Constraints |
|---|---|---|---|---|
| `max_qubits` | integer | Yes | | 1 to 1000. |
| `max_variables` | integer | Yes | | 1 to 100000. |
| `notes` | string | No | `""` | Shown in the rejection message, for example "Up to 24 nodes per run". |

How it is enforced:

- After your `__init__` runs, Qsolve calls `size()` and refuses the input if the result is greater than either `max_variables` or `max_qubits`, before any circuit runs and before anything is charged. The message is `Problem has <n> variables; this workflow supports at most <smaller of the two> (<notes>)`. In practice the smaller of the two is the limit.
- `max_qubits` is also a hard cap in the sandbox: a circuit with more qubits than this is refused by the executor with `circuit exceeds the allowed qubit count`.
- The input form shows `notes` next to "Your problem".
- It is separate from your input schema bounds. The schema (`maxItems`, `maximum`, ...) rejects a malformed input with `Invalid input at <path>: <reason>`; the envelope rejects a well-formed input whose size, as your `size()` reports it, is too big. Set both: schema bounds so the form can show limits early, and the envelope as the size limit that holds whatever shape the input takes.

## `targets`

| Field | Type | Default | Valid values | Meaning |
|---|---|---|---|---|
| `simulators` | list | `[cpu]` | `cpu`, `gpu` | A run on `sim-cpu` is accepted only if `cpu` is listed, and on `sim-gpu` only if `gpu` is listed. Any other entry fails the load. |
| `qpu` | list | `[]` | `ibm` | A run on `ibm:<device>` needs `ibm` listed. No other provider is accepted: Amazon Braket is not available yet, and `braket` in this list fails the manifest load. |
| `min_qubits_qpu` | integer or null | `null` | any integer | Accepted and stored, but **nothing reads it**: it has no effect on which runs are accepted, quoted or run. Lint reports it as warning `W052` so nobody relies on it. |

A run requesting an unlisted target is refused with HTTP 422 `target_not_supported` and the message `This workflow does not support target 'x'.`, with the workflow's lists attached. For `ibm:<device>`, the device name after the colon must match `^[A-Za-z0-9_.-]{1,64}$`. Noise-model targets (`fake:...`) are not accepted by the run API, so they are not listed here; they exist for benchmark cases and local runs. If `hardware_cases` are declared in `benchmarks/cases.yaml`, `qpu: [ibm]` is required (lint `E054`). See [Backends and targets](/docs/build/backends-and-targets) and [Machines and targets](/docs/use/machines-and-targets).

## `resources`

| Field | Type | Default | Valid values | Meaning |
|---|---|---|---|---|
| `timeout_s` | integer | `1800` | 1 to 86400 | Wall-clock limit for a run, in seconds. |
| `memory_mb` | integer | `4096` | 256 to 65536 | Memory limit for the sandbox, in MB. A run that exceeds it stops with `exceeded the <n> MB memory limit`. |
| `network` | string | `none` | `none` only | Workflows have no network. Any other value fails the load. |

How the limits are applied:

- **Timeout.** The sandbox stops a run at `timeout_s` with `exceeded its <n>s compute limit`. The platform also caps every run at its own maximum, 4 hours by default, whatever the manifest asks. Time spent waiting for a quantum computer does not count against it: the limit is extended by the queue wait, up to a platform cap (6 hours by default).
- **Up-front check.** If the run time predicted from your `estimate()` (its `runtime_s`, to which the SDK adds 20 s of run overhead) is above the effective limit (the smaller of `timeout_s` and the platform cap), the run is refused before it starts with `run_too_long`.
- **Validation.** The benchmark run during validation uses a time limit of `timeout_s` raised to at least 300 seconds and capped at 7200, and memory capped at 8192 MB. A heavy workflow therefore gets its whole `timeout_s` up to 2 hours while validating.

## `runtime`

| Field | Type | Default | Meaning |
|---|---|---|---|
| `python` | string | `3.12` | Informational: the value is stored but not read when a run starts. Runs use the Python of the platform image. |
| `dependencies` | string | `requirements.lock` | Informational: stored but not read. The package set is fixed by the platform image; see [Project layout](/docs/build/project-layout). |

## Examples of choices

A chemistry workflow measures energies, so it uses an absolute tolerance in the objective's units. From `molecular-ground-state`:

```yaml
objective: {label: Ground-state energy, unit: "Ha", decimals: 6}
metric: {kind: relative_error, tolerance_abs: 0.0016, note: "1 minus the energy difference to the exact active-space answer, as a fraction of that answer. Total energies are large numbers, so read the error in mHa too: chemical accuracy is 1.6 mHa."}
```

A classifier's objective is an error rate. From `quantum-kernel-classifier`:

```yaml
objective: {label: Test error rate, unit: "", decimals: 3}
metric: {kind: error_rate, note: "Share of held-out test points classified correctly, with its 95% interval. Fewer test points mean a wider interval."}
```

An error-rate workflow must put the number of test cases in its quantum method's `Outcome.details` as `n_test`, so the interval can be computed. See [Writing the workflow](/docs/build/writing-the-workflow).

Resources scale with the work. `max-cut` (a 24-variable QAOA) asks for the defaults of 1800 s and 1536 MB; `molecular-ground-state` runs a chemistry solver, so it asks for `timeout_s: 3600` and `memory_mb: 2048`; `electrolyte-solvent-screen` runs up to eight chemistry systems and asks for `timeout_s: 7200`. The envelope scales the same way: 24 qubits for the graph problems, 12 for the chemistry ones (6 active orbitals), 8 for the kernel classifier (one qubit per feature).

## Validate it

```bash
qsolve validate .
```

A manifest error prints `Invalid workflow:` with the field and reason, and exits 1. After the manifest loads, `validate` also checks that every example validates against the input schema. Common errors are in [Troubleshooting](/docs/build/troubleshooting) and every lint code is in [Lint codes](/docs/build/lint-codes).
