127 — Review runs as workflows over result files
Type: reference/types/adr.md · Status: accepted
Status: accepted; implemented on the operator's direction, which also ordered the old system's retirement without compatibility or data migration.
Date: 2026-11-10
Supersedes: ADR 010 (review state in SQLite), ADR 035 (queued jobs and their finalization), ADR 043 (the store's completion, outcome and baseline model), ADR 052 (the general freshness store), ADR 065 (the store's published transitions) and ADR 067 (the prompt-path worker contract). It also supersedes three earlier decisions about the same store: ADR 029 (one packing protocol for pairs), ADR 032 (freshness from SQLite snapshots under a model partition) and ADR 036 (acceptance rows as current state).
Amends: ADR 038 and ADR 041: the type spec and the COLLECTION.md stay the criterion; store identity, model partitions and acknowledgement go. ADR 051 and ADR 080: packets, pending hand-backs and the bounded closing stay; captures and the guard command become run records and recorded hashes. ADR 039: commonplace-review invokes Git for the repository root and the checkout guard.
Context
Review applies a criterion file to a note and keeps the judgment while both texts are unchanged. The store-based system did this with an agent in a conversation coordinating workers through files and a database: a selector, queued jobs, a finalizer, model partitions, baselines with acknowledgement, and a command per transition, over a 50 MB SQLite file. Most of that machinery existed because no code owned the run. The parent agent scheduled workers, so job state, transport files and all-or-nothing finalization had to make its handoffs safe.
The headless runtime (ADR 118, ADR 119) now supplies a workflow written in Python with what the store built by hand: one agent call per request, a run lock, durable launch records, replay of answered calls, deterministic checks with retry, worker problems and usage-limit stops. It does not keep state across runs: Workjig's records belong to one run and promise no format compatibility. Freshness across runs therefore still needs a durable record of what text a judgment was produced from.
Phase 0 of the design work measured the store on 2026-11-09: 9,217 baselines and 6,947 recorded jobs, the last completed 2026-09-26. 37 baselines stood on an acknowledgement (note side only); that count is a lower bound, because a later review replaces an acknowledged baseline. Outside the review and freshness packages, only status reporting and a store health check read the store.
Decision
A result is a file. Each accepted review writes
kb/reports/state/review/results/<note-path>/<criterion-id>.md. Its
frontmatter names the note and the criterion with the SHA-256 of the texts
the worker was given, the outcome (pass, warn, fail, or report for a
report criterion), the worker profile, the model the harness recorded, and
the run and launch; the body is the reviewer's text. A pair is fresh when both
hashes match the current files. Status, staleness and orphan detection are
pure functions over these files. Freshness depends on neither Git nor a
store. One result exists per pair, whichever profile produced it.
The note hash covers what the criterion watches. A criterion that
declares watches: (title, description, body) is hashed against those note
parts, normalized so a whitespace-only edit changes nothing. A criterion
without a valid declaration watches the whole note. This replaces
acknowledgement of edits outside the watched parts.
Review is workflows, under one command. commonplace-review runs
run gates (the gate sweep), run full-pass (the full improvement pass)
and run revise (the simplification passes), with status, packet,
retry and acknowledge, plus approve for operator answers. A workflow is a Python function over ask,
checked, gather and decide. Each run has one directory under
kb/reports/state/review/runs/. A sweep opens a new directory, and
--resume continues it and replays answered calls. A full pass or revision
derives its directory from the workflow, the note's path and the hash of the
note's text, so repeating the command continues the run by replay, and an
edited note opens a new run. Where a workflow needs the operator, it
proposes a file in the run and stops. Explicit approval captures the edited
or unedited file as an answer, bound to that stop, proposal and its inputs.
Repeating never approves; selection-list edits require approval again.
Changed proposal inputs invalidate the approval. Options
such as the profile and the full pass's
direction are recorded with the run.
Planning is an explicit mode, not the default for editing. A new revision
run requires --plan FILE for fixed work or --assess to choose work through
assessment. Repeats retain their mode; changed artifact text requires a new
choice. Fixed-plan execution constructs no assessment request. Editors receive
an execution instruction, not the planning procedure. Its publication context
excludes unused planner inputs. One fixed pass and one report-only criterion
are the independent evaluation baselines; assessment-led revision and full-pass
composition need separate evaluation.
Approval protects unsettled selection and final publication, not routine planning.
Within the explicitly chosen mode, ordinary intermediate work is preauthorized.
Revision retains the generated plan for inspection and executes it after
validation. --plan is an explicit validated input or override, including
replacement of a stopped plan; neither route requires plan approval.
Editing the retained plan alone is not an override.
Plain-wording and abstractions retain explicit approval per selection proposal as temporary procedure-development points. Retain proposed, accepted and rejected choices with reasons, revise selection rules and checks, and evaluate them on retained and fresh examples before separately authorizing removal. This calls for procedure development, not new automatic learning infrastructure. Fewer proposals alone do not establish reliability.
For changed final text, both workflows generate artifact/final.md,
artifact/final.diff and artifact/publication.json. The operator inspects
the text and diff and approves the exact generated manifest through
commonplace-review approve RUN FILE --key KEY. An edited manifest cannot
authorize publication. The candidate and manifest are outputs, not revision
inputs: use --plan for revision, change workflow inputs or start a new run.
Instructions own judgment; prompts name inputs only.
Workers never write the checkout. A worker receives captured inputs and writes only its output files. Code checks each output (the strict result line, a decision schema, the bounds an edit must keep). A workflow that edits a note carries each edited text forward in memory and in the run directory, and writes the note once, at its last step, only after exact final approval and with the original note and captured brief unchanged. A change to either during the run is a conflict, and nothing is written. A checkout guard reports paths that changed while a run was open.
A criterion is any markdown file. Catalog gates, a type spec, a
COLLECTION.md and the critique instruction all enter as the criterion
input. The workflow names the output contract, verdict or report.
The full pass keeps its packet. It asks the gates over each text it
reviews and writes result files once, at its end, for the text the note then
holds. When the final text is edited, the pass stops before publishing the
candidate packet, results or note until exact final approval. No-edit
dispositions may publish their report, packet and results automatically.
The packet is markdown whose frontmatter
code writes: the note, the start and final text hashes, the decision, the
closing status and the resolution fields. Its start text lives in the run;
commonplace-review packet compares the recorded hashes with the files.
Considered alternatives
One long-lived run as the store. Workjig keeps every call of a run in one record file; thousands of pairs would make that file the database this decision removes. Set aside for one run per invocation, with result files carrying freshness across runs.
Migrating the baselines. The operator judged on 2026-10-24 that the stored reviews were outdated. The new system started empty; the first sweeps produce its results. The store and its job directories are abandoned local data.
Implicit approval on repeat. Rejected: retrying after interruption must not make an operator decision. A separate approval command captures the answer; a stale proposal key is refused. The command does not authenticate the human: operating instructions permit an agent to approve only on the operator's stated word for that proposal.
Routine plan approval. Rejected: assessment, planning and settled
intermediate work are already authorized. Retaining the plan for inspection
and accepting a validated --plan override preserves operator control
without a routine stop. This does not authorize unsettled selections or
publication of changed final text.
Editable final approval payload. Rejected: approving a replacement manifest would detach consent from the checked candidate. Final approval must match the generated manifest exactly; revisions enter through workflow inputs instead.
Keeping acknowledgement. Dropped. It covered 37 of 9,217 baselines, and the watched-parts hash absorbs its commonest case. A changed pair is re-reviewed. An acknowledgement command returns only when sweeps show the re-review cost.
Keeping model partitions. Dropped. The profile and the recorded model are
provenance, not identity, and status shows them. What would reopen it: an
operator who wants two models' verdicts on one pair kept side by side.
One call per pair or per bundle. Both shipped. pair asks one worker per
stale pair, so a criterion edit re-asks exactly its pairs; bundle asks one
worker per note and bundle of catalog gates for that bundle's stale gates,
and each gate still gets its own result. A launch's fixed context dominates
its cost: one live pair cost $0.095, nearly all of it fixed context, and one
bundle launch reviewed nine prose pairs on one short note for $0.172 in
31 s. Each figure is a single observation. The sweep defaults to pair
until a comparison shows whether grouping changes verdict quality; the full
pass uses bundle.
Decision models for bounded gates. Deferred. A judgment-specialized model could serve closed-ended gates, but its cost is calibration per gate, under the gate calibration proposal. The result file names an executor rather than a launch, so such a result fits it.
A directory type for a full-pass run. Deferred until a run shows what validating a run directory as one unit would catch that the packet type and the result-file parser do not.
Consequences
The operativity path: the commonplace-review command and its library
modules enforce result files, freshness, the checkout bounds and packets in
code. commonplace-status --review reads the result files. The review-system
guide, the full-pass and disposition instructions and the fix procedure bind
agents operating review. kb/instructions/review-profiles.yaml sets what a
worker may do. A review gate's watches: now decides which edits stale its
pairs, so gate authors set staleness.
Easier: adding a workflow is one Python function plus its criterion files. A stopped run continues where it stopped without repeating a paid answer. Freshness is inspectable as files. The full pass and the revision apply an edit only through code that checks it, and change the note at most once per run. A hand-back or an unfinished run leaves the note untouched, so nothing needs restoring.
Harder: every changed pair is re-reviewed, and a type-spec or COLLECTION.md
edit re-reviews its whole cohort. A wrong watches: declaration leaves pairs
falsely fresh. A note move orphans its results; they are deleted and the
pairs reviewed again. Results stay machine-local and untracked.
commonplace-review needs a Git working tree: it finds the repository root
with Git and reads git status for the checkout guard, against ADR 039's
rule that commands never invoke Git. Freshness itself does not depend on
Git.
Dropped without replacement: acknowledgement, model partitions, link-consumption and availability telemetry, the reviewer's self-reported model, relocation's rekeying of review state and the generic freshness store for non-review targets.
Open:
- the default grouping, pending a quality comparison of pair and bundle verdicts;
- the checkout guard cannot tell a worker's change from another session's, so concurrent work in the checkout makes a run exit nonzero with paths to inspect;
- the Claude Code profiles' write denial outside the run directory has not been seen to refuse a write: the guard is the only verified control;
- the
codexprofile cannot be sandboxed read-only, so it relies on the prompt and the guard.
Limits: decided and run in this checkout, with the sonnet profile for the
live runs. One full pass on a short note took 27 launches and $4.47 and ended
in a hand-back; that is one observation, not an
estimate of pass cost or outcome. The opus and codex profiles and the
revision workflow have no live measurement here. The prompt around the inputs
is outside the freshness hashes, so a prompt change re-asks calls on a
resume or repeat but stales no result; judgment belongs in criterion files.
Relevant Notes:
- Review system — implemented-by: the operating page of
commonplace-review - Review architecture — implemented-by: result files, freshness, workflows and the checkout guard as built
- The practical scheduler is the host language — rests-on: control flow stays in Python; the runtime journals results
- The boundary of automation is the boundary of verification — rests-on: code applies verdicts and writes the checkout; workers only produce outputs