Task-run interface¶
How a program drives one AutoCode task run: start it, read where it stands,
answer what it asks, and let it continue. This is the only supported way for
code outside the runner (the scenario harness, autocode program, the
coding-agent skill and MCP server, and the planned
architecture and multi-component layer) to control a run. Such code must not import
runner internals such as autocode.py or read state.json directly; that file has
about 140 keys and changes without notice.
Every step is one CLI invocation, and the run's state lives on disk. A caller
that crashes can reattach to the same run directory with
TaskRun(workspace, run_dir), or with TaskRun.attach(workspace) if it never
learned the run directory (it returns the workspace's only run, or None). In a
workspace where someone else may also run AutoCode, note TaskRun.runs_in(workspace)
before the start and pass it as attach(workspace, exclude=...), so that only a run
created since can be adopted.
Python client¶
tools/autocode_taskrun.py wraps the commands below.
TaskRun.start(..., run_dir=path) selects an exact fresh run directory, an
immediate child of workspace/.autocode/runs. Existing directories and symlinks
are refused before a provider launches. The optional destination is used by
saved-run replay; the run is created there without moving bound
artifacts afterward.
When the workspace is a plain, non-empty folder, the core bootstraps a task
project inside workspace/autocode-projects/ and runs there. A destination
named under the selected folder's .autocode/runs keeps its name: the run is
created under the bootstrapped project instead, and commands may keep using the
selected folder and that name as the run's handle. The status view names the
effective workspace and run_dir.
This example accepts the displayed plan and chooses question defaults or the first listed option. An interactive client should collect the person’s answer instead, using the token from the view they saw. Operational recovery needs a separate decision.
from autocode_cli.autocode_taskrun import TaskRun, TaskRunError
run = TaskRun.start(workspace, brief, options=("--engine", "codex"))
view = run.advance_until_input()
while not view["done"]:
need = view["needs"]
if need["kind"] == "answer" and need.get("resolver_scope") in ("blocker", "operational_exhaustion"):
break # Inspect recovery guidance; respond_operational() handles these requests.
if need["kind"] == "approve_plan":
view = run.approve_plan(need["token"])
elif need["kind"] == "answer":
# One question per pass: an answer consumes the request, and the questions
# left come back in the returned view under a new resolver_token.
question = need["questions"][0]
# Decision questions may have no default; choose among their options.
view = run.answer(question["id"], question["proposed_default"] or question["options"][0],
resolver_token=need.get("resolver_token"))
elif need["kind"] == "continue":
view = run.advance_until_input()
else:
break # review, planning_budget or resume: a decision for the caller or a person
TaskRun.start works directly in the given workspace (--in-place): the caller
owns the workspace, typically a worktree it created, so start one run per
workspace at a time. Files uncommitted or untracked there at the start are the
code the run starts from (base_commit), not part of its change. options
(engine and model flags) are passed whenever the run starts or advances. Any
rejected command raises TaskRunError with AutoCode's message.
An AutoCode self-build requires an executing controller outside the task
workspace, so the task cannot edit the code that runs it. With such a controller
(an installed autocode, or another checkout), an AutoCode checkout is an ordinary
project and TaskRun.start or --in-place works in it directly, main checkout
included; an ordinary CLI launch creates its own worktree by default. An unsafe new run
or advancing invocation is refused before starting a provider. Saved status,
explanations and dry runs remain readable without changing the checkpoint.
view.runner and view.evidence.runner identify the controller that last saved
the run: its package directory (path) and source revision (commit, with
+modified for local source edits). view.evidence.base_commit identifies the
Builder's starting code separately. Legacy runs without a saved identity report
runner: null; an installed package without source revision metadata reports
commit: null. Status never substitutes the current installation or Builder
revision for this saved identity. The existing resume warning names a change
in the controller revision.
To build AutoCode itself with the controller from that same source checkout, keep that checkout fixed and create a separate workspace:
git -C /path/to/autocode worktree add --detach /path/to/autocode-self-build HEAD
/path/to/controller-venv/bin/python /path/to/autocode/tools/autocode.py \
"Fix the selected issue" --workspace /path/to/autocode-self-build --in-place
Use the existing provider and model flags for the run. Keep the controller checkout and its environment unchanged until the run has stopped. An installed controller is also safe when its package directory is outside the task worktree.
Inputs fixed when a run starts, such as --ui-run, --test-root and --package, belong in start_options
instead of options: TaskRun.start(workspace, brief, options=("--engine", "codex"),
start_options=("--ui-run", str(design_run))). They are passed once; later advances
and reattachment use the saved settings. For scoped Python proof, use
start_options=("--test-root", "components/api/"); the root selects tests, not
generic write ownership. Generated component runs bind their original ownership
declaration to this caller-selected root before plan approval and execution.
An older shipped component run with no saved root uses only its exact, single
original generated ownership declaration to derive a transient root for admission
and regression proof. It does not rewrite the original task, settings, approval,
user events or historical evidence. An explicit root must still match; generic
rootless tasks gain neither a test root nor write authority from model text.
At PAUSED_COMPONENT_PLAN, needs.kind remains resume for compatibility.
First check needs.new_run_required: when true, needs.action is fresh_run,
needs.edit_required is false, and needs.feedback_action is null. The original
caller declaration is missing/ambiguous or the saved root disagrees with it.
These immutable inputs cannot be repaired by editing the model's plan. Preserve
the stopped run and use TaskRun.start for a fresh component run in a separately
caller-owned workspace, with the correct generated declaration and matching
launch-only root. That new plan still requires approval. Feedback at this stop
is rejected before recording an event or scheduling a new model draft. Never
change the original task or saved root to make the old approval usable.
With a valid caller binding, needs.edit_required is true instead: incorrect
body paths or deferred/prohibited mandatory flow can be repaired. Follow
needs.action (--edit-goal FILE) or needs.feedback_action (--feedback TEXT),
then inspect and approve the new draft with its newly displayed token. The old
approval token cannot approve it. Plain resume leaves the checkpoint unchanged
and launches no Builder; adding --test-root on resume cannot repair a
launch-only setting. A valid older rootless run needs no root flag to edit its
plan. The recovery card, progress line and issue/finder guidance distinguish
these two cases without promising that an unchanged resume repairs either one.
A caller that keeps each invocation's output, as autocode program keeps a
workstream's stdout.log, stderr.log and exit code, reads it from the client:
TaskRun.last_advanceis thesubprocess.CompletedProcessof the latest call that starts or advances the run (start, advance, resume and retries), also when AutoCode rejected it. Status reads and user actions never replace it.TaskRunError.processis theCompletedProcessof the CLI call that failed. It isNonewhen the error is not a failed call: a call that could not run or did not finish (a missing working directory, a timeout),attachfinding several runs, or a guard such asadvance_until_input's no-progress check, which is raised after its calls finished;last_advancestill holds the latest advancing call. A start that fails may still have created its run:TaskRunError.run_dirnames it when it created exactly one.- A call that outlives
timeoutraisesTaskRunErrornaming the call and its limit (status did not finish within 60 s), followed by the end of each stream that reached the caller before the limit, orno output reached the caller before the limit.TaskRunError.partial_outputholds that(stdout, stderr)whole; it isNonefor every other error. It is what arrived, not everything the call printed: unlessPYTHONUNBUFFEREDis set, the CLI's stdout to a pipe is block-buffered, and a call stopped at its limit can lose what is still in that buffer. TaskRun.cwd(cwd=onstartandattach) is the CLI's working directory, so relative paths inoptionsandstart_optionsresolve against it.None, the default, keeps the caller's. A continuation fromrestore_checkpointkeeps it.
Operator-declared prerequisites can be supplied once with --task-preflight
in start_options. A failed prerequisite pauses before paid dispatch and is
visible in the additive task_preflight status field. See
task-preflight.md for phase selection, copied input checks,
receipt reuse and supported correction; readiness does not replace proof.
Chat projection for ordinary task runs¶
A clean user-requested pause may set needs.confirmation_required: false on
its ordinary resume offer. The person's Resume click then needs no second
confirmation dialog. The need remains person-only: clients must not automatically
resume it. Missing or true preserves the client's existing confirmation policy;
failure recovery, held operational pauses, retained attempts, report repairs and
terminal stops do not receive this exception. Numeric allowance changes still
need their explicit confirmation. Named commands and CLI admission checks are
unchanged.
The additive view.chat contains messages (stable id, role, text),
an opaque context, and an optional input offer. Messages project existing
human receipts and saved completed replies. At a follow-up, the prior reply is
retained under that turn's historical record; display text grants no approval
and does not become a new requirement. Status reads do not write a transcript.
During a running step, optional progress is an array of core-authored display
lines: the inspected current activity, saved task/requirement counts and up to
three recent finished attempts in this turn. An attempt finishing does not mean
its report was accepted or its checks passed. Unknown activity stays unconfirmed;
queued and stopped steps are not described as running. Clients render these lines
without deriving stage names or success from private state. Older cores may omit
the field.
While a supervised step works, optional activity is an array of at most 20
display events from the running attempt's own provider event file, oldest first:
{kind, text, status} with kind one of command, edit or tool, status
one of running, done or failed, and, for an edit, the paths it names
(relative to the project when they are inside it, at most 8). text is one
bounded line: a command's first line, the files changed, or the tool called.
Model prose, reasoning and tool output are never copied, and only the tail of a
file inside the run directory is read. done and failed describe the tool
call, not the work: a command that exited 0 is not a passed check, an edit is
not an accepted change, and nothing here is proof, approval or authority to
retry. Activity that is not confirmed as supervised, a finished or stopped step
and a paused run have none, and the field is then omitted. Codex-style items
(also what the built-in Claude provider writes) and OpenCode or Kilo tool parts
are read; other event shapes show nothing. Older cores may omit the field.
During spec-first Requirements chat, phase: "requirements" lets clients show
the conversation without the empty task dashboard. An assistant message may
have streaming: true; its text is provisional display content from the active
Requirements attempt, never an answer, approval, report or tool receipt. Kilo
text deltas are filtered to the current assistant message; reasoning and tool
output are excluded. Requirements reuses its own saved provider session while
independent plan reviewers retain separate sessions. Requirements answers
the first message of a spec-first run, whatever it says: a greeting or an
off-topic question gets a short answer and a question about what to build or
change. Until Requirements finds work to do, the person's newest reply becomes
the task; the source log keeps the first message and each reply as separate
sources. The Job recognizer runs once the person approves what Requirements
understood, and judges the job from that understanding. Model access probes for
planning and execution roles wait until the user confirms the requirements.
When the only pending question is the core's current requirements review,
review offers its question_id, exact answer, label and bound summary.
A client may display that summary and submit the ordinary named answer action.
This confirms the understanding and releases plan preparation and review.
The resulting execution plan still needs its own displayed-token approval.
The optional review.spend_confirmation supplies the modal's title, detail
and confirm-button label before submitting that review's planning answer.
match: "trim_lower" mechanically matches the answer after whitespace trimming
and lowercasing, as the core does; corrections do not trigger the planning modal.
This spend confirmation never approves an execution plan.
The optional notice explains a stop or ongoing work in ordinary language;
chat clients keep diagnostic headings, empty counts and pause categories in
Run details. A notice never grants an action beyond the view's named command.
A build plan can explicitly request local delivery with
git_delivery: {"kind": "atomic_commit", "message": "…"}. Use null when no local
commit is requested; older reports may omit the field without granting delivery
authority. This field is part of
the sealed plan and is shown alongside its approval. A fresh passing runner
regression proof allows the controller to create the scoped commit before the
independent Tester inspects it. The committed revision receives fresh runner proof
and independent verification before completion. Plans without executable regression
cases retain the final completion boundary. Existing Git hooks are honored.
Stage workers still cannot commit. Push, merge and deployment are separate actions.
Delivery refuses preexisting dirty launch work, staged changes, changes outside the approved affected paths, or a moved HEAD. A durable receipt prevents repeating a successful commit after a lost response. Uncertain Git writes or source changes in a hook stop for reconciliation. The initial supported route is the ordinary build review workflow. Older plans promising an atomic commit only in prose require a revised, explicitly approved structured delivery contract; prose cannot authorize a Git write and cannot silently satisfy the completion gate.
input is null when free text is not an admitted action. Otherwise it names
kind (feedback, follow_up, or resolver_response), a display label, and next_command with
one literal MESSAGE placeholder. A client replaces that argument with the
person's text, checks that the command names the same workspace/run, and runs it
as an argument array without a shell. Compare against the view's workspace and
run_dir fields: after bootstrap relocation they name the run's effective
location, which can differ from the handle the client started with. The headless
conformance client's message_command() implements this same parsing boundary.
A client compares the displayed context with a fresh status before submitting;
if it changed, retain the draft and ask the person to review the new checkpoint.
The ordinary CLI independently rechecks admission when saving the action.
An operational resolver_response offer binds provide_information to the current
Resolver request ID and token in needs. It supplies corrective information only;
retry grants, scope changes, model routing and resuming remain separate actions.
In spec-first chat runs, a message at plan approval is interpreted by the saved Requirements
route when the client continues. Questions receive an assistant reply while preserving the reviewed
plan and its token; requested or mixed corrections enter normal requirements/review again and need
fresh approval. Free text, including approval-like messages, never approves the plan. The existing
--feedback action and feedback input kind remain the client contract; no client intent parsing
is needed. Exchanges are stored as plan_message user-event receipts (ID, original text, contract
token, return stage, then intent and reply_after). They are not requirement sources unless a change
is forwarded once as ordinary brief feedback. Legacy nonchat feedback keeps its correction behavior.
Saving feedback or a follow-up launches no model. A button explicitly offering
to send and continue may then follow the ordinary needs.kind=continue
action. It may never infer a plan approval, answer another question or release
an operational pause. Approval remains bound to the displayed plan token.
Older cores without chat leave free-text input disabled for an existing run.
This projection is separate from the explicit Requirements journal below: ordinary run clients do not create or drive a second conversation controller.
Core Requirements conversations¶
TaskRun.start_conversation(workspace, text, request_id="first-message") starts a
core-owned journal and one ordinary task run. attach_conversation(workspace,
run_dir=...) validates an existing conversation; read_conversation() reads its
journal and current public run view without changing either or invoking a model.
Provider, model, effort and budget selections use the same ordinary runner
settings as start(), through a creation allowlist shared by Python and the CLI.
The core session always uses joint planning, stops for explicit plan approval,
and accepts --planning-v2 for separate Planner and Plan Reviewer stages.
The default joint flow presents a Requirements planning draft and an independent
Plan Reviewer. Ordinary planning review limits still apply: a budget pause remains
a pause, and conversation refresh grants no extra review calls or approval.
Creation inputs belong in start_options; later advances use saved settings.
send_conversation(text, request_id=..., expected_revision=...) queues a human
message only. Use the revision returned by the inspected conversation and retain
the request ID for a retry of that same message. It does not call feedback,
answer, or a model. refresh_conversation(request_id=..., expected_revision=...)
explicitly commits the pending human batch through the ordinary saved-action
boundary, then advances the same controller to its next question or approval
stop. A question refresh also supplies its question_id and current
resolver_token; exact answer and operational-recovery rules still apply.
Stale revisions, conflicting request IDs and stale question tokens are refused.
During joint planning, technical decisions with a proposed default are resolved internally and
sent through drafting and review again. They are retained as user_events receipts with
actor=autocode and kind=technical_decision, outside the human answers ledger and chat history.
They cannot authorize a protected contract change or approve a plan. Genuine questions about the
requested outcome, behavior, cost, permissions or side effects still require a human answer, as do
unresolved facts and rejected assumptions. A repeated identical technical question goes to report
repair instead of another planning loop. Non-joint runs keep their existing question path.
handoff_conversation(expected_revision=..., expected_plan_token=...) records the
reviewed current draft's handoff to that same run. It does not approve the plan or
start a second task. Read the current plan and use the separate approve_plan()
action only when the operator authorizes implementation. The original handoff
source digest stays immutable; later human feedback retains its own provenance.
Assistant text and unapproved drafts never become mandatory requirements merely
because they appear in the journal. The browser dashboard and macOS app still use their existing path.
The additive view.conversation is None for ordinary and legacy dashboard
runs. A core conversation exposes actionable status/needs (including current questions and resolver tokens),
messages, pending_message_ids,
revision/applied_revision, saved human actions and request acknowledgements,
accepted planning_records/history, current_draft/draft history, and Reviewer
findings under review. Each accepted record identifies its actual saved role,
model and effort; declared_seed_routes records only explicit creation context
and uses runner-default when no model was named. A queued message makes the
shown draft stale until refresh completes its ordinary independent review.
A completed identical refresh or an unchanged revision with no pending human
messages dispatches nothing. If a client exits after saving a request, its
runner-owned acknowledgement reconciles that exact source event on restart.
If it exits after marking planning delivery as advancing, an unresolved outcome
is held for ordinary inspection/recovery; repeating refresh never blindly
replays a paid call. The immutable initial handoff and all accepted/rejected
stage artifacts remain evidence on the same run. read and attach save
nothing and cannot approve it.
The public CLI has the same operations:
autocode conversation start --workspace "$PROJECT" --text "Build a greeting CLI" \
--request-id initial -- --provider opencode --joint-planning
autocode conversation read --workspace "$PROJECT" --run-dir "$RUN"
autocode conversation send --workspace "$PROJECT" --run-dir "$RUN" \
--text "Use standard-library Python" --request-id language --expected-revision 1
autocode conversation refresh --workspace "$PROJECT" --run-dir "$RUN" \
--request-id draft-2 --expected-revision 2
Every conversation command prints JSON on stdout. Provider/model/effort/budget
options follow -- on start; unit overrides, approval and automatic approval
are refused. Attach/read are read-only. Sending several messages does not
silently refresh between them. Only an explicit refresh asks the ordinary
controller for its next result.
A rejected conversation answer can be corrected without replaying it. Read the
current question and token first. If the runner retained an input rejection,
queue the corrected text with send_conversation(...,
supersedes_request_id="old-message-id") or CLI send --supersedes-request-id
old-message-id, at the current revision. The original message and sealed
rejection remain visible. Only that rejected, unapplied message leaves the
pending batch; saved actions, active or unknown delivery, and unattempted inputs
cannot be replaced. Refresh the correction with a new request ID and the current
question/token. A stale supplied token is refused before a request is sealed.
Controller ownership and recovery¶
status()["controller"] reports the current invocation's birth-bound owner,
last saved checkpoint and completed stage, declared CLI return code, and ready
or stopped disposition. A hard exit has no collected return code; that field
stays null. Deliberate answers, approvals and unit handoffs retain an explicit
ready receipt and keep their ordinary continuation behavior. A rejected user
command records its actual exit code 2 while retaining the ready checkpoint;
it does not imply a lost controller or a successful action.
If the controller disappears between stages, status projects
PAUSED_CONTROLLER_EXIT with a concrete --resume-paused command. Reading
status or attaching does not rewrite the checkpoint. The next advancing CLI
saves that pause under the existing run lock; resume_paused() acknowledges it
and continues the saved stage. The checkpoint digest must still match before
the receipt identifies that stage or return code as the current boundary.
Retained provider attempts and runner checks keep their existing recovery
actions. A live owner or provider and a busy exclusion lock remain active;
unverifiable ownership is reported precisely without guessing absence.
Existing approval, source and provider cleanup gates still apply.
A controller exit while applying a returned, accounted report also projects this
pause after fresh inspection confirms all supervised processes stopped with
successful cleanup and the regular report file remains inside the run directory.
status()["controller"]["completed_stage"] is true at that boundary. Resume
revalidates the retained report without abandoning it or replaying that provider
call. Usage, model routes, limits and plan-approval gates stay in force. An
unaccounted, interrupted or missing result keeps its existing recovery controls.
Pausing a working run¶
While a run works (a live stage, a fresh birth-bound controller, or a controller
holding the run's locks), status()["pause"] names the boundary pause of
queued interventions: label, effect, requested and
next_command, an argument array following autocode for
intervention submit --kind pause --json. Its REQUEST_ID placeholder is the
client's own id for this request, so a retried submission returns the original
receipt instead of queueing another. The current step finishes and is saved,
then the run pauses with its next stage selected; the ordinary
--resume-paused action continues it. While a pause request waits for that
boundary, requested is true and next_command is null. The field is null when
nothing is working, when the recorded runner cannot consume interventions, and
for a stopped run. It never interrupts a step in progress or stops the run.
First interaction timing¶
status()["interaction_timing"] reports the saved first useful interactions:
| Field | Meaning |
|---|---|
launched_at |
UTC timestamp at CLI main entry, before workspace setup and provider preflight. Interpreter startup and module imports precede this boundary. |
first_question_at |
First published question or decision request rendered for the caller. |
first_plan_at |
First approval-ready plan rendered for the caller. |
first_builder_at |
First successful Builder dispatch: provider supervisor launch in serial execution, or Builder worker process launch in parallel execution. |
first_question_seconds, first_plan_seconds, first_builder_seconds |
Wall seconds from launched_at to the corresponding event. |
Builder dispatch measures the runner's job handoff; it does not claim the provider has accepted a request or emitted its first token. A parallel worker can still fail its own preflight after dispatch. Failed process creation, report repairs, dry runs and scheduling alone do not count as Builder dispatch.
The first timestamps are retained across repeated displays and resumes. Durations
include human answer/approval time and time between invocations; compare automated
runs with automated runs rather than treating human wait as model latency. Status
reads do not stamp an event or perform a model listing. An unreached event is
null, and all seven fields remain null for older runs without a recorded
launch. A later resume does not fabricate their history. Canonical completion
reports bind these facts and show them in JSON and Markdown. The generated
reliability table lists dated samples and medians of
recorded values, keeping fake and live profiles separate.
Canonical completion evidence¶
Every newly completed task publishes evidence.json and evidence.md beside
its state.json. The version 1 JSON schema is
tools/autocode-schemas/evidence-report.schema.json.
Markdown is rendered deterministically from that document. The report records
the approved plan, checked source snapshot, criterion results, independent
commands and their exit codes, artifact paths and hashes, roles/models and
attempt durations, reported usage, regression proof and remaining gaps. The report of a build,
bug-fix or design run (the run's workflow, or none for a run saved before workflow
recognition) also has approvals: the status view's approvals rows when the report was
written (who approved each plan and review, accepted completion, edited the plan or resumed a
pause, with its recorded provenance, never verified), shown in the Markdown as an Approvals
table after the intended outcome, or as None recorded for a run with none
(Who approved). Review and discuss reports, which have no approval
step, have no approvals field and no such section, even when the status view lists a resume.
Neither do verification reports, nor reports written before AutoCode listed approvals, so
older pairs still authenticate. A program or components report lists its child runs' approvals
(below). An older build, bug-fix or design report
gains the section when AutoCode saves the completed run again; reading its status does not.
A task report whose approved plan is in the status view also has plan.goal_hash and
plan.plan_file, copied from approved_contract (#720); the Markdown says them after the
agreed plan's token. Reports written before AutoCode cited them have neither field and still
authenticate; a completed run saved again rebuilds its pair with them.
When the proof's mutation check left survivors, regression_proof.mutation_survivors lists
them per test case, keyed like case_tests, and the Markdown names each one under
the regression proof; they are reported, not gaps. Review-only mutation reports
are kept separately in workflow_result.mutation_check, with survivors keyed
by the reviewed tests' actual IDs. Local
artifact paths are references, not hosted evidence. Unknown usage stays unknown;
historical price estimates remain separate from provider-reported cost.
Usage records the public accounting snapshot captured at the completed checkpoint,
including its unknown quantities. It does not attest every later raw event-log byte.
Inspection compares the report's bound evidence and accounting facts with the
supplied current public projection; a changed finding, reproduced case, base
revision, attempt or usage fact makes the report stale. Log timestamps and other
inspection bookkeeping that do not change those facts do not rebind the report.
view = run.status() # small additive evidence_report metadata
report = run.evidence_report() # authenticates current completion and both files
markdown = report["markdown"] # exactly the bytes in evidence.md
document = report["document"] # parsed evidence.json
historical = run.evidence_report(require_current=False)
status(inspect_evidence=True) includes the authenticated document and Markdown
without writing files, launching a provider or replaying checks. Availability is
missing, export_failed, recorded (historical), current, stale or invalid.
The default evidence_report() raises TaskRunError unless the report pair,
approved contract, completion and current source are authenticated. Historical
inspection permits an intact old delivery record after source or approval changes;
it conveys no authority to publish that record as current. A missing old report
is never reconstructed by a status reader. Reports do not add approval or relax
the existing completion gate. Read-only workflows can record job completion with
explicitly unverified code checks.
Declare provider evidence when starting: --evidence-provenance live, fake,
mixed or unknown. This is a caller disclosure, fixed at launch, not a model
attestation. A fixture executable named codex remains fake when declared fake;
provider names and model names cannot distinguish fixtures from real inference.
Without a declaration, custom configurations and older runs are unknown. A resume
cannot relabel their prior attempts live. The scenario harness declares fake,
live or mixed according to the route it actually launches. Tests using a fixture
must pass --evidence-provenance fake. Human plan approval remains a separate
decision.
Completed programs publish the pair beside their owned program state; their
public program run/program status summaries expose evidence_report metadata.
Completed component builds publish it beside .autocode-components/manifest.json;
the component CLI summary and MultiComponentBuild.evidence_report() expose it.
Aggregate documents reference authenticated child TaskRun.evidence_report()
documents and retain simulated runtime/deployment scope and unknown totals.
Their existing inheritance and integration gates authorize accepted historical
child deliveries; the aggregate reader does not claim fresh child verification.
Aggregate completion timestamps and elapsed wall time remain unrecorded when the
coordinator has no owned timing record. A child's completion time does not stand
for later integration checks or runtime smoke.
An aggregate also lists, under approvals, the approvals each child's own report lists
(#722): child by child, in its children order, each row as that report has it plus child,
the child's id. Its Markdown shows them as an Approvals table with a Child column, and says
what it leaves out: a program agreement (autocode program approve) records no approver, a
program does not list approvals of a plan run it was derived from (autocode program derive),
and a components run does not list approvals of the architecture it was built from. Those runs'
own reports list them. A child whose
report lists no approvals, because it was written before AutoCode listed them, is named in the
aggregate's not-verified list rather than taken to have recorded none. When no child's report
lists approvals, the aggregate has no approvals and no such section, as before.
For component --run-local, --runtime-evidence-provenance fake|live|unknown
separately discloses the runtime smoke route. Its HTTP results and sanitized local
receipt are included in the aggregate; simulated smoke does not prove real
containers or database behavior. The harness supplies this known declaration.
autocode issue pr requires a current canonical report before staging source and
embeds its exact Markdown in the PR body. Oversized reports are refused rather
than truncated. The common writer masks text matching the shared credential policy
before binding canonical JSON and Markdown; it discloses masking and preserves
raw artifact/source hashes and plan bindings. Raw evidence is not rewritten.
Readers refuse unsafe historical pairs, and issue delivery refuses any wrapper
that the GitHub transport would change, including unsafe Git-quoted source names,
before staging or pushing. Committing tested dirty source changes Git HEAD: the issue adapter
records an owned delivery receipt with the tested manifest, plan/report bindings
and resulting commit/tree. A later pr --open requires that exact clean tree and
intact historical pair; changed bytes, modes, deletions, untracked source, approval
or report digests refuse publication. The body distinguishes the tested snapshot
from the later delivery commit and does not call historical evidence current.
The owned evidence_export state field stores only paths, hashes,
binding and publication error; the export adapter owns writes, while status and
TaskRun consume it. Cross-run coordinators use public child reports, never child
state.json.
Commands¶
All commands take --workspace WORKSPACE; commands on an existing run add
--run-dir RUN.
| Step | Command | Exit status |
|---|---|---|
| Start | autocode "BRIEF" --in-place --no-chat [options] |
0 complete, 2 stopped for input |
| Status | autocode --status |
0; prints JSON, the view is under "view" |
| Display brief | autocode --show-goal |
0; prints the current brief for human review |
Export the plan as a plan file (TaskRun.export_plan(path)) |
autocode plan --export PATH: the plan shown for approval, or the approved one, as the file a team reviews in a pull request (Workflow); changes nothing in the run |
0 written, 2 refused (no plan yet, or the file cannot be written) |
| Continue | autocode --no-chat [options] |
0 complete, 2 stopped for input |
| Resume a pause | autocode resume --no-chat [options]; after editing the design at PAUSED_DESIGN_CONFLICT, autocode --resume-paused --no-chat [options] |
0 complete, 2 stopped for input |
| Grant N recoveries after resolving the cause | autocode --resume-paused --grant-recovery N --no-chat [options] |
0 complete, 2 stopped for input |
| Accept a changed OpenCode transport | autocode --resume-paused --accept-transport-change --no-chat [options] |
0 complete, 2 stopped for input |
| Adopt an inspected interrupted Investigator report | autocode --recover-job-report TOKEN [options] |
0 applied, 2 rejected; no model launched |
| Answer | autocode --answer QUESTION_ID=TEXT --resolver-token TOKEN (--answer repeatable); a question with options takes only one of its option labels or its proposed_default, exactly |
0 saved, 2 rejected |
| Respond to an operational Resolver request | autocode --resolver-request ID --resolver-token TOKEN --resolver-response provide_information --resolver-message TEXT; the next resume re-evaluates it once (below) |
0 saved, 2 rejected |
| Name the model a role stopped on quota, a content-filter refusal or an authenticated output limit continues on | autocode --answer route-ROLE=MODEL --resolver-token TOKEN, then resume the pause |
0 saved, 2 rejected |
Name the model a stopped workflow job continues on (retry_job with route) |
autocode --answer route-ROLE=MODEL --job-retry-token TOKEN, then retry the job with the new token |
0 saved, 2 rejected |
| Approve the plan | autocode --approve-goal TOKEN |
0 saved, 2 rejected |
| Approve a review | autocode --approve-review CRITERION --review-token TOKEN |
0 saved, 2 rejected |
Reject a review (TaskRun.reject_review(criterion, token, note)) |
autocode --reject-review CRITERION --review-token TOKEN --review-note TEXT |
0 saved as feedback for revision, 2 rejected |
| Plan feedback | autocode --feedback TEXT |
0 saved, 2 rejected |
A program's correction of the displayed plan (TaskRun.guide_plan(text, token)) |
autocode --program-guidance TEXT --review-token - (reads AUTOCODE_REVIEW_TOKEN); saved as the program's, never as user feedback |
0 saved, 2 rejected |
| Follow up a finished run | autocode --follow-up TEXT |
0 saved, 2 rejected |
Answers and approvals never launch a model; continue afterwards. A follow-up
is the next request in the same conversation: on a finished run it records a
new turn (turn in the status view) and reopens the run, which recognizes the
kind of job again from the new message and continues in the same run
directory. After a review, a follow-up that asks to act on the findings is
planned from them: the review's blocking findings are the requirements, so no
requirements questions are asked, and the plan still goes to the user for
approval. The rewritten task names what the previous turn wrote (its report or
note, then the files its stages changed; at most eight paths). After a design
turn, a follow-up that asks to build the design names that document, so the
build starts by checking it against the repository (check_design) instead of
gathering requirements. The build is planned afresh from the design: the design
turn's contract and requirements move to the run's history rather than being
revised, and the new plan still needs approval. After a design review, a
reply to it (an answer, a correction) is recognized as design and the Architect
revises the same review: concern ids are kept, settled concerns stay as resolved,
and review/design-review.json records every revision
(CLI). Only a finished run takes
a follow-up, and a finished run takes no answer, feedback or edited plan: either
mistake exits 2 and changes nothing (see CLI). A usage error
also exits 2, with a message starting usage: on stderr; the client checks for
it so a mistyped flag is not mistaken for a pause. A rejection also exits 2,
starting Input rejected:, and startup can exit 2 before any run exists;
TaskRun.start raises with the tail of the CLI's output so a startup failure
is never mistaken for a pause.
TaskRun.respond_operational() uses the separate Resolver response command;
an operational request cannot be answered with TaskRun.answer(), except the
model question of a quota stop or a content-filter refusal (needs.route, see Models):
TaskRun.assign_model(role, model) answers route-ROLE with a model a person
named, updates every existing --ROLE-model MODEL or --ROLE-model=MODEL
occurrence in the client's options, and leaves the run paused for
resume_paused(). A refused model raises and leaves the run paused with the
question open. At a stopped workflow job (retry_job with route, see
Failed workflow jobs) the same call answers with the job's
job_retry_token instead of a resolver token and leaves the run paused for
retry_job() with the new token.
Each answer consumes the Resolver request whose token it carries, and the
questions still open come back under a new resolver_token, so a token read
before an earlier answer is rejected. Without resolver_token,
TaskRun.answer() reads the status view and uses the current request's token
after checking that the request lists the question. When a person chose the
answer, pass the token of the view they saw, so it cannot apply to a request
they never saw. A question that lists options is a choice AutoCode acts on, so its
answer must be one of those labels or the question's proposed_default, matched exactly
apart from surrounding spaces (#1013). Any other text is rejected with the valid labels
(Input rejected:, exit 2; TaskRun.answer() raises): nothing is saved, the token stays
current, and something the options do not cover goes in --feedback (/feedback in
--chat, which asks again after a rejected answer). A question without options takes any
nonempty text. The terminal CLI also shows the remaining question IDs and
current Resolver token on stderr after each successful answer and on --status, and --status
also prints the run's cost line there (Cost: ..., from the view's cost), then, when the run has
escalated, one Escalations: ... line and a line per escalation (from the view's escalations);
the status JSON on stdout and the returned TaskRun view keep their existing shape.
TaskRun.accept_transport_change() uses the explicit transport-change command
after a person inspects the new route and the saved run reports
PAUSED_TRANSPORT_CHANGED.
TaskRun.accept_source_edit() uses --resume-paused --accept-source-edit when
a repair is paused at PAUSED_STALE_HANDOFF because a person edited the source.
The approved contract, task, budget, proof and evidence pins stay.
Reviewed Figma input changes¶
Start with native references (--figma-file and repeatable --figma-additional-file)
or a complete exported bundle (--figma-manifest) in start_options. Both inputs
produce the same durable coverage and plan ownership, exposed in view.design.
See figma.md for collection, responsive derivation and source receipts.
At a stopped, reconciled boundary, run.revise_design(manifest_path,
view["design"]["manifest_hash"], reason) proposes a new complete bundle. It keeps
the old references, approvals and independently recorded evidence, pauses for a
new plan review, and launches no model. A changed hash, active worker or pending
control rejects the correction. Resume through TaskRun.resume_paused() to
review the updated coverage; existing plan and completion gates still apply.
Exact stopped-run recovery¶
The additive view.recovery projection describes a saved pause: what happened,
what is retained, recorded failure groups and specific next actions. Its token
binds the run, task, scope, settings, attempts and failure history. It does not
grant approval, increase limits or confirm that a human request is authorized.
Current AutoResolver request and approval gates remain authoritative. Running
and complete tasks have no recovery card. Unknown pause types retain inspection
and corrective feedback rather than offering a guessed execution command.
A caller displaying this card can append --expected-recovery-token TOKEN to
an existing --resume-paused or --abandon-stage command. The runner rechecks
the token under its run lock before applying settings or writing the checkpoint.
If the pause changed, it refuses: refresh and inspect the new card. Existing
CLI callers can omit the flag; all ordinary liveness, scope and approval gates
still apply. Abandoning an interrupted attempt preserves partial work and does
not resume automatically. Stop remains terminal for that conversation.
Each card action that runs a control names its exact flags in args, for
example ["--resume-paused", "--retry-builder", "M3"] or
["--abandon-stage", "004/builder-02"]. A repair paused on an edited source
offers accept_source_edit with ["--resume-paused", "--accept-source-edit"],
the same command needs.action names. At a recovery novelty hold, where needs
is the AutoResolver request with a retry_action, the card also offers
retry_failed_stage with ["--resume-paused", "--retry-failed-stage"]: the
one explicit retry of the held step. A client runs
autocode --run-dir RUN --expected-recovery-token TOKEN ARGS... and never
derives flags from the action's kind, so a new action needs no client change.
Inspection, feedback, decision and conversation actions have no args: they
open a view or ask the person for input instead of running a command.
Inspecting current verification¶
autocode --run-dir RUN --status --inspect-evidence performs a read-only source
and evidence inspection. The additive view.verification field lists every
planned criterion and method, its recorded result and current checked / failed /
unchecked state. A normal status projection labels saved results not_inspected;
a saved PASS alone never establishes current proof.
The inspection checks the report's source, task, plan and criterion identity, then hashes its pinned project-local evidence. It rereads the source and checkpoint to detect work changing during inspection. Missing, stale or unavailable evidence keeps current requirements unchecked while retaining the old report. Human acceptance remains a separately authenticated requirement. The inspection never runs tests, changes the checkpoint, approves work or substitutes for the full completion gate. The dashboard uses this supported inspection for selected task detail; list polling does not hash every project checkout.
Status view¶
Produced by tools/autocode_run_view.py from the saved state. Fields may be
added; existing fields keep their names and meanings, and schema changes if a
meaning must change. schemas/status-view.schema.json
describes the status payload --status and --json print, with this view under view:
every field it requires is always present, and a consumer must ignore fields it
does not know.
action() is the same composition of needs, pause_category and next_command
that view() returns those three fields from, without the evidence, usage and
completion accounting around them. A caller that only needs the next action takes
it from here; a caller that needs any other field takes view(). Nothing else may
derive a recovery command from saved state.
autocode events is this contract's streaming form: one JSON line per event the
run recorded, and --follow ends the stream where action() says the run ended —
exit 0 on a completed run, exit 2 with next_command named on stderr for a run
that needs a person. The envelope is
schemas/run-event.schema.json.
autocode wait is the contract's blocking form: it ends where action() says the
run reached a person, or where COMPLETE says it finished — and, because a saved
status is only a claim, it also ends when a fresh controller inspection finds
nothing owning the saved RUNNING checkpoint. Every other read in this family
surfaces that gap only after the fact.
task is the exact saved request text, including literal shell syntax and line
breaks. task_sha256 is its UTF-8 SHA-256, also recorded when a new run starts or
a follow-up rewrites its request. The read-only view computes the digest from
saved text for legacy runs too; absent task text gives null for both fields.
A legacy request that cannot encode as UTF-8 remains visible with a null hash.
These fields identify what AutoCode received; they cannot recover text expanded
by a shell before CLI invocation. Use --brief-file PATH or --brief - to read
the original UTF-8 bytes without shell expansion.
{
"schema": 2,
"pause_category": "your_decision",
"pause_category_label": "Your decision",
"status": "AWAITING_GOAL_APPROVAL",
"next_command": "AUTOCODE_APPROVE_GOAL_TOKEN=... autocode --run-dir RUN --approve-goal -",
"done": false,
"needs": {"kind": "approve_plan", "token": "..."},
"phase": "AWAITING_GOAL_APPROVAL",
"next_stage": "orchestrator",
"iteration": 1,
"stop_reason": null,
"current_task": {"id": "task-1", "objective": "...", "milestone_id": "M1"},
"workflow": "build",
"turn": 1,
"evidence": {
"outcome": "...",
"base_commit": "...",
"acceptance": [{"id": "AC1", "criterion": "...", "status": "passed", "evidence": "...",
"validator_status": "PASS", "human_reviewed": false}],
"validator_source_revision": "...",
"findings": [{"id": "F1", "status": "resolved", "severity": "minor", "finding": "...", "source": "sol"}],
"regression_proof": null,
"unverified_risk_claims": []
}
}
pause_category groups a stopped run's cause as your_decision,
your_environment, model_or_provider or autocode; pause_category_label
is its readable label. The exact status and every existing field retain their
meaning. Both category fields and next_command are null for active or completed
runs. A typed quota/refusal route retains the provider category; an operational
request uses the additive needs.pause_origin when its published request names
that cause.
next_command formats the current needs and existing recovery controls. It
contains the owned run/workspace paths, and issued tokens travel through their
paired environment variables with the - selector. Replace ANSWER, MODEL,
N or GOAL_FILE with a decision; presentation never chooses one or grants
approval. An uncertain attempt offers abandonment first; inspect status again
before resuming. Missing issued tokens offer a noninteractive display, and a
missing original source or immutable caller binding offers inspection. A terminal
stop or dependency wait offers no recovery command. CLI gates still validate
freshness and authority when a person executes any displayed command.
For the exact finite total-time recovery, needs.command_inputs describes the
person's numeric decision additively:
{"id":"max_seconds","kind":"integer","flag":"--max-seconds","placeholder":"N",
"arg_index":2,"label":"Total active time (seconds)","current":2700,"elapsed":2800.5,
"minimum":2801,"maximum":9007199254740991}
The field is an array containing this one descriptor, and is offered only when
the named action requires --resume-paused --max-seconds N. arg_index counts
the arguments after removing the executable and owned --run-dir/--workspace
pairs. The complete remaining command must be --resume-paused --max-seconds N
--expected-recovery-token -, with the current recovery.token in
AUTOCODE_EXPECTED_RECOVERY_TOKEN. A client validates the exact command,
descriptor and token binding before substituting that one argument. A person
must enter a whole number between minimum and maximum; there is no default
and no extraction of numbers from chat. The minimum exceeds both the saved cap
and consumed active time. An empty, zero, fractional, unsafe or out-of-range
value grants nothing. Other unresolved N placeholders remain client gaps.
This choice changes only the total active-time cap. It neither changes other
limits or model routes nor approves a plan. Recovery freshness is rechecked
under the run lock, including the saved active-time accounting. Ordinary CLI
callers retain the existing --max-seconds 0 behavior; this finite input does
not expose disabling the guard. Corrective information still authorizes no
allowance: its pending deterministic review must first reach the held decision.
At a reconciled PAUSED_PLANNING_BUDGET checkpoint, the same array can instead
describe the existing finite planning-review allowance:
{"id":"planning_review_call_limit","kind":"integer","flag":"--planning-review-call-limit",
"placeholder":"N","arg_index":2,"label":"Total plan-review calls","current":2,"used":2,
"minimum":3,"maximum":9007199254740991}
Only two exact remaining command shapes are supported, each followed by
--expected-recovery-token - with the current token in the same private channel:
--resume-paused --planning-review-call-limit N (index 2, explicit resume requested) and
--planning-review-call-limit N (index 1, save only). The latter is the held
operational-information action; a client must not insert Resume. Its next fresh
status determines whether a separate continuation is available. Both inputs say
"Set review allowance": even the explicit-resume form can save first and leave
a separately named Resume. current is
the saved finite allowance and used is the charged review-call count. Both
are integers; the minimum exceeds both, and there is no default or unlimited
choice. Planning accounting participates in the recovery token's freshness.
The person confirms the exact total number of calls. This uses the existing allowance setter and preserves charged calls, recovery history, other limits, routes and plan approval. It does not reset a cycle, change allowance policy or approve the plan. Ordinary CLI callers retain their existing explicit zero semantics. Other numeric placeholders remain unsupported.
An exhausted PAUSED_TIMEOUT_RECOVERY can name one additional finite choice:
{"id":"grant_recovery","kind":"integer","flag":"--grant-recovery","placeholder":"N",
"arg_index":2,"label":"Additional recoveries","current":3,"used":3,"minimum":1,"maximum":3}
Its only remaining command is --resume-paused --grant-recovery N
--expected-recovery-token -, using the same current private token channel.
current is the existing fixed automatic-recovery allowance; used is counted
spend, including the existing legacy fallback. The finite input accepts 1 through
used, validated as safe integers, and never supplies a default. Pending
corrective information must first reach its deterministic held decision.
This takes the existing grant action: choosing 1 replenishes one spent slot (for example, 3 counted becomes 2). It does not add to the fixed allowance or bank an excess grant. The existing action clears the consecutive-timeout streak, retains historical attempts and appends its ordinary grant receipt. Accounting, legacy fallback inputs and prior grant receipts are bound into recovery freshness. Routes, global time/spend/stage limits and plan approval remain unchanged.
evidence is what the run agreed to deliver and what supports it, for reports
made outside the runner (such as a pull request body, docs/issues.md):
the approved contract's intended outcome, the base commit, one row per
acceptance criterion with its latest recorded outcome and evidence, the
findings ledger, and, for bug fixes, the runner's own fail-before/pass-after
regression proof (verdict, fail_to_pass, failures, unverified,
commands, source_revision, and case_tests: each English test case's
proving tests; null otherwise). Each acceptance row also carries
validator_status: the latest saved validation's result for that criterion
(FAIL, PASS or NOT_VERIFIED), or null when that validation has no row
for it. A failed criterion must be distinguishable from an unchecked one; the
decision report's own status is a separate field and a separate vocabulary
(verified / not). validator_source_revision is the source revision that
validation checked, or null before one. The view does not read the
workspace: after rework, validator_status still reports that validation
until a newer one replaces it, so compare validator_source_revision to the
workspace before treating the status as current. test_cases lists a
reproduced bug's regression tests in plain English (id, given, when,
then; empty otherwise; see Bug fixes). check_replay
is the current validation's checks as the runner itself re-ran them in a clean
copy: verdict, source_revision and one row per command (command,
exit_code, timed_out, output); null before a PASS validation and for
validations that predate it (see Execution).
It also carries the runner's own observations of promises a person declared:
brief_acceptance (exact CLI output formats) and risk_acceptance (process
recovery of a declared lease queue or transactional outbox, and a race between
three interpreters when the request also promises atomicity under contention:
verdict, source_revision, summary and one row per observation with its
pinned supervisor transcript), each null when the request declares none. A failed
risk_acceptance makes the replay's verdict FAIL even though the Tester's
own checks passed (see Execution).
Each findings row also has source: sol (Tester), astra (Completion
Reviewer) or runner (a failed runner lifecycle observation or, in a
--package run, the files changed outside the package, one blocking row naming
each path (Monorepo packages)). No model
report closes a runner finding: the runner closes it when a later check of its
own passes, and a person can close one with --close-finding. Closing the
package finding accepts nothing: the next check before review opens a new one,
and --accept-completion refuses while the change is there.
unverified_risk_claims lists the person's own sentences that
promise durability or concurrency but that no runner protocol exercises (kind
durability or concurrency, source_id, quote, reason); [] when there
are none. It is a disclosure, not a check: a PASS never verifies them.
For a completed code-review workflow, workflow_result.outcome and its artifact
hash name the runner-derived verdict. Its optional review_attribution object
contains pre_existing and inconclusive counts, submitted_verdict and
verdict_adjustment. The summary distinguishes nonblocking pre-existing
observations from blockers with inconclusive attribution. The findings artifact
preserves each executable blocker's disposition (kind, reason, runner,
inputs, base_proof, changed_proof); proof references carry path and
sha256. Only a validated runner disposition of pre_existing exempts a
blocking finding. Identical failures only support that exemption when the cited
file and line range are verified unchanged in the actual applied scratch source;
changed, unknown and out-of-range locations retain the blocker. Legacy findings
and reviews without an explicit patch retain blocking behavior. This attribution
applies to review findings, not the task's acceptance criteria. See patch attribution.
proof_stability is the proving tests' repeats: repeats, unstable (each
test, which side, its kind and the run outcomes), unrepeated, next_step when
a test disagreed, and note; null when the proof did not repeat tests.
diff_coverage is the same proof's line coverage of changed product hunks
(collected, tool, note, files, uncovered, unmeasured); null when the
proof did not record it. It never blocks completion.
mutation_check is the same proof's report-only mutation check (#699, see
Named test proof): collected, note,
limit_per_file, budget_seconds, files (each path, notes, and mutants
with line, operator, before, after, outcome killed, survived,
timeout or not_run, and survived_tests) and, once collected, survivors,
keyed by the same English test-case ids as regression_proof.case_tests (a
diagnosis's T1 for a bug fix, the acceptance criterion's id for a planned build): each
case's proving tests that still passed with a product line changed (test,
path, line, operator, before, after, text), {} when every mutant
that ran was killed. Every proof that did not pass, including one refused before
any test ran, has the note not run: the proof did not pass and no survivors;
null when a passing proof had no due test case, or there is no proof. It never
blocks completion. A review can instead report the original tests in its
explicit patch: subject is reviewed_patch_tests, patch_path and
patch_sha256 identify that patch, selected_files and selected_tests record
the independently run pool, and survivors is keyed by actual test IDs. These
are separate from reviewer-delivered finding tests, which must still fail on
the reviewed code. Not-run notes never mean that all possible mutants were
killed. The same report appears in workflow_result.mutation_check and the
canonical document's Review test mutation check section; no named proof is
manufactured for a review.
regression_proof.unstable lists the same unstable tests.
finding_scope_moves appears once an approved revision moved a criterion that
open findings cite to another milestone: one row per finding it re-attributed
(finding, from with the old milestone_id and criteria, to with every
resulting finding's id, milestone_id and criteria, contract_token,
at). A finding split across milestones appears in findings once per part
(see Execution).
usage is the run's tokens and cost so far: stages (finished), active_stage (the stage
running now, or null), tokens, cost_usd (reported, estimated, complete), unknown_stages
and by_role (see Cost reporting). Unknown cost is
not zero: complete is false while a stage has none or is running.
cost is the run's spend against its money cap (Money cap), priced at the
dated table autocode models --prices prints: estimated_usd (stages priced from the table),
reported_usd (stages whose provider reported a cost), inherited_usd (the spend a restored
checkpoint carried from the run it was restored from), spent_usd (their sum, which the cap is
compared with), cap_usd (the saved --max-cost-usd, or null for no cap), unpriced_stages
(stages on a model without a rate, or with no token counts recorded; not counted), complete and prices_as_of. Each stage counts
once. A null amount is unknown, never zero: estimated_usd and reported_usd are null when no stage
has that basis, and spent_usd is null when no stage is known at all; while complete is false,
spent_usd is a floor. usage.cost_usd keeps the frozen comparison rates its reports are bound to,
so its estimated can differ from cost.estimated_usd.
cost.breakdown groups that same finished-stage ledger in two alternative ways:
by_stage and by_process_outcome (completed, failed, interrupted, unknown).
Each row has stages, spent_usd, reported_usd, estimated_usd, unpriced_stages,
partial_stages, failed_stages, interrupted_stages and complete; stage rows
also name the stage and routing-aware job. The two groupings overlap and must
not be added together. Each grouping plus inherited_usd reconciles with the known
run total, subject to six-decimal rounding. Inherited spend has no stage attribution.
Active attempts and unaccounted workers are excluded, just as in the money-cap
ledger; the overall cost.complete remains false while they run. Unknown cost is
not zero. A completed process may return a rejecting review, and another attempt
may be required work: these labels do not claim acceptance or wasted spend.
The cost line calls out repeated stages and failed/interrupted attempts. For
observed repetition reasons, consult the existing efficiency.repeats projection.
escalation_outcomes counts the run's escalations and Builder retries by the outcome the runner
stamped on them (passed, paused, open; tools/autocode_route_ladder.py). The runner stamps
every one still open passed when the run completes on work a validation passed (including when
completion is accepted via --accept-completion), or when the Completion Reviewer or Resolver assigns
the next task while the last validation passed, and paused at the Builder's retry-limit pause. At that
assignment it leaves the Builder's own (its climbs, its retries and its stronger attempt) open when the
work was sent back after that validation's attempt started (a retry-policy charge or a correction task
anywhere in the run; escalations takes the same two as a failure after a PASS of the escalation's own
milestone), or when that attempt's start is not in the run's records, so a send-back cannot be ruled out.
A stamp is the struggle's: every entry still open when the struggle ends takes its end, so a climb a
later climb replaced reads passed there and failed_again as a row, and before the struggle ends a
stamp is open while its row may read passed. Read a row's outcome for how one escalation ended.
escalations lists each escalation the run made, oldest first (tools/autocode_escalation_view.py, #612):
a role's climb on its effort ladder (kind effort_climb) and the Builder's stronger attempt
(stronger_attempt; see Models). Each row has the route role that moved,
the stage that struggled and its job as the screen names it (from the role when the stage is not
known; null, never a code name, when neither names a job), at, trigger (validation_rework,
rejected_output, no_progress or abandoned_attempt for a climb, execution_failure for the stronger
attempt), detail, struggle_id, task_id (for the stronger attempt, the task of the failed attempt its
decision names), the route it climbed from and to (model, reasoning_effort, profile),
deferred_by_parallel_builder (a parallel Builder left the stronger attempt to this run, which made it
serially), inherited, attempts, cost and outcome:
attemptsare the role's stage records launched on the route it climbed to, from the escalation until the first of: a validation of its work that passed (that attempt included), unless one of its attempts was then charged as a failure (seeoutcome), or the role's next escalation of either kind (its next climb, the Builder's stronger attempt). The running one is included (active); dry runs and the runner's own stages are not attempts. A climb lasts for the rest of the run, so a later milestone's attempts on the climbed rung count until one of those ends it. A stronger attempt's attempts are this run's own Builder records: a parallel Builder's records the parent holds (worker_milestone) are its worker's, whose own stronger attempt is recorded only in its worker run. A validation judged an escalation's work when one of its attempts produced it, or, for the Builder, whose work another role validates, when the Builder's last attempt before it is one of its attempts. No other role is judged by another role's validation: a Tester PASS of the Builder's work is not the work of a Completion Reviewer's or Resolver's climb. Nor is a validation of another milestone's work, or of work on a route the role was moved to. The runner assigns every task, a rework included, a new id, so an attempt's milestone is its task's contract hash and milestone ids (current_task,task_archive): an attempt whose task is of another milestone than the attempt the escalation was made from (a later milestone, or a replanned contract's) is not its work, and for the Builder neither is a validation whose own task is: a replanned contract's first task may be a validate task, whose Tester runs before any Builder attempt. When either task is not recorded, they are not told apart. The Builder's retry policy's charge names the record of the attempt that failed: the Builder's own, or the Tester's or Completion Reviewer's that sent its work back, whose work is the Builder's last attempt for that record's task before the decision; when no Builder attempt of that task started before it (a validate task: unchanged work sent back to the Tester; a record that names no task), the Builder's last attempt of that task's milestone (of any, when it names none) before that record. A review that sends the work back also assigns a correction task (an implement task whosedecisionisREWORK), which charges the Builder's last attempt of that task's milestone before it was assigned: with the retry policy off (settings.workflowset, orbuilder_retrydisabled), nothing else records that a review sent passed work back.costprices those attempts ascostprices a stage:usd(reported plus estimated),reported_usd,estimated_usd,unpriced_attempts,unrouted_attemptsandcomplete. An attempt with no usage record is unpriced, never zero:usdis then a floor (completefalse), or null when no attempt is priced. A stage record saved without a launch route (before 2026-10-01) cannot be shown to have run on the route it climbed to: the role's such records in that span are counted inunrouted_attempts, not listed inattempts, and no validation of them decides the outcome; while there are any, the cost is unknown the same way (completefalse,usdnull when no listed attempt is priced). An escalation whose attempt never launched (a cost-cap or time-budget stop before it) cost0.inheritedis true for an escalation made before this run was created: in a checkpoint continuation (autocode checkpoint --restore), one the restored run made. Its attempts there are not in this run's stage records, so itscostis unknown, never zero:usdis null (a floor of its attempts since the restore, when it has any) andcompletefalse. What that run spent is incost'sinherited_usd.outcomeispassed,failed_again,pausedorrunning, andoutcome_basissays what decided it, the first that applies: the first decisive record after it (validation: a validation of its work passed, unless one of its attempts of its milestone was then charged as a failure: by the Builder's retry policy, as it charges a review that sends passed work back, or by any correction task of its milestone assigned after that PASS, also one that sends back a later attempt that failed its own check;later_escalation: the role climbed again, the milestone made another stronger attempt, or the Builder made its stronger attempt after climbing, or climbed after it, from this escalation's attempt), the Builder's retry-limit pause (recorded: the runner'spausedstamp, or thepausedecision that charged one of its attempts of its milestone, since the runner does not stamp an escalation it already stampedpassed), a validation of its work failing or blocked (validation), else such a charge (recorded: a retry, the stronger attempt, the retry-limit pause or a correction task charged to one of its attempts of its milestone), the runner'spassedstamp (recorded), for a role other than the Builder, only when every attempt of its role since it, until the role's next escalation, ran on the route it climbed to and there was at least one, else the run's status now (run_status: complete ispassed, running isrunning, any pause, wait or stop ispaused). The runner stamps another role's open climbpassedwhere the Completion Reviewer or Resolver assigns the next task while the last validation passed, whoever's work passed, so that stamp is not taken over a failure of its own work, once a person or the Builder's retry policy moved the role to another route, or when the role ran nothing since. Validations judge the Builder's work, and a run saved by AutoCode before this change (#612) may hold apassedstamp the runner wrote on the Builder's stronger attempt in the handler that made it, at the PASS the review just sent back, before that attempt ran, so the Builder's stamp is never taken. It is a projection of saved records, so it can change as the run continues.
A parallel Builder's own climbs and stronger attempts stay in its worker run's state, which the view does not read; a stronger attempt it leaves to the parent is listed once the parent makes it.
progress is how far the run has got, counted only from saved records
(tools/autocode_progress_view.py; added by autocode --status, which supplies
the accepted milestones, the valid review receipts and the state of the recorded
workers). line is the one line a person reads, for example
Builder working · 1 of 3 tasks done · 1 of 3 requirements checked, 1 failed · 1 open problem · nothing needed from you;
its wording may change, so programs read the fields beside it:
headline:Complete,Waiting for you,Paused,Waiting for another run,<job> working,<job> finished(its report is saved but not applied yet),Next: <job>,<job> stopped without saving a report(the recorded workers are gone; see the top-levelstaleandnext_action; a stopped runner check is namedRunner check (...)), or the status in words when no stage is known. Job names come fromautocode_roles;stageis that name.needs_you: what a person has to do, in words: whatneedsasks for, the top-levelnext_actionwhen the workers are gone, orplan approval neededwhile a saved plan waits to be shown for approval.tasks: one item per milestone of the current plan, proposed or approved (number,id,label,state,current), withdoneandtotal.stateisdone(accepted under the current contract, or the run is complete),working(the run is on it now),waiting, orunknownwhen milestone checkpoints are off (knownis then false until the run completes).requirements: one item per acceptance criterion. Its state comes from the newestPASSorFAILfor it across the Tester results under the current contract (the current validation and the ones before it);NOT_VERIFIEDand a missing row leave an earlier verdict standing.checkedis a pass in the current validation with no different source recorded since;checked_earliera pass from an earlier validation, or from one the source has changed since (rebuilt_since_check);failed;awaiting_review(a human-review criterion the Tester passed but no person has accepted yet);reviewed(a person's review receipt is valid for the current validation);uncheckedotherwise, neverchecked. Each state has a count. Results under another contract, or with no contract hash under a contract, do not count. The view does not read the workspace.problems:opencounts the open findings (items:id,finding,source,severity,blocking) plus, for a code or design review and a design-conflict stop, the findings that job saved instead (reports:kind,blocking,advisory,report_path; a design review also hasquestions, the number of questions its report asks, andrevision, 1 until a reply revises it). A design review'sblockingandadvisorycount its open concerns: a concern a reply resolved is not a problem.for_earlier_request: true after a--follow-upuntil the follow-up's own plan is drafted; the previous request's tasks, requirements and review findings are then left out.
No field is a percentage or an estimate from elapsed time.
displayed_plan is an optional structured approval projection: revision, hash,
token, acceptance_criteria, constraints and permission_boundaries. It appears
only when the last displayed token matches the current sealed contract. Missing,
modified or stale contracts do not expose it. Criterion verification_method and
human_review values are copied without interpreting or coercing model-authored
text. Automation must check these structured fields and bind the token to
needs.token; do not infer approval authority by parsing headings or review labels
embedded in --show-goal prose. This projection is not approval, execution permission
or completion proof; the existing CLI approval checks remain authoritative.
approved_contract is the plan in force: the contract that was approved, for as long
as that approval holds. It has revision, hash, token (the r<revision>:<hash>
that was approved), task_id, approved_at and body, the full approved contract
body (outcome, deliverables, acceptance criteria, constraints, permission boundaries,
end-to-end flow, technical approach, milestones and the rest) copied as saved. It also has
goal_hash, the SHA-256 of that body (what autocode plan --check FILE prints for a file
holding exactly this plan), and plan_file: null, or, when the plan was loaded with
--goal-file and not approved, changed and approved again since, the file's path and
goal_hash, the revision it was loaded as and the parts of the plan that changed since
then, apart from the runner's records of the plan's review (#720). A malformed record gives
null, never an absent approved_contract. It is
absent until the current sealed contract carries an approval of exactly its token,
once a newer draft revision replaces the approved one (a goal change, or the plan a
--follow-up drafts), and while the contract itself records an open blocking
question (approval refuses one); a question the run asks after approval does not
remove it. An edited or stale contract does not expose it, and a completed run keeps
it. Until a follow-up's own plan is drafted, and for a follow-up answered by a
review, design or discussion, which drafts none, it is still the earlier request's
approved plan; compare turn or progress.for_earlier_request. A follow-up that
builds the design the previous turn proposed is the exception: when the design
check passes, the design turn's plan is archived, so approved_contract is absent
until the build's own plan is approved. The approval is
normally the user's, but a small bug fix approved under the workflow policy the user
agreed to (the short path for small fixes, off for now; see
Bug fixes) shows it too, and the view does not say which.
displayed_plan is what a person is asked to approve; approved_contract is what
was approved. A coordinating layer such as
autocode program reads a child run's approved criteria here (program derive and
the inherited-requirement check), never from state.json. Like displayed_plan, it
is neither execution permission nor completion proof.
approvals lists every plan and review approval, completion acceptance, goal edit and
pause-releasing resume the run recorded, oldest first (#722): kind (plan, review,
completion for --accept-completion, edit or resume), at, actor (user_cli for a
person, workflow_policy for a small bug fix approved under the workflow policy),
contract_token (the plan token approved, or the draft an edit installed; null for a review,
a completion acceptance or a resume), criterion (the accepted criterion, null otherwise),
pause_status (the pause a resume took the run out of, such as PAUSED_TIME_LIMIT, or the
one a pause intervention interrupted; null otherwise) and provenance: git_user_name,
git_user_email, host, command (autocode, the resume command word when typed, and
the invocation's option names, never their values) and claimed_as (the --as name, or
null). None of it is verified (Who approved). provenance is
null for a record saved before AutoCode kept it and for a workflow-policy approval. It is a
history: an approval stays listed after a newer plan or a rejection replaces it, so read what
is in force from approved_contract and evidence.acceptance[].human_reviewed. It is empty
until the first record.
routes maps every configured role to its saved model and engine, plus
reasoning_effort (null when unspecified), resolved per-role provider, and
job (its primary job's screen name from autocode_roles). These are configured
assignments, not evidence a stage ran or a prediction of conditional routing or
escalation. Several slots can serve the same job. The actual history is in
usage.accounting.attempts: each attempt now also includes job, derived from
its stage and workflow, alongside its recorded model and launch_route.
Clients must keep configured assignments separate from actual attempts; changing
a saved route does not rewrite an earlier attempt's model or reasoning effort.
The top-level provider names the provider those OpenCode-engine routes launch through
(opencode, claude for the built-in Claude Code provider,
or a configured tool's name); it is null when every role runs on Codex. route_assignments lists, oldest first, every model a person named for a
role after its quota ran out or its provider's content filter refused it: kind (always route_assignment), role, job,
from, to, engine, stage, attempt_id, events, pause_status, at,
actor, via (answer or resume_flag) and, when via is answer, the
request_id it answered. It is empty for runs that never stopped on quota or a refusal.
For a diagnosed criterion contradiction, an answer need may include
recommended_revision with acceptance_criteria: [{id, criterion}] and
recommended_revision_response: "Accept recommended revision". Present the
request's current/proposed wording before submitting that exact response through
TaskRun.respond_operational(request_id, token, text).
Acceptance installs a draft; it does not advance the run or approve the plan.
Follow the next published need through configured plan review and explicit
approve_plan(new_token). The correction is limited to existing criterion text:
at most 32 rows, with IDs up to 128 characters and wording up to 8,192 characters.
All verification, human-review, source and permission fields are preserved.
Replaying the identical response is idempotent. A stale request or changed
diagnosis is refused, and other information keeps the ordinary hold.
information_review describes Resolver's one re-evaluation of the corrective
information last sent to an operational request (--resolver-response
provide_information), or is null: request_id, status, cause (the pause
the request was for), scheduled_at, evaluated_at, decision (hold or
continue), reason, action and receipt (the runner-owned decision record).
status is pending until the next resume at that pause (autocode resume or
--resume-paused with no other recovery flag) evaluates it once, with no provider
call. A run that left the pause first (through another control, or to a newer
request) never evaluates it, and status reads superseded. Otherwise
it is held (still paused, action and needs.action name the control it
requires, such as --resume-paused --grant-recovery N, a raised bound,
--abandon-stage ATTEMPT or, at a parallel Builder member's quota or
content-filter stop, --resume-paused --retry-builder M, which action also names
while the review is pending; at PAUSED_PLANNING_BUDGET that is the
planning_budget need's action), admitted (the run continued through the normal
admission checks) or stale (the run, request, response or evidence changed after
the response, so it was not evaluated and Resolver asks a fresh request). Information never raises a limit, resets
a count or approves anything; a later resume repeats a held decision without
evaluating again.
tool_containment says how built-in OpenCode stages other than planning run their
tools: contained (inside the kernel tool boundary, see
Execution) or uncontained_user_accepted
(the run was started or resumed with --allow-uncontained-tools; those stages have
OpenCode's own permission checks only, and each such stage record says
uncontained_tools: true). It is null for runs that launch no such stage: the
native Codex engine (unless its Investigator is pinned to an OpenCode model) and
configured providers.
tool_containment_backend names the kernel boundary the latest contained launch ran
in, read from that launch's own stage record (the running stage first), never from
the machine reading the status: backend (seatbelt or bubblewrap; null if the
record names neither), backend_version (the macOS release, or bwrap --version), and
that launch's stage, iteration and started_at. A launch saved before backends were
recorded reads as seatbelt with a null version, since Seatbelt was the only backend
then. Launches without a boundary (planning, uncontained, configured providers, Codex)
are passed over, so after a run turns uncontained this still names the last boundary
that applied; tool_containment says whether the run's stages are contained now. It
is null until a stage launches contained.
package is the repository directory a run started with --package works in,
or null for a run of the whole repository; see
Monorepo packages.
direct_rework_assignments records a repair assigned directly from a Completion
Owner's accepted REWORK report. Each entry binds the original and assigned tasks,
contract, source, report and evidence hashes, and the ordinary retry charged by
the runner. It is assignment provenance, not a Resolver diagnosis or completion
proof. The list is empty for runs that have never used this path.
Direct assignment is limited to the first ordinary repair of a single serial milestone, with an independent Tester's executed failure and a complete task within the same approved scope. Ambiguous or incomplete tasks, repeated failures, parallel/integrated work and recovery cases retain the Resolver path. Pending human decisions remain intact and hold the handoff before either route. Modified sealed evidence pauses before dispatch. Every assigned repair still requires fresh verification and independent completion acceptance.
workflow is the kind of job AutoCode recognized from the request, decided by
the first stage of every new run (recognize_workflow; in a spec-first run, once
the person approves the requirements): one of build,
bugfix, review, design or discuss (see scenarios/README.md,
"Workflows"). It is null until that stage has run, and for runs that predate
it. workflow_source is "model" when the recognizer decided it and "user"
when --workflow named it; workflow_reason is the recognizer's one-sentence
reason. Both are null whenever workflow is.
planning_depth shows how much planning the run chose and why
(adaptive planning). It is always present:
| Field | Meaning |
|---|---|
adaptive |
Whether the run plans adaptively (--adaptive-planning, the default where it applies); false on the fixed sequence |
clarity |
The recognizer's judgment of the request text, "clear" or "vague"; null on the fixed sequence, under --workflow, and before recognition |
requirements_skipped |
true when recognition judged a build request clear and sent it straight to the Planner, false when that judgment kept the Requirements stage (a vague request, a follow-up refreshing its requirements, or --spec-first); null where clarity decides nothing (another kind of job, a build from an approved design, the fixed sequence, a run saved before this field) |
plan_size |
"small", "medium" or "large", measured from the draft's milestones and the files they touch at its first review; null before that review |
size_signals |
The counts behind plan_size: milestones, affected_paths, acceptance_criteria, dependency_edges; null before the first review |
review_limit |
The plan-review call allowance set at the first review (0 is unlimited); a large plan gets one more call. A later --planning-review-call-limit changes the allowance in force, not this record |
challenges |
How many first-style reviews the Plan Reviewer has made |
approved_early |
true when the Plan Reviewer approved the Planner's draft at a first-style review, with no revision or final review |
The Plan Reviewer still reviews every build plan and you still approve it; these fields only report the decisions.
needs is null when the run is complete. Otherwise its kind says what the
run is waiting for:
kind |
Waiting for | Extra fields | Answer with |
|---|---|---|---|
approve_plan |
approval of the plan AutoCode displayed | token |
Approve the plan |
answer |
answers to clarifying questions or a decision | questions (id, question, why, options, proposed_default), request_kind; for a question Resolver published, also resolver_request_id, resolver_token and resolver_scope; with resolver_scope blocker or operational_exhaustion (and no route), action is the response command; when pause_origin is PAUSED_NO_PROGRESS because a repeated incident was held, also retry_action (--resume-paused --retry-failed-stage), the one scoped retry the hold admits after inspection |
Answer, with the current resolver_token; the questions left return under a new one. A question with options takes exactly one of those labels or its proposed_default; free text answers only a question without options. A blocker or operational_exhaustion request refuses --answer and takes action (respond_operational()), or, after inspection, retry_action (retry_failed_stage()) |
review |
a person to accept specific acceptance criteria | criteria, token, question |
Approve a review, per criterion |
planning_budget |
more plan-review calls | reason |
Plan feedback, or --planning-review-call-limit N |
recover_source |
an attempt without a saved original source identity | retained retry metadata, recovery_hint; action is null |
Inspect the archive and current changes before a new run |
retry_job |
inspection of a stopped workflow job | job_retry_token, archive, write_diagnosis, recovery_hint; route after quota, a content-filter refusal or an authenticated output limit |
Exact retry after restoring original source; with route, name another model first |
resume |
a person to inspect a pause and resolve its cause | reason; new_run_required with action=fresh_run preserves an immutable caller-binding failure and requires a new run; edit_required at a repairable component-plan stop prescribes action (--edit-goal FILE) or feedback_action (--feedback TEXT), then fresh approval; otherwise action when one command continues, such as --resume-paused --no-progress-limit N at a PAUSED_NO_PROGRESS its unchanged-batch limit caused, with no_progress_batches, the retained count N must exceed (or N is 0), or --resume-paused --retry-builder M at a parallel Builder member's quota, content-filter or output-limit stop whose request was answered without a model |
Start a fresh run when required; otherwise correct and freshly approve an edit-required plan, or resume once resolved |
dependency |
a bound producer run's delivery (see Dependencies between existing runs) | reason, producer_run, producer_workspace |
autocode wait --run-dir <producer_run> --workspace <producer_workspace>: returns when the producer needs a person or ends. Nothing to answer on this run: the dependency worker (python tools/autocode_dependencies.py ... --watch) calls --receive-dependency MANIFEST (TaskRun.receive_dependency) when the producer's verified delivery is ready |
continue |
nothing; the run can simply proceed | Continue |
A resolver_scope of operational_exhaustion or blocker means Resolver
stopped the run because it could not continue safely (for example, the
run time limit was reached). That question is for a person who has looked at
the run; a caller must not answer it with a proposed default. Its action is
the response command. A blocker is often a decision about the approved
contract (a criterion Resolver proved contradictory): the response is retained
as information and never edits the contract, so after it the run holds and
needs.reason names the path that applies such a decision, --edit-goal
body.json followed by --approve-goal.
When a role's quota ran out, its provider's content filter refused it, or its authenticated response exhausted the output limit, the answer
need also carries route: question_id (route-ROLE), role, job, current_model,
engine, cause (quota, content_filter or output_limit), stopped_model and, for a refusal,
candidates (configured models that would pass the launch rules; advice only). Its
question has no default and is never delegable; only a model a person names
answers it (--answer route-ROLE=MODEL), and the run then needs a resume. A
stopped workflow job carries the same route, with the same fields, on its
retry_job need (Failed workflow jobs).
Approving a plan or a review is a real user decision. Automated callers should do it only when a person has delegated that decision to them, as the scenario harness does for test runs.
Resolver scopes preserve separate decision authority even when they share a
needs kind. The fast execution matrix in tests/test_needs_execution_matrix.py
checks these scopes through the CLI's saved-action pipeline and the public
TaskRun methods; adding a scope or a needs-table row requires an execution case.
| Resolver scope | Need and decision | Authority retained after the decision |
|---|---|---|
clarification |
answer: answer with the displayed Resolver token |
Intent is saved for fresh planning; execution is not approved |
goal_approval |
approve_plan: approve the displayed sealed plan token |
Only that current plan gains approval |
permission |
answer: select an offered scoped permission answer |
The existing approved contract is preserved |
goal_change |
answer: answer the proposed change with its current token |
Fresh planning and approval remain necessary |
human_review |
review: accept a named criterion with its current artifact token |
Source and technical-evidence pins must still match |
blocker |
answer: use respond_operational, not an ordinary answer |
Corrective information does not edit the contract or authorize execution |
operational_exhaustion |
answer: use respond_operational, not an ordinary answer |
Existing limits hold; information is considered at the next explicit resume |
intake |
answer: answer the Resolver intake question with its current token |
Intent is saved; no project plan or execution approval is implied |
Supervision timing in status¶
view.liveness.timing reports an authenticated supervision receipt's
monotonic_elapsed_seconds, wall_elapsed_seconds, admitted_stage_limit_seconds,
excess_wall_seconds, suspension_tolerance_seconds and
likely_host_or_vm_suspension. Legacy or malformed timing is null. A runner
check exposes the same evidence at view.runner_check.liveness.timing. This is
passive evidence: stage, idle and tool limits and supervision triggers continue
to use monotonic awake time only.
Runner checks in status¶
view.runner_check describes a local check in progress before the Tester:
its stage, plain-language summary, started_at, updated_at, command and
output path. It is null when no such check is active. These checks do not
consume a model turn. A resumed run saves its running state before the first
test starts, so it does not continue to look paused throughout a long suite.
The CLI also returns runner_check_workers with checked, alive and
live_pids, using the controller's recorded process birth identity. An exited
controller makes the checkpoint stale; an inaccessible process is unknown,
not assumed dead. Inspect retained test output before restarting a stale check.
The check activity never substitutes for a passing proof or independent review.
Dependencies between existing runs¶
An already authorized delivery should not become a request for a person to assemble
hashes and review logs. At an exact stopped permission request, an operator can bind
the consumer to a producer using --bind-dependency SPEC.json (also
TaskRun.bind_dependency). The specification names producer_workspace,
producer_run, question_id, the current request_token, a human-readable label,
an explicit files list, and a destination beneath the consumer run's evidence/.
This action delegates only that delivery, records the authorization, and presents
WAITING_FOR_DEPENDENCY / needs.kind=dependency, with no question to answer.
It cannot consume a plan approval or artifact acceptance request.
Run python tools/autocode_dependencies.py --workspace CONSUMER --run-dir RUN --watch
to supervise this binding. The worker uses TaskRun.status, never another run's
private state. The additive view.delivery exists only for runner-verified current
completion, with the source snapshot, approved contract and independently recorded
Tester/completion review pins. The worker waits without model calls, copies only
the declared regular files into an atomic evidence bundle, checks the producer again,
then calls --receive-dependency MANIFEST and continues the consumer. It never imports
source into the consumer itself or accepts the consumer's integration result.
Both the binding and delivered receipt survive restarts. A single worker lock prevents duplicate transports; a crash after publishing the bundle is safely replayable. Changed producer source, missing review pins, modified deliveries and changed consumer source/contracts block transport or resume. Operator feedback or an edited plan cancels the wait so the new decision can be reviewed. Keep the worker running while waiting; if it exits with an error, reconcile the reported cause and restart it against the same run. A failed/incomplete producer cannot release the consumer.
TaskRun.grant_recovery(N) explicitly grants a positive number of additional recoveries after the operator inspects saved work and fixes the cause. It preserves recovery history and uses the CLI checkpoint guards. resume_paused() and operational guidance do not grant an allowance.
Failed workflow jobs¶
When a sessionless report_file bug Investigator lost its controller after
writing a report but before the exit/result checkpoint, status() may expose
job_report_recovery: the exact owned report path, its sha256, attempt_id,
original source_identity, and a bound token. Read the report and inspect the
attempt before calling run.recover_job_report(token) (CLI:
--recover-job-report TOKEN). This is explicit adoption of those exact bytes,
not a claim that the provider completed successfully. The raw exit remains
unknown and stage history records operator recovery provenance. The action
loads and applies the report normally, including the reproduction probe, and
launches no model; continue separately afterwards.
The offer is read-only and disappears if the report, source capture, original source, configuration, or cleanup evidence is invalid or changed. All recorded workers and the independent keeper must be conclusively dead, with an authenticated owner-loss receipt proving cleanup. Timeouts, uncertain cleanup, rejected or malformed output, foreign/symlink paths, session/event reports, writers and report-repair attempts are not eligible. There is no durable provider report finalization seal, so presence/schema validity alone never authorizes automatic adoption. Plain attach/resume retains a valid offered attempt without applying or replaying it. No offer is inferred for an already archived job failure.
A stopped Reviewer, Architect, Analyst or Investigator publishes
needs.kind = "retry_job", retaining its owning stage and archived transcript.
Inspect needs.archive, needs.reason and needs.write_diagnosis; then call
run.retry_job(view["needs"]["job_retry_token"]) for one fresh attempt under the
saved source, route and limits. The CLI equivalent is
--resume-paused --retry-failed-stage --job-retry-token TOKEN. A plain resume
keeps the pause. Stale source/configuration, a token for a different attempt,
or unresolved restoration is rejected before any model request.
A job stopped by its provider's content filter or on quota also carries
needs.route (the shape of the answer need's route: question_id, role,
job, current_model, engine, cause, stopped_model, and candidates for a
refusal). The same model is likely to refuse again, so before retrying, a person
can name another model: run.assign_model(role, model) answers
--answer route-ROLE=MODEL --job-retry-token TOKEN with the view's token. A model the
launch would refuse, or the refused model itself, raises and changes nothing. An
accepted model is saved as a route_assignment and the need comes back with a new
job_retry_token; the old one is rejected. Its route is asked again against the new
configuration: current_model is the named model, and candidates never lists it
(answering it again is refused). Then call
run.retry_job(view["needs"]["job_retry_token"]). assign_model updates every existing
split or equals-form --ROLE-model in the client's options, so the retry never
passes the old model back; the CLI refuses a --ROLE-model change for the stopped role at this stop (it would
make the exact retry stale), unless it puts back the model the retry is bound to.
The answer carries no other setting: given with a limit or another role's model it
is refused and nothing is saved, and the CLI refuses it next to
--resume-paused --retry-failed-stage (the retry needs the new token). After a
refusal or output-limit stop, until a different model is named, the need's action is that answer
(--answer route-ROLE=MODEL --job-retry-token TOKEN) and the recovery card offers
no retry_job action, since the exact retry would replay the stopped model; the
CLI still accepts the shown token. A quota stop keeps the retry in both, for once
the quota resets. Once a model
is named, progress.needs_you and the recovery card's what_happened ask only for
the retry on it, and action and the card's retry_job carry that retry. The
same holds when the job's uncertain attempt was set aside with --abandon-stage:
the reason keeps the refusal or quota, the model and the provider's words. The Architect, Analyst
and Investigator routes have no flag; only this answer moves them (an
--investigator-model in the options pins the stuck-stage Investigator and is left
as it is).
A missing or corrupt capture file does not prevent retry if the current source
exactly matches the identity saved before the attempt, including file modes and
Git HEAD. After manual restoration, the same token can be used; the old
unrestored/write_diagnosis fields remain historical evidence and the CLI
rechecks the full identity. It does not reconstruct an original identity from
the current checkout.
In status schema 2, an older attempt that never recorded its original identity
exposes needs.kind = "recover_source" and action = null, with
recovery_hint. Its token, archive and diagnosis remain available for inspection,
but cannot authorize an exact retry. Inspect the retained work and current changes
before starting a new run. No automatic restart or budget reset occurs.
Saved code checkpoints¶
status()["code_checkpoints"] adds immutable source receipts for recorded,
code-changing Builder steps. A parallel batch's verified integration is one
saved step: its milestone_id is null and members names each milestone it
served with the files that milestone's Builder changed (members is null on
other steps). Historical step metadata without source objects
is inspectable but cannot be restored. recorded_check is historical, never
current proof. This interface does not silently materialize snapshots for old runs.
Use compare_checkpoint(id) to inspect all changed paths and a bounded textual
diff (including uncommitted additions). restore_checkpoint(id, expected_token,
request_id) requires that exact comparison and returns a new TaskRun on a new
managed branch. The original branch, staged changes, run and later work remain
intact. Restoration is supported at an explicit reconciled pause of an ordinary
build run with the same approved contract. Active workers/checks, pending
interventions, human decisions, terminal Stop, progressive contracts and other
workflow kinds refuse with an explanation; no worker is interrupted.
The new continuation is paused, with the exact plan approval, saved settings,
model routes, limits and consumed allowances preserved. All milestones require
fresh proof. Implementation/validation/completion and human artifact acceptance
are not inherited as current. Earlier finding dispositions are restored; later
open findings are marked rolled back, with complete original history retained
in restoration-history.json and on the original run. Explicit resume_paused()
uses the normal Builder/Validator/completion gates. No approval or provider call
happens during restoration. Replaying the same request reconciles its owned
candidate; changed or uncertain partial candidates are preserved and refused.
The CLI equivalent is autocode checkpoint --workspace WORKTREE --run-dir RUN
--compare CHECKPOINT, followed by the mutually exclusive --restore CHECKPOINT
--expected-token TOKEN --request-id ID. The dashboard uses this supported
interface, places confirmation in chat and comparison in the Changes pane.
While a Builder step runs, code_checkpoints.working (otherwise null) names one more action in the
same shape, autocode checkpoint --working. It is display only: it prints {since, changed_files,
patch, truncated, compared_at}, the files as they are now against the newest saved step (since is
its id) or, before any step is saved, against the checked-out commit (since is "start"), limited to
the run's own scope. It carries no token and restores nothing. The Builder may still be writing, so a
file changing during the read makes the action refuse; ask again.
Each row of code_checkpoints that still has verified source objects also
names the actions a client may take, in the same shape as a connection
(actions with id, label and next_command: an argument array following
autocode). A client executes only offered actions. The compare action is
always named on an available row. The restore action is named only where
restoration is currently allowed (blocked_reason is null); it carries
--expected-token -, which reads AUTOCODE_CHECKPOINT_EXPECTED_TOKEN and is
supplied only by a fresh compare of that same checkpoint (a stale token is
refused), and the REQUEST_ID placeholder, which the client replaces with its
own stable id for this confirmation — replaying the same id reconciles its
candidate instead of creating another. Without the run's paths the array uses
the WORKSPACE and RUN_DIR placeholders; the status view supplies the real
ones.
status()["changes_by_criterion"] attributes those saved steps to the approved
plan's acceptance criteria. Each criterion lists the plan milestones that cover
it, the checkpoint ids of their saved Builder steps and the union of the files
those steps changed; a parallel batch's step counts only each member's own files
toward that member's milestone. overlapping is true when a step outside the criterion's
milestones also changed one of those files, so the file's diff may hold other
work. unattributed lists steps whose milestone is not in the plan. It is
attribution, not proof: it says where to look, not that the changes satisfy the
criterion. It is null until a plan is approved.
status()["dev_server"] is the run's development server
(Development server): configured says whether the
run saved a serve command, status is off, starting, running, stopped,
exited, failed or lost, and url is set only while it is running.
code_checkpoint names the code checkpoint the server was started on and
restarts counts the code changes that restarted it, and waiting is true while
a server that exited or failed waits for the run's next code change to start
again; a client can compare
code_checkpoint with the latest code_checkpoints row to tell whether the
preview shows the newest code. The
status command inspects it on every call; a direct run_view.view() without that
inspection leaves it null. Starting and stopping the server is
autocode serve, not a TaskRun method, and it never changes the run.
Conversation task provenance¶
New dashboard attachments, including saved legacy conversations, carry a canonical
autocode.conversation-task envelope. The full validated handoff remains in the
task presented to workers: human and assistant messages, titles, structured drafts,
routes and receipts are retained. Only its human messages are requirement sources;
assistant suggestions and unapproved drafts do not become mandatory requirements.
Later human feedback and answers retain their existing source rules. A human can
adopt a suggested literal by requesting it in a message, feedback or answer.
Source projection requires the complete canonical envelope and valid handoff digest. Extra instructions, altered envelopes and ordinary CLI tasks retain whole-task source semantics. Existing saved flattened tasks are not rewritten or reinterpreted. This format changes neither model routes nor the legacy/continuous handoff controls, and conveys no approval to implement.
Current verification ownership¶
status() includes additive verification_obligation metadata when a scheduled
clean replay is pending. It names the current obligation separately from the
saved runner_check, including its phase, exact preparation admission and current
native command custody. runner_check_matches_current tells whether the saved
check describes that same command. Status binds the current attempt, admission,
phase/owner and command hierarchy before inspecting liveness, including genuinely
live workers; it does not require a stopped result merely to display them.
An admitted or post-execution phase with no current custody borrows no earlier
command's liveness. A legacy unbound marker or unavailable
ownership remains held; status does not reconcile it, grant a retry or establish
test success. Use the displayed supported recovery action and retain interrupted
receipts separately from fresh verification.
Connections for clients¶
Connection setup is a core interface alongside task runs. A client may read
autocode connections list --json before starting a task. The version-1 object
contains connections, each with id, label, connection_status, detail, and actions.
The connection new runs use without --provider (AUTOCODE_PROVIDER, then default_provider
in ~/.config/autocode/config.toml, else opencode) also has default: true; it is display
only, so a client can say what a new chat runs on.
An action has id, label, and next_command: an argument array following
autocode, not shell text. Clients execute only offered actions. Listing reads
user-level provider configuration and executable availability; it never launches
models, reads credential stores, or runs backend commands.
connections check --provider NAME --json checks the backend without inference.
Its result is one connection object. credentials_present means the backend
reported credentials, not that every model, billing route or live task passed.
Other statuses are not_checked, unavailable, sign_in_required, and
check_failed. Status-command output and credential values are never returned.
connections login --provider NAME delegates interactive login to the backend
in a terminal, inheriting its input and output. The core resolves the offered
backend command again; the client supplies no credentials or executable. Login
does not alter a task or start inference. After login the person checks again.
A remote CLI wrapper must forward a terminal for interactive login (for example,
a container transport needs a TTY). Connections are checked in the CLI's own
execution environment, which may differ from the editor host.
Changing a client's selected backend applies to new chats. Existing task actions use their saved provider, models and budgets. Backend-specific role overrides must not be copied onto a different backend.
The always-present autocode connection advertises a setup action with
next_command=["connections", "setup"]. Execute it interactively in a terminal,
with no credential arguments or webview input. Its additional setup states are
setup_required and configured; clients must not describe
configured as authenticated live access. The core setup action neither chooses
a client's default backend nor launches a model. Discovery reads its nonsecret
metadata; an explicit check validates its private local credential setup.
Run modes for clients¶
A client that starts runs may offer a choice of how much planning a new run does
before the person approves it. autocode modes list --json returns the
version-1 list: schema_version, default (a mode id) and modes, each with
id, label, description and start_args, an argument array of start flags
following the brief. A client shows the label and description and passes the
chosen mode's start_args unchanged when it starts a run; it never builds its
own list or adds flags to a mode. The flags apply only to the new run: a saved
run keeps the flow it started with. Listing launches nothing and reads no run.
Optional spend_confirmation is "planning" for Full mode: start the Requirements
conversation without a spend dialog, then honor the core's review confirmation
before preparing the plan. Requirements conversation still uses the selected
model allowance. Quick mode uses "start" because it can plan immediately.
Missing or unknown metadata retains confirmation at launch for older cores.
| Mode | Start flags | What it changes |
|---|---|---|
full (default) |
--joint-planning --spec-first |
Requirements talks with the person and confirms what it understood before the plan is prepared (Spec-first runs). |
quick |
--joint-planning --no-spec-first --adaptive-planning |
A request the Job recognizer judges clear goes straight to the plan without Requirements; a vague one still gets it (Adaptive planning). |
Every mode keeps the plan approval, the Tester and the Completion Reviewer: a
mode changes how much planning happens before approval, never what counts as
done. A CLI without modes (exit 2 with a usage error) offers no choice; a
client then starts the full flow.
Automatic routes for new runs¶
New runs automatically select qualified models from the chosen tool's connected
accounts, preferring subscriptions and preserving independent checking. No per-role
model flags are needed, including with TaskRun.start(workspace, brief).
Explicit model, effort and tool overrides win; saved runs keep their choices.
--no-auto-select retains fixed defaults; conversation creation accepts both selection
switches too. --auto-select requires selection to
succeed instead of falling back when discovery or an independent checker is unavailable.
See automatic model selection for discovery,
reasoning levels, bounded preflight, recovery and the read-only selection preview.