A flow run writes its journal to the relay store's flow tables, and a resumed run, crw flow recover and the acceptance of an
agent node read it from there. This page says what is stored, when, and how an effect recorded in the journal is proven to
still be in the worktree. The events themselves (prepared, thread_requested, ... superseded) are described in
script.md.
internal/flow/journal is the engine.Journal over flow_runs, flow_calls and flow_events. It writes the events the
in-memory journal writes, in the same order, and keeps the engine's sequence numbers: Seq is run-wide, it is the event_seq
of a flow_calls row and the seq of a flow_events row, so reading a run back gives the events and the numbers the engine
saw. A row is never updated.
-
A dispatch intent (
prepared,thread_requested,fork_requested,turn_requested,interrupt_requested, and thestep_publishedandevidence_placedrows of a session call) is committed in a transaction of its own beforeAppendreturns, and so before the request it announces is sent (for a session call, before its step file and packet are written, and before a flow evidence record is written under the directory anevidence_placedrow names). The rows that wait before it are committed first, in their own transaction, so the order on disk is the order of the run. -
Every other row is written by the one run process in a batch transaction. It waits in memory until the next intent, the end of a call (its terminal row), the run's
startedand end rows (finished, orrun/paused), orFlush, and is committed then, in order. A crash loses at most the result rows after the last intent, and the reconcile pass finds those on the host from the intent that is stored. A session call that stops a subagent's turn (a pause, a cancellation, a deadline) flushes before it sends the interrupt, so theturn_startedit bound after itsinterrupt_requestedrow (the turn that row decides on a resume) is stored before the interrupt reaches the subagent. -
A journal that cannot store a row keeps the first error (
Err) and keeps its rows in memory only. The engine sends nothing whose intent row could not be stored, and ends the run with that error. The run's own end row is written with the rows still buffered, so the error is read again after it: a run whose last write failed ends with the journal's error, not as completed or paused. -
A process ends the run with one KindRun end row. It is
finished(status,error_kind, andoutcomewhen a completed script returned a milestone state, script.md) unless the operator paused the run (crw flow pause,byoperator) or an answer drained it (crw flow answerto a run a process holds,byanswer) and it would have ended completed: then it is arun/pausedrow (statuspaused, and thebyandreasonof the last such ask) and nofinishedrow, and no completion is told. A run no process holds whose last end row after its laststartedrow isrun/pausedreadspaused, notdead; its resume writes a newstartedrow. A deadline, a token budget or a quota pause ends withfinished. -
A process whose run ends cancelled (
error_kindcancelled: a SIGTERM,crw flow cancelof a run it holds) while registered ops (crw.<name>) are still running writes onecancel_waitingflow row before it waits for them:calls, one{call_id, op, node}per op in flight (nodeis the op's first argument when that is a string, or itsnodemember when it is an object, else""), sorted by call id. Each op it names writes its own terminal row after it, and thefinishedrow comes last. No op in flight, or another end (a script error, a pause, a deadline or budget stop), writes no such row. A cancelled run does not resume, so no replay reads it. -
A run process whose run ends cancelled gives up the run's executions itself (CRW-1573): after its world is closed, still under its run lock, it settles the releases the run's rows show it did not end, as the cancel of an ended run does, and prints
N execution(s) of run R given up, M slot(s) returned; a failed settle is told and changes no exit status. So one cancel is enough, whether the cancel's wait ran out, the cancel was killed, or a plain SIGTERM ended the run; the settle of the cancel that sees the lock go finds nothing more (it is replay-safe). Acrw flow cancelwhose--waitends while the process still holds the run reads the run's rows again and names what the run is ending - the ops of its latestcancel_waitingrow as<op> (call <id>, node <n>)and the agent calls whose last row is a start row as<label> (call <id>), or "no call in flight is known" - says that the run gives up its executions itself when they end (no second cancel), and exits 1. -
A run no process holds is ended by
crw flow cancelitself: under the run lock it appends onefinishedrow (statusscript_error,error_kindcancelled,byoperator,executions_closed,slots_returned), anddead: trueonly when the rows it reads under the lock show astartedrow with no end row after it (a run whose last end isrun/paused, or a held run, had no process because it ended, not because its process died). The member records what the cancel saw; readers derive the state of a run from its rows and do not read thedeadmember. A cancel that finds the run over under the lock appends nothing, so two cancels of one run write one end row, and still gives up the executions the ended run released and did not end (their slots go back), as a cancel of a run it read over from the start does. -
A binary before CRW-1487 knows no
run/pausedend: it reads a run that ended paused asdead(it resumes nothing; its pump raisesrun_deadafterrun_dead_seconds, reporting.md). A rollback past CRW-1487 leaves such runs to be resumed (crw flow resume) or cancelled by hand; a binary cannot be fixed after the fact. -
flow_runsholds the identity of the run: the workflow, the digests of the script, its helpers and its arguments, and what the caller gives the journal (Identity: the plan and its revision, the actor task, the epoch nonce, the run it resumes). It is written with the firststartedrow; a resumed run reuses it: a journal that is opened again (journal.Openwith noIdentity, ascrw flow resumeandcrw flow recoverdo) reads the plan, its revision, the actor, the epoch and the resumed run back from the run record, and a call the resumed process reaches for the first time on a node whose earlier calls were associated with an execution is associated with the same execution, so the relay finds its rows byplan_id,node_idandexecution_idlike the rows of the calls before it. -
flow_callsholds everyKindCallrow. The columns the relay reads without openingdetail_jsonare filled:kind(the call's op,agentfor an agent call),role,node_id,plan_id,execution_id(when the row carries one),commit_sha(the commit of acrw.commitrow, else theheada reviewer reads),thread_idandturn_id(a terminal row names the thread and the last turn the call ended on),operation(the turn operation), the allocation of apreparedrow (worktree_kind,worktree_path,start_commit,attempt,pending_tree,snapshot_digest),pending_treeof a completed executor call,held_tree(p_held) of a held one,input_state,tokens, andresult_bytesandresult_digest.detail_jsonis the row's Data without the result; the label is its member_label. The result is stored once, as the canonical bytes the call returned. -
flow_eventsholds the other rows (KindRun,KindFlow: phases, logs, escalations, the creation sightings, the rows that rebind a resumed run);payload_jsonis the Data plus the reserved members_kind,_path,_position,_opand_label. A member of Data never starts with an underscore. A session run adds two kinds there, with no call:session_wake, each wake of its owner thread it staged (the supervisor message itself is insupervisor_messages, obligation kindsession_step_waiting), and theescalationofreasonsession_unresponsive(script.md); and one with its call:step_unwritten, a step whose packet or file could not be written, so nothing was sent for it (input_statenot_delivered). -
A session run's exchange with its owner thread is not in the store: the steps, packets, dispatch reports and replies are files under
<state>/flow/<run>/(engine.FileStepBus), and the owner's binding and the spawn hook's ledger (with each spawn'sroleandop) are the flow-bound files ofinternal/flow/bound. What the relay reads of a session call is its rows: the newestthread_started(the subagent, the host-read pair,cwdP) and the terminal row. -
A session call's
step_publishedandturn_startedrows record the turn's place in the call's conversation indetail_json(no column holds it):conversation_turn, the conversation turn the turn is (the call's task turn 1, its repair 2), and for a turn run again (the next attempt of a lost turn, the turn a held call goes on with in place)replaces_turn, the turn it replaces. The checkpoint packet of a continuation reads the call's conversation from them, each conversation turn once. A row of a journal written before the place was recorded carries neither: its turn takes the place its step recorded, therepairmember everystep_publishedrow has (the task turn 1, the repair 2), a later turn at that place replacing the earlier one, so an older journal is read from recorded members too and never from its held rows or k (script.md). -
A session call's
evidence_placedrow (detail_json:evidence_assignment,evidence_cwd,attempt,marker) names a directory the engine is about to write the step's flow evidence record under when no other row of the call's newest generation names it (CRW-1496). It has no column of its own and no reader of a call's state counts it; the release and the restore read the directories from it. -
A command that writes into a run which may be alive (
report-up,report-ack,report-delivered; reporting.md) is not the run's process and numbers itsflow_eventsrows fromengine.ForeignSeqBase(1 << 40). A run's ownSeqstays below it: a journal that is opened again counts after the largest number below the base, so the two writers never meet a number, and the run reads the commands' rows back as flow rows of kinds it does not act on. The journal tells a commit notifier (engine.CommitNotifier) after each batch it committed, outside its lock, which is how the engine's upward hook (engine.UpwardHook) learns of a batch.
A row reads back as it was appended: the journal keeps its rows in the form the store returns (the JSON round trip of Data), so a row reads the same live and after a restart.
journal.Load reads a whole run. journal.LoadLean reads it without the results of its calls (the shape of the run is what crw flow ps
and show and the GUI need, and a result can be large), and journal.LoadAfter(run, after, limit) reads one page of rows after a
sequence number, lean, with more set when a row beyond the page exists. crw flow watch and the GUI's event stream keep the seq of
the last row they delivered and ask for the rows after it, as crw relay journal-tail does with the relay journal: a reader that was away
resumes exactly where it stopped, and no row is delivered twice. A page holds the run's own rows (below engine.ForeignSeqBase): a
cursor that took the number of a command's row would pass every row the live run writes after it, so the commands' rows are read with
crw flow reports (and are in LoadLean). Only the run process (and a command that holds the run lock) writes the run's own rows; a
reader opens the store read-only and creates nothing.
journal.ReadCallResult (and engine.RestoreCallResult, which it calls) restores the CallResult of a call from its flow_calls
rows and from nothing else: output is the stored canonical result bytes, status and reason are those of the last terminal
event of the call's newest attempt (a held call too, whose output is null), and meta is the references the rows carry (role, requested and configured
pair, thread, turns, repair, tokens, and the sequence numbers of the first and the last row). The host is not asked and no other
table is read. A call has a result only while it is ended: a prepared row starts an attempt, and a thread or turn intent, a
started thread or turn, or a recovery_cleared row after a terminal row makes the call open again, so the terminal row of an
earlier attempt is never returned with the thread and sequence numbers of a later one (ok is false until the new attempt ends).
A resumed run replays the calls it replays through the same function; the resume walk says which calls those are.
A worktree's working snapshot is the tree of the files it holds now. ops.Snapshot makes it in a private index file
(GIT_INDEX_FILE) that starts from HEAD (read-tree HEAD), takes in every change of the working files (add -A --sparse -- .)
and writes the tree (write-tree). It holds tracked changes, deletions, mode changes and the untracked files that no ignore
rule hides; a file that is ignored and not tracked is in neither the snapshot nor a commit of it. The worktree's real index is
neither read for content nor written: it holds only what was staged, and an executor leaves nothing staged. (The one thing read
from it is which entries are skip-worktree and absent from disk, so that a sparse checkout is not a deletion.)
The snapshot reads the directory it is given, exactly as given: a worktree whose name ends in a space is a worktree, and no path
that git prints is used in its place. ops.Scope asks git for --name-status -z records, so a changed path that begins with a
space keeps it.
A completed or held executor call records the snapshot of its worktree on its terminal row, as pending_tree (P) or p_held
(P_held). An executor turn that ended but whose worktree cannot be snapshotted is not completed: the call is held for
worktree_failed, its row carries pending_tree_error (and p_held when a second attempt at the snapshot succeeds), and the
script sees a held call instead of a success no commit or proof can stand on. crw.commit commits that recorded tree as it is: ops.Commit makes the commit with commit-tree on the recorded tree
and the recorded parent, moves HEAD with a compare-and-swap update-ref (a HEAD that is no longer the parent refuses the
commit), and then sets the real index alone to the new HEAD (the working files are not touched, so whatever differs from the
snapshot stays visible as a change). The path that a snapshot changes against its base and that lies outside the declared edit
regions is recorded, not refused: ops.Scope lists them for the commit row and the reviewer packet.
A proof is a function of the journal rows and, for the effects in a worktree, of what git says about it now. This page defines the functions; the resume walk says which positions a resumed run replays on them.
Build and fix. ops.Prover.Node (engine.Prover is its interface) proves the executor calls of one node against the worktree of its current attempt and
reads no other directory: the attempt is the node's last prepared row with a fresh or restore allocation, and the worktree of
an attempt that was retired (a null_model attempt leaves its uncommitted edits there) is never read. The recorded commits of
the node (crw.commit rows) after that row form a chain that starts at the attempt's start commit. A dirty worktree is not foreign
as such:
- The
HEADmust be the last recorded commit of the chain. AHEADthat is not, because someone committed or reset, is a foreign commit: the calls a recorded commit onHEAD's history covers stay proven, and the first call after the last recorded commit that is on the chain, and every later call, are not. - A recorded commit covers the calls whose state it carries: its row names the tree it committed, and that tree is the pending tree (or P_held) of the latest call that recorded it before the commit; the calls up to and including that call are covered. A call recorded after it, or any call when the committed tree is no call's pending tree, is not covered by that commit, whatever the sequence order says.
- The calls that no valid commit covers are judged by the snapshot of the worktree: it is valid when it is the tree of the last recorded commit, the pending tree P of a build or fix journaled after that commit, or the P_held of an executor call held after it. A call is proven when the snapshot is its own P (or an earlier call's, which leaves the later calls unproven). A snapshot that is none of them breaks the proof from the latest build or fix after the last valid commit.
- A call of a retired attempt (the node went on in a fresh worktree: a tier step, a rebuild) is judged without its worktree, which is
never read. A call that a recorded commit of the node made in that attempt covers, by the rule above over that attempt's calls, is
proven while the current
HEADdescends from that commit (its work is in the history the node goes on from) and not otherwise. A call no recorded commit covers left only uncommitted work, which a fresh attempt of the node prepared after it abandoned (a fresh attempt starts at the node's last commit and takes none of an earlier attempt's working files: the node lane's tier step before any commit, a rebuild after a checked fix): nothing reads its effect again, and it is proven as the recorded fact it is. A call of an attempt no fresh one followed is the current attempt's. - A split node (CRW-1459) runs its sub-tasks as turns of its one executor thread: each next one is a
subtask:<node>:<tier>:s<k>call, an executor call of the node as a fix is, and the checkpoint commit of each sub-task before the last is one morecrw.commitrow of the chain, made against the sub-task'sedit_regions. A split node makes one checkpointcrw.commitper sub-task before the last and is judged once: nocrw.verifyorcrw.landrow of the node precedes the last sub-task's commit, and verify and land read the node's last commit, as they read the last of a fix round's. Nocrw.unitCheckrow of the node precedes the last sub-task's commit either: after the verification of a commit of the node a committedcrw.unitCheckrow judges the node's whole change,base..commitfrom the verification's base, and the node'scrw.acceptrests on it alone (itsreviewer_call). A fresh attempt (a rebuild, a tier step) starts at that last commit, so the sub-tasks it holds stay proven while the currentHEADdescends from it, by the rule above. - A worktree a resume restored (below) holds the pending tree P of the last build or fix of the replayed prefix. It is the node's current worktree from the call that adopts it on, and it proves the calls of the prefix - the call P belongs to and the calls before it, which ran in the worktree of an earlier attempt - while its files are P.
- A session executor whose subagent committed left
HEADat a commit of its own. Its completed row records it (executor_head_moved: thestartit was to be at, theheadand the strayrefthat keeps it): aHEADthat is that recorded head, grew from the last recorded commit and whose tree is the call's pending tree is on the chain, and the call is proven by its pending tree as above;crw.committhen commits the pending tree onstart(its row names thestray_headit replaced). Any otherHEADoff the chain is a foreign commit. - The proof is of the state of the files, not a flag: restoring the files restores the proof. Another actor moving the integration target does not touch a worktree and does not undo a call that ended.
Other effects. Verify: a record of the same head and command that ran (crw.verify); a verification that could not run (the verifier is missing, it left no
record) is asked again on a resume. The base it names is the merge-base of the head and the integration tip, the commit the node's
work grew from, which the acceptance of the head uses too. A failed verification's row and result carry failed_tests, one entry
for each step of its record that failed and gives a reason (not a step skipped after an earlier failure): job, step, the tests (--- FAIL: <Test>) and packages (FAIL\t<pkg>)
its reason names and tail, at most 20 lines and 2048 bytes of the reason: a reason that fits is its own tail; one that leads with
crw-dev ci local's marked block (the failing lines of the output before its last lines, each --- FAIL line with its indented
message lines, then a ... line) keeps the block's first 10 lines (at most 1024 bytes), then ..., then the reason's last lines
(CRW-1569); any other keeps its last lines. A reason with no test or package line has no test and no package, only the tail. The member is additive (a row without it names no failed test), and the executor's fix packet and a fresh
thread's packet render it (CRW-1469). A node sized for a child, a bundle split or a split with no sub-tasks keeps its unreleased release row (crw.release); a split with sub-tasks is released as an executor node is (CRW-1459). Review: a record of the same head and
criteria digest (crw.review). Accept: the recorded acceptance is active, not stale, of the same head (the relay says which).
Integrate: the recorded batch commit is an ancestor of the integration ref; the target moving on keeps it an ancestor. Gate: a
passing record of the current integration head, the same command set (the plan's required commands joined with ;) and the same
range (crw.finalGate; its row holds the judgement of each piece of evidence, fresh, missing or stale, and the gate row of a head the
target has left is not replayed: the gate judges the new head). Reviewer-final: a record of the same integration range and criteria
digest (crw.reviewerFinal; the shipped close makes none, its review is the crosscheck below). The test evidence of the gate is a crw.verify row with no node (the verification of the integrated head,
which names the milestone), and the QA evidence a crw.qaReceipt row (it holds the identity of the tree the capture call ended in, which the completed row of a QA call records as qa_source); each is judged fresh by the identity of the head it stands on
(source.Compare).
Unit check. A row crw.unitCheck is one unit check of a node's work, run by the run itself (the op runs the review tool through
the review adapter; no agent call, no reviewer thread). Its data: node; mode (uncommitted: the changes the judged executor call left in
the node's worktree, or committed: the range base..commit checked out in it); call (the executor call it judged, uncommitted mode) or
commit and base (committed mode; a committed check of a run's node, the check of a split node's whole change, also names the call
whose receipt the range ends on, and range_receipts, oldest first, the call and receipt of every other executor call a crw.commit
row of the range was made from; a receipt of such a call that is not taken holds the check as the judged call's would, the row naming it
hold_call); round and final as the lane passed them; execution_id and criteria_digest of the node's release
row; tree (the fingerprint the check saw, the snapshot crw.commit commits as its tree); role unit-check; state, verdict and
findings (their count); retried and first_state invalid when an invalid answer was asked once more and the second record kept;
fallback_from (the name of the first profile, such as codex-review), fallback_state (quota), fallback_model and, when that
quota came on the retry of an invalid answer, fallback_retried, when the first tool's quota was spent and the check was asked once more
of the fallback profile (Bugbot; retried and first_state are then about the fallback's calls, and the first profile's record is the
result's fallback); hold (unit_check_quota, unit_check_no_answer, or receipt_missing, receipt_unverified, receipt_not_copied when the executor's
evidence receipt could not be read) when the lane is to hold the node; receipt (the executor's evidence receipt as the check read it:
state accepted with its sha256, copy and ignored, or, when the backend could not copy it to the step bus or the copy cannot be
read, with the path it was read from in place of the copy (where the hook accepted it, the bytes still hashing to the sha256) and the
copy_error; an accepted receipt also names handed, true when the tool that judged (the row's result) was given the prompt that
holds the receipt and false for the Codex review profiles, which take no prompt (crw.accept then names the receipt as read and kept,
not handed to the tool; a row with no handed was written when every unit check took the prompt and reads as handed); not_assigned when the call was given none, or no_executor for a committed check that names no call); and result, the reviewadapter.Result of the call that answered last (state, verdict, findings with their line-free ids, engine family, model, effort, guide
revision, prompt digest, both fingerprints), with fallback, the first profile's quota record, when the check fell back. crw.accept and the relay take a row of role unit-check whose result state is ok and that is
on the head (its commit, or its tree equal to the head's tree) as the judgment of the head. A check that was not run names no role: when
the worktree is no longer the judged call's pending tree the row has state tree_moved, tree_moved true, pending_tree and tree
(the tool is not run, since crw.commit commits the pending tree); when the worktree's HEAD is not the commit the node's attempt is
at (the start the judged call's row records under executor_head_moved, else the start of the attempt's worktree or the node's last
crw.commit since), as when the executor committed its work itself on any backend, the row has state head_moved and head_moved
(start, head), since the uncommitted changes are not the change crw.commit commits on that start; a committed check whose checkout is not the commit's tree has
state tree_moved, commit_tree and tree; a check whose tool judged another tree than the one the op snapped for it (the worktree
changed after the snap, before the call or before the retry of an invalid answer: the tool's fingerprint, or the worktree after the call,
is not the snapped tree) is no judgment either and has state tree_moved, tree_moved true, the snapped tree (commit_tree, or
pending_tree) and the tree it saw; a check during which the worktree's HEAD moved (it is read before and after each call, also one
that failed, the first and the retry of an invalid answer; a commit of the same tree moves it too, and empties the uncommitted diff the tool reads) is
no judgment either and has state head_moved, head_moved (start, the HEAD the check is of, and head) and the snapped tree
(commit_tree, or pending_tree); and a receipt that could not be read leaves receipt and hold with no
tree and no commit; neither reader takes such a row. A check is proven unless its row names a hold: a resume runs the check that held the
node again (it asks the tool, or reads the receipt, again), and the hold itself is the crw.hold row the lane writes after it (source unit_check), as for a
verification that could not run. An error of the tool's runner (a bad request, a fingerprint that could not be taken) writes no row.
Crosscheck. A row crw.crosscheck is one round of the crosscheck of a milestone at its close (CRW-1473), run by the run itself: the
op runs the review engine through the review adapter, as the unit check does, with no agent call and no reviewer thread. Its data:
milestone, plan, head (the head of the gate the close passed), round (1: the first judgment, carrying the most recent earlier
project-lane run's recorded crosscheck_pending V when it belongs to this plan, repository and milestone; 2: this run's one recheck of
its recorded round-1 V), prior when present (that canonical V), evidence_round (0, or 1 for the evidence recheck),
state (verdict, evidence, held, refused, failed, no_verdict),
reason (of a refused, failed or no_verdict round), detail, hold (of a held round: crosscheck_quota and crosscheck_no_answer when a
pass gave no judgment, crosscheck_receipt_missing, crosscheck_receipt_unverified, crosscheck_receipt_not_copied and
crosscheck_receipt_mismatch with the node when an executor receipt could not be handed over), engine_family and model once an engine
was chosen, passes (each attempt of each pass: pass A or B, attempt, state, verdict (the pass's own word, verified or
needs_changes, of an ok answer; absent for an attempt that gave none), elapsed_ms, model, prompt_digest, voided
for an attempt that read no repository, retried for the one retry of an invalid answer, detail), cleanup when a checkout could not be
removed, and verdict, the verdict V of a round that judged (null otherwise): the word (verified or needs_changes; verified only
when no merged finding blocks and every pass's own verdict is verified), the merged
findings, carried and follow_ups (the findings that do not block, by id and as one line each), guide_rev, engine_family,
engine_families, model, round, evidence_round, milestone, plan, head, base, run, author_families and authors (each
author with its source role or call, its name, model and family), receipts (each node's receipt state and sha256),
receipts_file and receipts_sha256, and passes. engine_family, engine_families and author_families remain provenance fields for compatibility; they do not select or refuse a new review. Missing author model information is recorded in author_warnings and does not block. A crosscheck is a separate tool invocation with no agent thread id. A same-thread review observation on an agent path is warning-only and recorded, and the run continues. A crosscheck is proven (ops.CrosscheckProof) by a completed row of the same
milestone, head, round and evidence round with a state other than held; a row of another head is no proof, and a held row proves
nothing, so a resume runs the round again.
For a verdict with a prior, prior_run, prior_head and prior_digest identify it. An explicit round-1 prior must equal the previous
run's recorded pending V; round 2 must carry this run's exact round-1 needs_changes V. A changed, unrelated or unreadable prior is
crosscheck_prior_invalid; no third round is served. The historical reader excludes the current run, later runs and other plans,
and checks the source repository. The close report annotates its crosscheck with previous_crosscheck, containing the previous
run's V and the observed disposition of each blocking finding: blocking, follow_up or not_reported. This report annotation
does not change the V stored by crw.crosscheck or the V used by crw.milestoneClose.
Close. A row crw.milestoneClose is the close of a milestone over its evidence {gate, crosscheck}: the gate that passed and the
crosscheck's V. The op refuses, writing no completed row (reason close_unverified), evidence whose crosscheck is not a verified V of
the gate's milestone and head, evidence with no crosscheck whose final (the reviewer-final review of a journal of a close made before
the crosscheck) is not a verified verdict, and evidence with neither. The row names the range, the tip the close judged and whether the
target moved on after the gate (moved); the answer carries the gate, the crosscheck V (crosscheck, null when absent) and final as
they were given. The report of the close (crw.reportUp) stores a body that names the integrated range and, one line each, Final gate:,
Crosscheck: (V on one line), Reflection: (the reflection's alignment: ALIGNED, MISALIGNED or NOT_ASSESSED), Final review: and
Audit:, with none for a piece the close did not make (reporting.md).
The design ledger and the signals. A row crw.designLedger is a revision of the milestone's ledger (revision, digest, design, answers,
architect_call and architect_handle, the signal that made it, base_tip - the head the DESIGN ran at: for r0 the start commit of the architect's call-private checkout as the architect's prepared row records it (reached through resume_of from a design fix that went on in that directory), never the integration tip read after the audit, and "" when no placement recorded one; for a later revision the head its re-plan pinned - and slices, the slice digest of every node of the plan it was made
against: for a revision after the first the plan the architect and the auditor evaluated, as the decision read it). Revision ids are used once: the next is named after every ledger row of the milestone, the ones a resume superseded included. The ledger that stands is the newest
row no superseded row came after; the handle of the architect thread is that of the newest row that stands among the revisions and the crw.reflection rows. A release is proven
while its revision stands; a release under a revision a resume superseded is proven when the node's slice (slice_digest, the slice of the plan when the node was released) is the
one the revision that stands was made against, and a flow row release_rebind (node, from_revision, to_revision) says the replay was kept. The flow row revision_superseded
(milestone, revision, signal) names a revision whose signal a resume did not replay. A signal is the rows crw.signalBind (milestone, k, prev, members as (kind, call, node,
digest), attempt (live:<n> for a recorded signal that could not replay, n one more than the attempt of the row it did not replay), started, unstarted and blocked nodes, tip, plan_digest and revision_read), crw.signalDone (outcome, revision, reasons, digest) and, at the end of the path,
crw.signalClose. The nodes are the milestone's, in the scheduler's order, from the run's crw.milestone row (CRW-1439): a node is started from its release on, from the design row that sized it for a split or a child, or when its newest crw.release row that stands released nothing and either sized it (kind child, split, bundle_split) or found the plan had dropped it (node_not_in_plan) or the epoch lost (epoch_lost); a newest plan_changed, relay_plan_changed, node_not_designed, design_* or hold row leaves it unstarted. A node that has not started is blocked when a node of the milestone it waits for (after) ended so, was given up (released, then a crw.escalate of it with closed true and no later crw.integrate), or is blocked itself; a held predecessor or one waiting on an open escalation blocks nothing. The rest are unstarted. A row recorded before CRW-1439 has no blocked and replays as recorded. A member is a RECEIPT (kind receipt: its call and the digest of its result) or a plan revision a lane read before its release (kind revision: call plan:<occurrence>:<digest>, the digest; the row of crw.planRead with changed, plan_digest, occurrence, base, judging). base is the plan the audited DESIGN stands on: the plan the current revision was made for (its milestone_digest), or the plan a later crw.replanSignals row of a signal judged when that signal's crw.signalDone is continue (the signal kept the revision for it). A read whose plan is the one a bound and not yet decided signal read is no trigger: judging names that signal and the lane's release waits for it. An occurrence is after:<seq>: the journal sequence of the newest standing observation of another digest (crw.planRead, crw.signalBind, or the milestone_digest a crw.replanSignals row of a signal judged), or the first DESIGN ledger row when no such observation exists (initial without a ledger). Reads of the same uninterrupted occurrence coalesce even across a signal bind of the same digest; a return to an earlier digest after another observed digest has its own trigger and release wait. The recorded read preserves its occurrence on replay, and only a standing signal that took that occurrence proves a changed read. Older reads without an occurrence keep their recorded plan:<digest> key. The engine reads three things for the signal path and writes none of them: crw.callFacts, crw.recordedSignal and crw.priorRelease (script.md). Every release of a lane compares the plan with that plan in the flow, before Relay.Release, and the relay's release commit compares the node's slice of that plan again (CRW-1288): the crw.release row of a pinned release records plan_digest (the plan the relay had when the flow read it for the comparison) and pinned_plan, and when they differ, or when the relay finds another slice of the node at its commit, the node is not released (released false, reason plan_changed); that row is proven as the decision of its moment, and the lane's next positions are a new crw.planRead and a new release. A relay refusal while the plan the flow reads again is still the pinned one is reason relay_plan_changed and raises the run-level hold plan_superseded (CRW-1291): the release writes a flow row escalation (reason and ask plan_superseded, the node, release_call, and evidence: plan, node, pinned_revision, head_revision, held_execution, held_run, slot - returned for an execution of the run, which the release closes with the reason plan_superseded, owned_by_run for another run's, none), and the row names it (escalation, hold, and slot_released when it closed the execution). While a hold no decision_applied row answers stands, every crw.release of the run answers plan_superseded (hold) and asks the relay nothing; the call that raised a hold, run again, answers that hold as its own and closes its execution again (two releases refused at once raise one hold each). The release that raised a hold is proven for good; a release the hold answered is proven while it stands. The slice_digest of a released row is the slice of the manifest the relay committed; the relay answers the replay of a release only to a request that expects the slice it was committed under, so a release committed before a plan change and not recorded in the journal is plan_changed for a request under the new slice, not a release; an unreleased row's is the slice the flow asked for.
Audit. A row crw.audit (the call row of the audit, written by crw.audit in a run and by crw flow audit beside it) holds the
audit results of acceptances: results, each with the acceptance id, the node, the head, the reviewer call, the thread and turn it read,
the checks and the state (ok, mismatch, unobserved). The newest row that audits an acceptance decides whether it passed
(audit.Passed); a reviewer thread is archived only for an acceptance that passed (crw.archive). The completed crw.accept row names what the acceptance rests
on: basis (review, or unit_check) and reviewer_call (the reviewer's call, or the call of the crw.unitCheck row), besides
node, head, acceptance_id, premerge_digest and plan; an older row names neither and is audited on the newest verified
crw.review of its head. An acceptance on a unit check rests on no reviewer thread: its audit checks that row (unit_check_row: a
completed check of the node with role unit-check, state ok, verdict verified over no blocking finding, on the head by its commit or by the head's recorded tree, under the
criteria digest of the node's release) and the executor pair, reads nothing from the host and names no thread, and crw.archive
neither archives nor keeps anything for it. A land-head acceptance has no executor call, so its executor pair and its result are
unobserved.
Recording calls. escalate, reportUp, hold, tierStep, spawnDecision, stepRecord, exploreRoute, exploreRecord, designAudit, designLedger, qaReceipt, qaCapture, reviewerFinal, audit, archive, replanSignals, currentDesign, reflection, signalBind, signalDone, signalClose, designHead and releasedPlans only record a fact. releaseChild was the old project lane's start of a node's child session (a code id, not a layer); no
script calls it and no lane installs it now (CRW-1415). A run whose journal holds a crw.releaseChild row is a legacy child-session run, and
it is not resumed: the current lane sends a node sized for a child up as a size_split escalation at the position where the old lane
decided and started the child, so a resume would not replay that start - it would escalate the node live, leave the child running and run
every later position of the parent's path live. engine.Run refuses the resume of such a run before it writes a row (and the resume of a
run whose spec or script names the retired lane's workflow, even before its first row), engine.Recover appends no recovery row to it, and
crw flow resume RUN --detach, crw flow answer and crw flow next ask the same before they start a run process. Every command that would
write to the run's journal, or make its process write, refuses it with legacy_issue_child_run before it writes or signals, whether a
process holds the run or not: crw flow cancel (no finished row, no execution given up), crw flow pause, crw flow recover, crw flow dispatched (no report in the run's inbox), crw flow answer and crw flow report-up; crw flow audit prints its results and records no
row. The delivery bookkeeping of a report already on record (crw flow report-ack, crw flow report-delivered) records the delivery and
not the run's progress, and is kept. The refusal names the legacy child-session path and the crw.releaseChild row. The run's records stay
read only: crw flow show, the summary and the run readers read them. A call with a completed row
of its own operation at its call id is not sent again (a completed call of another operation at the same position, such as an
agent call of the earlier script, is not its record), whatever else is true; a call without one is executed live under the same call id and the same idempotent
request id (ops.RequestID).
The binary installs engine.RecoverEnv: it opens the relay store (--state DIR, else CODEX_SESSION_RELAY_STATE, else the
relay's discovery for the control socket) and connects to the App Server control socket (--socket PATH, else the default one).
It finds the run that holds the call (or takes --run), opens its journal, runs the recovery of script.md
and commits the rows of the recovery together, in one transaction, when it ends. The command exits 0 when it recovered the call, 1 when the proof does not hold
(nothing is appended but the sighting of a closed connection), and 2 for a command line, a store or a call it cannot use.
A run resumes with Config.Resume; crw flow resume RUN SCRIPT is the command. The journal is read in this order:
- The pass of reconcile looks up on the host every call whose last row is a dispatch intent, and every call whose outcome was left unknown and not cleared. What it cannot prove holds the node.
- The script runs again from its top. Every position it opens - an
agent()oragent.call, acrw.*call, a join, aworkflow()- is looked up at its own(path, position)in the rows the earlier processes wrote, and replays or runs live.
There is no reuse across positions. A result belongs to the (path, position) it was recorded at, within the journal of one run;
no call is found by its prompt, and no earlier lineage's result is a cache (this is the deliberate departure from Claude's
resumeFromRunId, which matches calls by key and occurrence across the whole run and asks no host: here the host is reconciled
first, the walk is per path, the conversation a call starts from is part of what must match, and nothing is global).
A position replays only when all of these hold:
- (a) every earlier position of its path replayed;
- (b) every position of the parent path before the call that created this path replayed. Whether that call - a join - replays is not asked: a join replays only when all its item paths do, so asking would make one lane's suffix run every sibling lane;
- (c) its input digest is the recorded one. The digest covers the canonical prompt, the schema, the role and its pair, the node,
step decision, head and directory options, the digests of the joins settled before it on its path, and the context boundary:
{fresh, packet digest}for a call that starts a thread; for a continuation the handle's thread, itsthrough_turn_idand the digest chain of the turns up to it with the result digest of the call each belongs to, so a continuation whose conversation would hold turns outside the replayed prefix never matches. The prepared row has it asinput_digest, withprompt_digestand thecontext; - (d) its input snapshot is the recorded one.
Config.InputSnapshotreads the facts only an external act changes (the node's slice of the plan revision, the criteria digest, the acceptance and integration ids of its dependencies); the prepared row keeps them asinput_snapshot. Git facts are never compared with live HEAD: a start state is rebuilt from the journal, so a commit the run made later never invalidates an earlier call; - (e) the effect it produced is still there: for an executor, the pending tree P of its completed row is carried by a recorded
commit or is the state of the node's current worktree (
engine.Prover, whichops.Proverand the App Server backend implement; a run with no prover has nothing to prove against); for an op,Op.Proven. A call that is not an executor's has no effect to prove (a reviewer's, an explorer's); - (f) when it comes after a join on its path, the join replayed;
- (g) its last event is not
held(below).
A completed, null_model, refused or outcome_unknown call replays through RestoreCallResult; a call refused before it
opened (policy, pair, schema) replays when it was refused for the same reason. A call whose outcome is unknown and not cleared is
never run again, whatever the walk says: its result stays unknown and its node stays in recovery_hold.
A join - parallel, pipeline - settles with a digest: the canonical digest of its item paths' final results ordered by path
key, written on the join_settled row. It replays when no position under it ran live and the digest is the recorded one; the
digest joins the chain every later input digest of the path carries, so the positions after a join whose items changed run live
though their own arguments did not. A workflow() position is a join of its one child path. The order the items finish in, and
the order the array lists them in, are not identity: items are keyed by their explicit key or their index.
crw.<name> calls with an effect outside the engine - commit, verify, review, accept, integrate, hold, a report to the
relay - are engine.Ops registered in Config.Ops (ops.NewLane(...).Ops() registers the operations of the project lane, listed
with what each replays on in script.md). An op takes a position like a call. Its completed row (crw.<name>, with the
input_digest of its arguments, its request_id, its result and what its proofs read: the node, commit and tree of a
commit, the acceptance_id of an acceptance) replays when the position's earlier positions replayed, the arguments are the same
and Op.Proven says the effect is there. An op that ran and did not write its row runs again under the same call id and the
same RequestID, so whoever it asked sees one request. An op that runs live at a position whose earlier attempt the journal
ended (a completed row, or a refusal of other arguments) because its arguments changed or a position before it did not replay
is a request of its own: its RequestID is AttemptRequestID(run, call, n) for the n-th ended attempt, so a receiver that
deduplicates by request id does not answer it with the earlier attempt's result. crw.tierStep, crw.clock and crw.classify are the engine's own: a
resumed script gets the step decision, the time and the state it got before.
The first position that does not replay, every later position of its path and every path a later position of it opened are the
suffix. The calls the earlier processes ended there are ended superseded (reason superseded_by_resume, with the lineage of
the resume: 0 for the first process of the run, one more for each resume). A turn an earlier process left running there is first
interrupted and the interruption confirmed with thread/read; the call it belongs to is ended after that. Every other path -
the sibling items of a join included - is untouched and replays. A node whose accepted result has another spec or packet is not
rebuilt: the call is refused (accepted_node_changed) and the refusal names the route, a plan revision with update_node (which
makes the acceptance stale) or dag-correct for an oversized child.
The first executor call of the suffix on a node that had been worked on rebuilds the boundary before its prepared row:
- The old worktree's HEAD and working snapshot are kept as one commit on
refs/crw/flow/<run>/superseded/<lineage>/<path>; the path is one percent-encoded component followed by@<node>, and a flow rowworktree_preservednames the ref. The state a held call recorded (p_held) is kept too, on...-held-<call>, when it is not the snapshot. The old worktree is never written to or proven again. - A new worktree
<state>/flow/<run>/wt/<node>/a<attempt>-l<lineage>is made withgit worktree add --detachat C, the last commit of the positions that held (the node's creation base when none). When they end with a build or fix whose pending tree P was proven, P is put on top withread-tree -m -u C Pin a temporary index, so the files are P and the worktree's own index stays at C. The snapshot is checked against the boundary (the tree of C, or P) before any call runs. - A flow row
worktree_restoredrecords the node, lineage, attempt, path,C,P(or null) and the verifiedsnapshot_digest.
This worktree is the node's restore allocation: the suffix's first executor call on the node - a new thread, a fork or a
checkpoint thread - adopts it as its cwd, and its prepared row carries the same C, P and digest; the allocator never makes
another or resets a file. A process that dies after the restore leaves a call with no result, which the next resume meets as its
first failing position: it keeps the old restored worktree on a ref of its own lineage and rebuilds the same C and P in a
worktree of the new lineage, verified against the same digest.
A continuation of a thread the earlier process started never sends a turn to that thread inside a suffix, even when the thread's
last turn already is its through_turn_id: its cwd is the retired worktree, and moving it with TurnStartParams.cwd would
leave the thread's recorded cwd, its thread/list identity and the effect proofs on the old allocation. It runs on
thread/fork {threadId, lastTurnId: through_turn_id, cwd, model and config of the role, ephemeral false, excludeTurns true}, with
cwd the restored worktree for an executor and the call's own directory
(<state>/flow/<run>/calls/<call_id>/wt) for any other role. thread/rollback is not used: it changes the history only, leaves
the files and edits the original thread. The answer carries no history; the fork's turns are read with thread/turns/list
(itemsView full) to the end of every page. A fork whose Thread.cwd is not the cwd it was forked into, or whose last turn is
not the through turn (found by id or by the turn's clientId marker) and has ended, is archived and the call refused; a fork
whose configured pair is not the role's is refused for its pair (pair_refused). A fork whose answer was lost is found by reconcile through its cwd,
Thread.forkedFromId and a history that is exactly the recorded prefix. When the source thread or the turn cannot be read, the
continuation starts a new thread of the same role in the same directory, with a checkpoint packet built from the journal's
record of the turns up to the through turn (a flow row checkpoint_started has its digest and the packet itself). The first turn
of such a thread carries the packet before the call's input, also when the creation was recovered: the thread whose answer was
lost and that the next process adopts, and the creation the next process proves absent and sends again, get the packet from the
checkpoint_started row of the call's newest attempt.
Every continuation of the suffix on a thread of the earlier process forks, not only the first: the node's worktree is then the
restored one the first executor call adopted, so a later continuation's prepared row records the same path (continued, fork)
and its thread is forked into it. A call that is not an executor's and runs again takes the next private directory of its own
(calls/<call_id>/wt, then wt-1, wt-2): a path belongs to one allocation, and the directory of its earlier attempt is not
made over. The directory of an architect turn that names its head is the call's own and goes with the call's end: the terminal
row of the call (any but outcome_unknown) names it (own_directory) - the rows of the call's own run and the superseded rows a
resume writes for an attempt an earlier process left, decided by the same rule from the attempt's prepared row; reconcile writes
only outcome_unknown, which names none - the engine removes it after that row with a context the
run's cancellation does not end, and a removal that fails is a flow row own_directory_kept (call, path, error). A resume removes,
before the script runs, every directory a terminal row of an earlier process names that is still there, except the one a call is
still at work in (a call whose newest state is no terminal row or an unknown outcome).
A signal replays under the rule of the walk above: its crw.signalBind position replays when its arguments are the recorded ones. The arguments are the signal's input - its triggers
as (call, result digest) and the result digest of the signal before it - so a signal replays only when every trigger position replayed and answered the same result digest (the
script hands the bind the recorded triggers only then, and marks any other bind of a recorded signal live so that its arguments are not the recorded ones even when every trigger answers the digest it answered; crw.callFacts says whether a call was replayed) and the signal before it replayed. The first signal that does not replay
is the first position of a suffix of the path signals:<milestone>: the rows of that signal and of every later one - the calls they made and the revisions they recorded - are
ended superseded, the revisions get a revision_superseded row, and the triggers that replayed re-enter the first signal that runs, in the order of the journal, with the number after
the last signal that replayed. A lane released under such a revision waits for that signal to be decided and then keeps its replay (release_rebind), is released again, or is refused,
by the slice of its node (script.md).
A position whose last event is held never replays: neither its held CallResult nor the crw.hold record after it is reused.
When (a) to (d) and (f) hold for it, and its effect proof does (for an executor: the live snapshot of its worktree is its
p_held, compared with the live snapshot of the call's own worktree, not with the cumulative proof of the node's calls; a hold
whose snapshot could not be made, p_held_error, has no state to compare, so its files are not known to be the call's; for
host_infra_activeTurnNotSteerable also: the thread reads idle), it goes on in place: the call keeps its call id,
cwd and pair, a flow row hold_cleared (call id, lineage, reason, input state) is written, and the records after it are the suffix
and so superseded. What it sends follows input_state of the held row:
delivered(a turn carrying the packet started): the next turn<call_id>/t<k+1>of the same thread carries the marker and a fixed instruction - the previous turn stopped for the reason, continue the same task, the output schema is unchanged;not_delivered(stopped before sending,thread/startrefused, the packet turn'sturn/startrefused): the host is asked first whether the refused request made anything. For a creation,thread/listat the call's cwd: a thread with no turn is adopted, a thread with turns is an unknown outcome, and none letsthread_requestedbe written again at the same cwd with the next send ordinal (the answer proves the request finished, so no grace is waited for). For a turn, the marker<call_id>/t<k>on the thread: a turn that carries it is an unknown outcome and nothing is sent; with every turn read in full and none carrying it, the original packet is sent as<call_id>/t<k+1>.
A turn the retry sends is an ordinary turn, whatever its ordinal; the turn_requested row says so (repair: false) and a
recovered retry turn is not taken for the call's schema repair: the call keeps its one repair turn, and repair_used is true
only when it was sent. A call held again on the retry has a second held row and its node holds again. When any condition fails, the held position is an
ordinary first failing position: it is superseded, the boundary is restored, p_held is kept on its ref, and the call runs with
its full packet in the restored worktree.
crw flow resume [--state DIR] [--socket PATH] [--repo DIR] [--args JSON | --args-file FILE] RUN SCRIPT opens the run's stored
journal and the App Server host, checks that the script and the arguments are the run's (their digests are on the run's started
row; an edited script is not a resume of the run), and runs the walk above. It prints the result as crw flow run does and exits
0 when the script completed, 1 when it ended in script_error, 2 for a command line, a store or a script it cannot use. It goes on
with every call that is not settled and not in recovery hold; the calls in recovery hold stay until crw flow recover settles
them. dag-restart names the command by the same reading of a call's rows (internal/flow/callstate): crw flow recover for a
call in recovery hold, crw flow resume for every other unsettled call.