How it works
The Resolve job lifecycle from triage to attestation, what the public proof document actually proves, and what the sandboxed runner will and will not touch.
A Resolve job is a small state machine with one hard rule at the end: the charge cannot exist without the attestation. Everything below is how a job gets there — or doesn't.
Job lifecycle
POST /v1/resolve/jobs
│
▼
triage ──── decline ───▶ declined ($0, final)
│
quote t7|t19|t49
│
├── free tier left ──▶ accepted (auto)
│
▼
quoted ── POST …/accept ──▶ accepted
│
▼
running ──▶ resolved (attestation + charge)
│
├──────▶ failed ($0 — no verified fix within budget)
└──────▶ error ($0 — infrastructure; safe to resubmit)| Status | Meaning | Billed? |
|---|---|---|
triage | Transient — the issue is being read. You never see this in a response. | — |
declined | Triage judged a verified fix unlikely (wrong stack, no clear repro, needs credentials…). decline_reason says why. | No |
quoted | A flat price was set (tier, quote_micro_usd). Nothing runs until you accept. | Not yet |
accepted | You accepted (or the free tier applied). Queued for a runner. | Not yet |
running | The sandbox is cloning, reproducing, fixing, verifying. | Not yet |
resolved | Repro flipped fail → pass, regression suite green, attestation written. attestation_url is set. | Yes — the quote |
failed | No candidate passed both gates within the turn and time budget. fail_reason explains. | No |
error | Environment or infrastructure problem (broken clone, sandbox unavailable, worker lost). Resubmit freely. | No |
Statuses only move forward; resolved, failed, error and declined are terminal. GET /v1/resolve/jobs/{id} is scoped to the key's user — another user's job id is a 404.
Triage
Runs inline in your POST, against the codai model, and answers with exactly one line: QUOTE t7|t19|t49 <reason> or DECLINE <reason>. Rough meaning of the tiers: t7 — small, well-scoped (single file, clear repro); t19 — moderate (multi-file, needs investigation); t49 — hard but plausible (subtle, cross-cutting). If triage itself is unavailable the job is declined with triage unavailable, please retry — Resolve never quotes blind.
The free tier
Each user gets 3 free verified fixes. The claim is atomic: when a quotable job comes in and you still have a free slot, the job is created as tier: "free", quote_micro_usd: 0, status: "accepted" and starts immediately. free_tier_remaining in the response tells you how many are left after this one. A free job that ends failed or error gives the slot back.
Accept
Only quoted jobs accept; anything else is 409 job is <status>, only quoted jobs can be accepted. Accepting is the moment you agree to the quote — but you still pay only if it resolves.
Running
Reproduce first
The runner clones the repo at HEAD (recorded as base_commit). If you supplied test_command, it runs that: it must fail on the base commit, otherwise the job ends with supplied test_command already passes on HEAD — nothing to fix. Without a command, the agent writes a repro test — a single pytest / vitest / jest file — and confirms it fails. That failing output becomes test_output_before.
Fix
The agent gets the issue, the repo and the failing test, and edits code through a bounded loop of at most 4 turns per candidate (SEARCH/REPLACE edits plus file reads). Paid tiers fan out up to 4 candidates (the first at temperature 0, the rest at 0.8) and stop at the first one that passes; the free tier runs one. A candidate passes only if the repro now passes and the repository's own suite still passes.
Verify
Verification never fails open. Two commands run in the sandbox on base_commit + patch: the FAIL_TO_PASS command (test_command) and a PASS_TO_PASS regression command (regression_command) — the repo's full suite with the repro file excluded. Environment breakage, timeouts or a runner that vanished are error, never resolved.
Attest, then charge
The attestation row is written, then — and only then — the resolve_charges row for the quoted amount. If anything between the two fails, you have a proof and no bill, never the reverse.
What the attestation proves
GET /v1/resolve/attestations/{id} needs no authentication: the UUID is the capability. Anyone with the link can audit the fix. It returns:
{
"id": "a45764d8-…",
"kind": "codai.resolve.attestation.v0",
"repo_url": "https://github.com/owner/repo",
"base_commit": "3f9c…",
"patch": "diff --git a/src/parse.ts b/src/parse.ts\n…",
"repro_test": { "path": "tests/test_issue_42.py", "content": "…" },
"test_command": "python -m pytest -q tests/test_issue_42.py",
"regression_command": "python -m pytest -q --deselect tests/test_issue_42.py --ignore=tests/test_issue_42.py",
"test_output_before": "… 1 failed …",
"test_output_after": "… 1 passed …",
"regression_output": "… 212 passed …",
"runner_image_digest": "sha256:…",
"started_at": "2026-09-23T09:12:04.118Z",
"verified_at": "2026-09-23T09:19:41.402Z"
}| Field | What it lets you check |
|---|---|
repo_url + base_commit | The exact tree the fix was verified against. git checkout <base_commit> and reproduce it yourself. |
patch | The complete unified diff. Nothing outside it was changed. |
repro_test | The test that encodes the bug (null when you supplied test_command). Read it to judge whether it captures the issue. |
test_command / test_output_before | The FAIL_TO_PASS command and its output on the base commit — the "before". |
test_output_after | The same command after the patch — the "after". |
regression_command / regression_output | The repo's own suite, still green. |
runner_image_digest | The container image the tests ran in, pinned by digest. |
started_at / verified_at | Wall-clock window of the attempt. |
What it does not prove: that the repro test is the right test for the issue, or that the fix is the one you would have written. That is what the diff and the test content are there for — read them like a PR, with the advantage that the execution evidence is attached.
The attestation is immutable and stays public; the same URL is what the GitHub Action links from the PR.
Safety: what the runner touches
Resolve runs untrusted repository code — your tests, your install scripts — so the execution model is deliberately narrow.
- Sandboxed execution. Tests, setup and the fix candidates run in a separate, powerless Cloud Run job (
codai-swe-exec) with no database access and no gateway credentials; results come back as artifacts. The API process itself only clones the repo, reads files and builds diffs. In production the API refuses to run repository code in-process — a misconfiguration turns intoerror, never into code running beside secrets. - Public repos, read-only. Resolve fetches
https://github.com/<owner>/<repo>anonymously. It never pushes; the GitHub Action does that with your token in your workflow. - Diff-only changes. The patch in the attestation is the full extent of what changed. The agent edits via SEARCH/REPLACE blocks; it cannot run arbitrary shell in the API process.
- Bounded budgets. At most 4 agent turns per candidate, at most 4 candidates, a per-job timeout of 30 minutes with a sandbox run limit of 15 minutes each, and an orphan watchdog that marks a job
error(and returns a free slot) if its worker disappears. - Nothing hidden. The model calls happen on the
codaigateway under a session idresolve-<jobId>-…, so the upstream cost per job is auditable inusage_eventslike any other request.
Private beta means an honest limits list: public github.com repos only; Python (pytest) and JavaScript/TypeScript (vitest, jest, node:test); repos whose tests need secrets, services or non-trivial system setup will usually be declined or end in error.
GitHub Action
Install the Codai Resolve action — label an issue codai-fix and get a PR with a verified fix and a public attestation. Inputs, outputs, secrets, permissions and example workflows.
Pricing and limits
Flat quotes of $7, $19 or $49 billed only on a verified fix; three free fixes; how a charge reaches your Stripe invoice; per-user caps and timeouts.