Skip to content

bob do

The machine for unstructured work (#2979, #2980). The engine is scripts/do/do.js, and the run’s state is .claude/.do-state.json. For flags and exit codes see bob do in the CLI reference. For the steps see Run unstructured work with bob do, and for the reasoning see The bob do machine.

Every event except stop is evidence-fired. bob do transition <event> runs the phase’s gate, and the event fires only when the gate passes. A bare engine transition is refused.

From Event To Payload (--data) Gate
framing framed working {"artifact":"<type>","question":"…"?} a one-line question and an artifact from the closed set
working worked landing {"enough":"<why>"} the budget is spent, or a one-line reason is given
working discard discarded {"decision":N} a decision issue naming the question (read back)
landing landed recording see Artifact evidence the declared artifact exists (read back)
landing discard discarded {"decision":N} as above
recording recorded done {"prompted_by":"#N" | "<path>" | "none"} the artifact and its prompt link each other (read back)

done and discarded are the terminals, and both exit 0. When the budget is spent, every bob do verb moves a run in working to landing with budget_exhausted: true. bob do stop removes the state file from any phase.

The table below is ARTIFACT_EVIDENCE in scripts/do/do.js, the one place the evidence is declared. The landed gate re-reads the forge or the tree at the moment of the transition and never trusts the state. A refusal names the artifact type and what is missing.

Artifact landed requires Payload
decision an issue labelled decision whose title, body or comments name the question {"issue":N}
lesson an issue labelled lesson naming the question {"issue":N}
doc a file under docs/ that exists, is committed with no uncommitted changes, and passes the docs check (the doc-keeper auditor scans it and reports no high-severity finding) {"path":"docs/…"}
spike an issue labelled research naming the question, or a committed doc as for doc {"issue":N} or {"path":"docs/…"}
issue-set at least one issue labelled req, task or bug. Each one names the run by its question or its run_id {"issues":[N,…]}
discard the decision issue a discard needs: labelled decision, naming the question {"issue":N}

“Names the question” means the question appears in the issue’s title, body or comments, compared case-insensitively with whitespace collapsed.

prompted_by is what prompted the run:

Value Meaning What is read back
#N an issue: a brief, a session report, a bug its title, body and comments
<path> a file in the checkout, such as a brief the file
none no prompt nothing. prompted_by: none is recorded explicitly

Both directions must hold. The artifact links the prompt: every artifact issue mentions #N (or /issues/N), or the doc contains the prompt’s path. The prompt links the artifact: it mentions every artifact issue, or the doc’s path. A refusal names each direction that is missing.

bob do status --json prints these at the top level. The same values live under data in the state file.

Field Set at Value
run_id start do-<UTC timestamp>
question, artifact framing the one-line question and the artifact type
budget, budget_use, budget_exhausted start / working {kind: minutes | tokens, value, source}, usage so far, and whether the budget ended the phase
enough working the declared reason, or null
artifact_ref landing (or discard) {kind: issue | issue-set | doc, issues?, path?, url?}
decision discard {number, url}
prompted_by recording #N, a path, or none
outcome the terminal done or discarded. Together with artifact_ref this is the run’s last result, which the session row of #2978 reads
refusal any refused gate {gate, reason, detail, at}, cleared when an event fires