Review architecture
Type: types/note.md
Review is a set of Python workflows over the headless runtime, behind one
command, commonplace-review. Code owns the control flow, every check that
can be checked and every write to the checkout. Workers only judge and write
their output files. The only durable review state is files: one result per
(note, criterion) pair and one current packet per note that had a full pass.
For operation, see the review-system guide.
ADR 127 records
the decision. Exact behavior belongs to commonplace.lib.review_workflows,
commonplace.headless and commonplace.cli.review_workflows.
Components
| Part | Role |
|---|---|
headless.jobs |
ask(), one agent call with named inputs and outputs; checked(), retry with a deterministic check; gather(), parallel calls; Run, a run directory with a lock, durable launch records and replay; dismiss() and acknowledge_stopped() |
headless.decisions |
decide(), an ask whose markdown frontmatter or YAML output is checked against a closed schema before code branches on it |
headless.operator |
answer(), a proposal that waits for an explicitly captured answer; approve(), capture under the run lock bound to the proposal and its inputs; waiting(), the file, proposal key, approval command and repeat command |
headless.harness |
worker profiles as Workjig command workers for Claude Code, codex and pi, and the model each harness recorded |
review_workflows.criteria |
criterion requests (gate ids, bundles, type, collection, critique) resolved into applicable pairs; reads only |
review_workflows.outcomes |
the strict result-line parser |
review_workflows.results |
result files, criterion ids, the watched-parts note hash, and freshness, status, summary and orphans as pure functions over the files |
review_workflows.gates |
ask(), the gate calls for given texts returning unwritten results; the sweep, which skips fresh pairs and writes each result as it arrives; call grouping, the worker prompt and the checkout guard |
review_workflows.edits |
the bounds an edited text must keep, the single write of the final text, the run directory derived from the inputs and the guard minus a workflow's own write |
review_workflows.full_pass |
the full improvement pass and its packet |
review_workflows.revise |
the revision workflow over the simplification passes |
Workjig launches each worker in the checkout through the harness's headless mode (ADR 119). The worker loads the project's normal configuration and the profile's flags.
Ownership
A workflow is ordinary Python that branches only on what code parsed: a
verdict line, a decision schema, an edit's bounds. A worker receives its
inputs as files Workjig writes for each launch, so it judges captured text,
not the live file, and the result hashes exactly that text. A worker writes
only its declared outputs, or a problem file when it cannot do the job. Code
turns an accepted output into a result file, a packet or a write to the
checkout. Asking and publishing are separate: gates.ask() returns each
accepted review as a result without writing it. The sweep writes each result
as its answer arrives. The full pass writes its results once, at its end,
for the text the note then holds, and nothing after a stop or a conflict.
An edit reaches the checkout only through code. Each edited text is checked
first; frontmatter title, type and brief and the H1 must be
kept, user-verified removed after a body change, and no validation failure
added. An accepted text stays in memory and in the run's artifact/
directory, and the next step works on it. The workflow's last step writes the
final text once, atomically, only over the start text and after approval of
the exact generated publication manifest. Any other text
is a change made outside the run: the run writes nothing and reports a
conflict that names the file holding the proposed text.
A full pass or revision reuses answers by each call's inputs. Its run directory is
derived from the workflow, the note's path and the hash of the note's start
text, so repeating the command continues the run by replay, and an edited
note opens a new run. The directory is not a complete input identity: changed
instructions, profiles, write briefs or approved answers can produce new calls inside it.
A new revision run explicitly chooses --plan for fixed work or --assess
for planning. Repeats retain that mode; changed artifact text requires a new
choice. Within the selected mode, ordinary intermediate work is preauthorized.
Revision retains its generated plan for inspection and runs it after
validation; --plan also supplies a validated override of a stopped plan,
without approval. Editing the retained plan alone is not an override.
Fixed-plan execution constructs no assessment request. Editors in both modes
receive the execution instruction and the assigned pass, not the planning
procedure. Fixed-plan publication binds those inputs, the supplied plan and
the profiles used by its passes; assessment-only instructions, unselected
passes and unused effort profiles do not invalidate it. Assessment-led runs
also bind the assessment request, answer and full pass catalog. A single
criterion through run gates is report-only and never selects subsequent work.
Unsettled plain-wording and abstraction selections require explicit approval per proposal. These temporary procedure-development points retain proposed, accepted and rejected choices and reasons. Removal needs separate authorization after selection rules and checks have been revised and evaluated on retained and fresh examples. This is not automatic learning infrastructure; a lower proposal count does not establish reliability.
Changed final text in both workflows requires approval through
commonplace-review approve RUN FILE --key KEY of the exact generated
artifact/publication.json. artifact/final.md and artifact/final.diff
provide inspection views. Repeating never approves. The manifest and candidate
are generated outputs, not revision inputs: use --plan for revision, change
workflow inputs or start a new run. An edited manifest cannot authorize
publication. Full-pass also withholds candidate packets and results at this
boundary; no-edit dispositions may publish reports, packets and results
automatically. Judgment stays in instructions; prompts name inputs only.
A declared write brief is captured for full-pass planning and editing and for revision assessment and every pass. Its bytes participate in those calls' identities and in final publication approval. A worker reports a brief conflict as a problem, stopping the run. Code prevents changing the declaration and checks that the captured brief is still current before publication; it cannot verify semantic obedience. An amendment to the commission must happen outside the run. Method and closing judges do not receive this input, and brief changes do not stale gate results.
Durable state and run state
| State | Location | Lifetime |
|---|---|---|
| result files | kb/reports/state/review/results/<note-path>/<criterion-id>.md |
until replaced by a later review of the pair or deleted as orphaned |
| current full-pass packet | kb/reports/state/review/packets/<note-path> |
until a later pass replaces it; holds the resolution of a pending hand-back |
| run directory | kb/reports/state/review/runs/<stamp>/ for a sweep; runs/<workflow>/<note path without .md>/<start-hash prefix>/ for a full pass or revision |
a sweep and its resumes; every repeat of a workflow command on the same note text |
A run directory holds Workjig's records under calls/, the profile and
options it opened with in sweep.json, operator proposals and approved answers
under operator/, and accepted per-run artifacts under artifact/
(the start text, method reports, each accepted edited text and the packet copy). Its
records exist for replay inside the run and promise no format stability
across versions. Freshness across runs never reads them.
Result files are written atomically. One run holds a lock on its directory; two runs over the same pair both write, and the later write stands.
Freshness hash boundary
A result pins exactly two inputs: the note and the criterion. The criterion
hash covers the criterion file's whole text. The note hash covers the parts
the criterion declares in watches: (title, description, body) under the
criterion's current text, or the whole note without a valid declaration.
Parts are normalized: the title is the first # heading, and the body drops
blank lines and trailing spaces, so a whitespace-only edit changes nothing.
A criterion edit that changes watches: may therefore report both inputs
changed.
Everything else is outside the hashes: the prompt around the inputs, the
type and collection wrappers, the output contract and the reading scope.
Judgment-bearing text therefore belongs in criterion files, where the hash
sees it (ADRs 038
and 041
apply this to their wrappers). A prompt change is part of each call's
identity, so a resumed run asks those calls again, but it stales no result. A
prompt change that shifts judgments needs a deliberate --force sweep.
Linked files a worker reads, including the one path a criterion may derive from a link (ADR 073), are reading context, not freshness inputs.
Criterion identity
A criterion is named by its artifact identity, and its text is read through
the library resolution. In this checkout the library is kb/, so every
criterion is a repository path. In an installed project, the gate catalog,
global types and the critique instruction are commonplace:<path>
identities, while project type specs and COLLECTION.md contracts are
repository paths (ADR 086).
The criterion id that keys a result file follows from the identity, so both
layouts store results the same way.
Checkout guard
gates.checkout_state() records HEAD and, for each path git status lists
(including untracked files) outside kb/reports/state/review/, its status
code and content digest. A run compares the record before and after; a
workflow that writes the checkout drops its note when the note holds exactly
the final text the run applied. The guard can say that a path changed while the run was
open. It cannot attribute the change, and it misses ignored files and changes
undone before the run ended. It reverts nothing.
Maintenance scope
Review this page when the ownership split between code and workers, the
durable state, the freshness inputs or the guard's claim changes. New
workflows, prompt wording, profile flags and command arguments do not by
themselves require an edit; source and --help own them.
See also
- ADR 118 — decided-by: the headless workflow runtime review shares with the analysis workflow
- Storage architecture — part-of: where review state sits among Commonplace's storage roles
- Full improvement pass closure — see-also: why the full pass reviews its final text once more