codai docs
Resolve

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)
StatusMeaningBilled?
triageTransient — the issue is being read. You never see this in a response.—
declinedTriage judged a verified fix unlikely (wrong stack, no clear repro, needs credentials…). decline_reason says why.No
quotedA flat price was set (tier, quote_micro_usd). Nothing runs until you accept.Not yet
acceptedYou accepted (or the free tier applied). Queued for a runner.Not yet
runningThe sandbox is cloning, reproducing, fixing, verifying.Not yet
resolvedRepro flipped fail → pass, regression suite green, attestation written. attestation_url is set.Yes — the quote
failedNo candidate passed both gates within the turn and time budget. fail_reason explains.No
errorEnvironment 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"
}
FieldWhat it lets you check
repo_url + base_commitThe exact tree the fix was verified against. git checkout <base_commit> and reproduce it yourself.
patchThe complete unified diff. Nothing outside it was changed.
repro_testThe test that encodes the bug (null when you supplied test_command). Read it to judge whether it captures the issue.
test_command / test_output_beforeThe FAIL_TO_PASS command and its output on the base commit — the "before".
test_output_afterThe same command after the patch — the "after".
regression_command / regression_outputThe repo's own suite, still green.
runner_image_digestThe container image the tests ran in, pinned by digest.
started_at / verified_atWall-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 into error, 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 codai gateway under a session id resolve-<jobId>-…, so the upstream cost per job is auditable in usage_events like 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.

On this page