# Your first workflow

> Build, test and submit a Qsolve workflow from scratch: install the SDK, scaffold with qsolve init, run, validate, lint, benchmark, push to GitHub and submit.

Source: https://qsolve.omlabs.co/docs/build/your-first-workflow
Updated: 2026-11-13

`qsolve init` generates a passing workflow that solves a small QUBO with QAOA. Run it unchanged first, submit it, then swap in your own problem. Every command used here is specified in [The qsolve command line](/docs/build/cli), and every Python name in the [SDK reference](/docs/build/sdk-reference).

## 1. Install the SDK

You need Python 3.12 or newer. The `ibm` extra installs the device noise models that `qsolve test` uses for its noise-model benchmark case, so install it even if you never run on hardware.

```bash
pip install "qsolve-sdk[ibm]"
qsolve --version
qsolve --help
```

With the extra you can also run on a real device from your machine with `--target ibm:<device>` and your own token in `QSOLVE_IBM_TOKEN`. Simulators and noise-model targets need no token.

## 2. Scaffold a workflow

```bash
qsolve init depot-picker --summary "Pick the best depots"
cd depot-picker
```

The name is 2 to 63 lowercase letters, digits and dashes, starting with a letter or digit. It becomes the workflow's identifier on Qsolve. The summary is written into `qsolve.yaml` unquoted, so avoid a colon followed by a space and ` #` in it. The command creates:

```text
depot-picker/
  qsolve.yaml
  README.md
  benchmarks/cases.yaml
  examples/qubo_5.json
  schemas/input.json
  schemas/output.json
  src/__init__.py
  src/workflow.py
  tests/test_workflow.py
```

`src/workflow.py` is about 120 lines of Qiskit: it builds a QAOA circuit, trains the angles on a local statevector simulator, samples the final circuit through `ctx.sampler` and compares the result with an exhaustive search. The class is a `qsolve.Workflow` subclass ([The Workflow class](/docs/build/sdk-workflow-class)). [Project layout](/docs/build/project-layout) explains each file. `tests/test_workflow.py` is a pytest file that runs the example through `qsolve.run_workflow` and asserts the quantum result matches the best classical answer; run it with `pytest`.

## 3. Validate the manifest and inputs

```bash
qsolve validate .
```

```text
OK depot-picker v0.1.0
```

`validate` loads `qsolve.yaml`, reads both schemas and checks every example input against the input schema.

## 4. Lint

```bash
qsolve lint .
```

```text
WARNING W051 no LICENSE file found
0 error(s), 1 warning(s)
```

Lint is the static check the platform runs before it executes any of your code. The scaffold sets `license: Apache-2.0` but does not create the file, hence the warning: add a `LICENSE` file with the license text to clear it. Every code is explained in [Lint codes](/docs/build/lint-codes); [Troubleshooting](/docs/build/troubleshooting) covers common fixes. Errors exit 1, warnings do not.

## 5. Run it locally

```bash
qsolve run . -i examples/qubo_5.json --seed 1
```

```text
            depot-picker v0.1.0 on aer-cpu
┌───────────────────────────────┬─────────────────────┐
│ Metric                        │ Value               │
├───────────────────────────────┼─────────────────────┤
│ Objective (quantum)           │ -10                 │
│ Objective (Exhaustive search) │ -10                 │
│ Gap vs baseline               │ 0.000%              │
│ Uncertainty                   │ exact (stderr None) │
│ Shots                         │ 4000                │
│ Run time (s)                  │ 2.22                │
└───────────────────────────────┴─────────────────────┘
QAOA (quantum approximate optimisation): -10; Exhaustive search: -10 (accuracy 100.00%).
```

Override a parameter with `-p layers=1`; write the full JSON result with `--out result.json`. The file holds a `result` and a `provenance`, described in [Results and events](/docs/build/sdk-results-and-events). The same seed gives the same result every time.

## 6. Check the cost estimate

```bash
qsolve estimate . -i examples/qubo_5.json
```

```json
{
  "qubits": 5,
  "circuits_on_target": 1,
  "shots_on_target": 4000,
  "classical_cost_class": "low",
  "runtime_s": null,
  "notes": [],
  "by_default": false
}
```

Nothing runs. The gate `estimate_honest` fails if this is off by more than a factor of two from what a run sends. `qsolve estimate` has no `--param`, so it uses the manifest defaults. The numbers come from the `estimate()` method of your class ([Metering and cost](/docs/build/sdk-metering-and-cost#estimate)).

## 7. Run the gates and benchmarks

```bash
qsolve test .
```

```text
                                  Gates
┌──────────────────┬────────┬────────────────────────────────────────────┐
│ Gate             │ Result │ Detail                                     │
├──────────────────┼────────┼────────────────────────────────────────────┤
│ manifest         │ pass   │ depot-picker v0.1.0                        │
│ example_inputs   │ pass   │ 1 valid                                    │
│ benchmark_cases  │ pass   │ 3 cases defined                            │
│ methods_declared │ pass   │ 3 methods declared                         │
│ fidelity_checked │ pass   │ a case sets min_fidelity on a noisy target │
│ cases_pass       │ pass   │ 3/3 passed                                 │
│ reproducible     │ pass   │ identical result hash                      │
│ estimate_honest  │ pass   │ estimate within 2x of what was sent        │
└──────────────────┴────────┴────────────────────────────────────────────┘
                      Cases on sim-cpu
┌────────────────────┬────────┬───────┬──────────┬──────────┐
│ Case               │ Result │ Gap   │ Fidelity │ Time (s) │
├────────────────────┼────────┼───────┼──────────┼──────────┤
│ unconstrained-5    │ pass   │ 0.00% │ 0.998    │ 1.5      │
│ dense-random-8     │ pass   │ 0.00% │ 0.982    │ 0.4      │
│ noise-model-qubo-5 │ pass   │ 0.00% │ 0.974    │ 10.1     │
└────────────────────┴────────┴───────┴──────────┴──────────┘
ALL GATES PASSED
```

These are the gates and cases the platform runs on submission. (`reproducible` and `estimate_honest` need at least one case that ran, so a workflow whose cases all fail shows six gates.) `qsolve test` does not lint, so run `qsolve lint .` as well. The third case runs on `fake:fez@1.6`, a device noise model with every error rate multiplied by 1.6. [Benchmarks](/docs/build/benchmarks) explains each gate.

## 8. Make it yours

Change these in order, running `qsolve test .` after each step:

1. `schemas/input.json` and `examples/`: the input a user provides.
2. `__init__`, `size` and `evaluate` in `src/workflow.py`: parse the input and score a solution. Raise `WorkflowError` for a bad input and `InvalidSolution` for a solution that breaks a rule.
3. `quantum`: your circuits, run through `ctx.sampler`. See [Writing the workflow](/docs/build/writing-the-workflow).
4. `reference` and any `classical_<id>` methods: the strongest classical method that is practical.
5. `qsolve.yaml`: method descriptions, metric and envelope ([The manifest](/docs/build/manifest)).
6. `benchmarks/cases.yaml`: cases with known answers, plus one noise-model case ([Benchmarks](/docs/build/benchmarks)).
7. `schemas/output.json` and `answer()`: what people read.

Also update `tests/test_workflow.py` so `pytest` keeps passing. Bump `version` in `qsolve.yaml` whenever anything changes after a submission; see [Versioning](/docs/build/versioning).

## 9. Push to GitHub

```bash
git init
git add .
git commit -m "First version"
git branch -M main
git remote add origin https://github.com/<you>/depot-picker.git
git push -u origin main
```

The repository can be public, or private if you connect the Qsolve GitHub app ([Private repositories](/docs/build/private-repositories)). It needs `qsolve.yaml` at the root, no symbolic links or submodules, and must be under 50 MB.

## 10. Submit

Sign in to Qsolve, open the Publish a workflow page, paste `https://github.com/<you>/depot-picker` into the repository field and press Submit. The branch, tag or commit field is optional and defaults to the default branch. Qsolve pins the exact commit it fetched.

The submission moves from Waiting to Checking, then Published or Needs changes; open it to see lint findings and gates. A published workflow is runnable by its link at once and listed publicly once approved. See [Validation and publishing](/docs/build/validation-and-publishing).
