Verification & Quality¶
This page covers the quality-assurance pipeline attached to agent output: the verification stage that runs after an agent completes a task, the harness middleware that wraps every agent invocation, the review pipeline that validates produced artifacts, and the intake engine that ingests new work.
Scope
The Build Loop is authoritative on where verification sits in the loop and on what a reviewer is shown. This page owns the review gate's internals: reviewer selection, session narrowing, the verdict and finding models, and the build/test oracle.
Two things here are superseded by that page. A reviewer is shown the diff against the trunk commit its unit branched from, not a deliverable string, and its findings are anchored to a path and line range within that diff. A reviewer never runs the build or the tests: the system runs every deterministic check on the commit and the reviewer reads the cached, attributable result.
What these gates establish, and what they do not¶
Every gate on this page answers one narrow question: was the work checked by something other than the agent that produced it, and did the check leave a record. That property is enforced structurally rather than by convention, at three independent layers described in Review Gate Invariants.
It does not establish that the work is correct, and nothing on this page should
be read as saying so. An independent reviewer is a filter, not an authority: it
catches some defects, misses others, and its verdict is one agent's judgement of another
agent's work. The two agents are distinct identities with their own bound
(provider, model) pairs, but nothing binds them to different model
families, so a blind spot shared by both is a blind spot the pair cannot
see. Where the machinery cannot decide, it hands the question to a person
rather than resolving it: an ESCALATE verdict parks the task at BLOCKED, and
an unstaffed gate role parks rather than passing. Review narrows what can be
recorded as done; it does not establish that what completed is right.
Verification Stage¶
Verification is a first-class stage in the workflow engine, and it runs as a separate evaluator with its own context rather than as a self-evaluation inside the generator step: a generator grading its own output reuses the reasoning that produced it, so the blind spot that caused a defect is the same one that hides it.
Workflow Node and Edge Types¶
WorkflowNodeType.VERIFICATION is a control-flow node like CONDITIONAL. Three dedicated edge types route verification outcomes:
VERIFICATION_PASS: artifact acceptedVERIFICATION_FAIL: artifact rejected, routed to regenerationVERIFICATION_REFER: confidence below threshold, escalated to human review
Blueprint validation enforces exactly one of each edge type per verification node.
Calibrated Rubric Grading¶
Each verification node references a VerificationRubric by name. A rubric contains:
- Criteria (
RubricCriterion): weighted dimensions withbinary,ternary, orscoregrade types - Calibration examples: few-shot demonstrations for LLM graders
- Minimum confidence: below this threshold, the verdict is overridden to
REFER
Built-in rubrics: frontend-design (four criteria: design/originality/craft/functionality) and default-task (correctness/completeness/probe-adherence).
Atomic Criteria Decomposition¶
Acceptance criteria are decomposed into atomic binary probes (AtomicProbe) via a pluggable CriteriaDecomposer protocol. simulations.verification_decomposer selects the variant and defaults to identity: IdentityCriteriaDecomposer maps each criterion to one probe with no model call. LLMCriteriaDecomposer runs on the explicit (provider, model) pair in simulations.verification_decomposer_model, and with that pair unset it degrades to the identity decomposer rather than probing on a connection nobody chose.
Structured Handoff Artifacts¶
HandoffArtifact carries the payload, artifact references, probes, and optional rubric between stages. A model validator rejects self-handoff (from_agent_id == to_agent_id). Immutability is enforced by the frozen Pydantic model (frozen=True).
Self-Evaluation Rejection¶
Self-evaluation, where the generator also judges its own output, is rejected.
VerificationResult.evaluator_agent_idMUST differ from the generator agent id, enforced by a model validator at construction. The invariant is about who judged; it says nothing about whether the judgement was right.
Pluggable Grading¶
The RubricGrader protocol follows the standard protocol + strategy + factory + config discriminator pattern (mirroring engine/classification/). Variants: LLM (production) and HEURISTIC (testing/fallback). Configuration via VerificationConfig.
Rubric Grading on the Review Pipeline¶
The decomposer + grader factories are wired onto the live post-completion path as a VerificationReviewStage (engine/review/stages/verification.py), which runs first in the review pipeline. It decomposes a task's acceptance criteria into probes, grades the work against a rubric with a separate evaluator identity, and maps the verdict onto the pipeline: PASS/REFER let the task proceed (REFER is surfaced in stage metadata for human review, never a hard fail), FAIL bounces the task to IN_PROGRESS for rework. A grader fault fails OPEN (the stage SKIPs) so a verifier defect never blocks completion. The deterministic default (identity decomposer + heuristic grader) grades the proportion of acceptance criteria marked met, so the stage works without a provider; simulations.verification_grader / verification_decomposer switch to the LLM variants and simulations.verification_review_enabled gates the stage (on by default, baked in at startup).
Harness Middleware Layer¶
The engine uses a composable middleware layer for cross-cutting concerns that span agent execution and multi-agent coordination. Two separate protocols serve two distinct pipelines.
Agent Middleware¶
Protocol: AgentMiddleware (engine/middleware/protocol.py). Six async hooks in declared order:
| Hook | Runs | Purpose |
|---|---|---|
before_agent |
Once on invocation | Load memory, validate input, record hashes |
before_model |
Before each model call | Trim history, redact PII, inject context |
wrap_model_call |
Around model call | Caching, dynamic tools, model swap |
wrap_tool_call |
Around tool execution | Inject context, gate tools |
after_model |
After model responds | Human-in-loop, assumption-violation checks |
after_agent |
Once on completion | Save results, notify, cleanup |
Composition: before_* left-to-right, after_* right-to-left, wrap_* onion-style (each wraps the next). Exceptions propagate to the classification pipeline.
The chain is wired into the engine at boot (gated by engine.enable_agent_middleware, on by default): its before_agent / after_agent hooks fire at the AgentEngine execution boundary (engine/_agent_middleware_run.py). The live effect today is authority-deference defence: when AuthorityDeferenceGuard.before_agent detects authority cues in the conversation, the engine injects its justification header as a system message. The per-call slots (security_interceptor, approval_gate, cost_recording, classification) remain ordering placeholders whose real logic stays inline (ToolInvoker, the execution loop, _post_execution_pipeline) until the chain is also wired into the per-turn model / tool call sites.
Default chain: checkpoint_resume, delegation_chain_hash, authority_deference, sanitize_message, security_interceptor, policy_gate, approval_gate, assumption_violation, classification, cost_recording.
Optional middleware (registered in _AGENT_OPT_IN, must be enabled explicitly):
SemanticDriftDetector(after_modelslot): compares model output against task acceptance criteria using cosine similarity. Opt-in viaCompanyConfig.security.semantic_drift_enabled. Fail-soft: logs warnings but never blocks.
Coordination Middleware¶
Protocol: CoordinationMiddleware (engine/middleware/coordination_protocol.py). Five async hooks:
| Hook | Pipeline Position | Purpose |
|---|---|---|
before_decompose |
Before Phase 1 | Clarification gate |
after_decompose |
After Phase 1 | Post-decomposition analysis |
before_dispatch |
Before Phase 3-5 | Plan review gate, task ledger |
after_rollup |
After Phase 6 | Extension point; no default occupant |
before_update_parent |
Before Phase 7 | Authority deference scan |
Default chain: clarification_gate, task_ledger, plan_review_gate, authority_deference_coordination.
S1 Constraint Hooks¶
| Middleware | Hook | Behaviour |
|---|---|---|
AuthorityDeferenceGuard |
before_agent |
Detects authority cues in transcripts, logs patterns, injects justification header |
AssumptionViolationMiddleware |
after_model |
Detects broken assumptions, emits escalation events |
ClarificationGateMiddleware |
before_decompose |
Validates acceptance criteria specificity |
DelegationChainHashMiddleware |
before_agent |
Records SHA-256 content hash for delegation drift detection |
Configuration¶
Per-company: CompanyConfig.middleware (MiddlewareConfig) with agent and coordination sub-configs.
Per-task: Task.middleware_override replaces the company-level chain when set.
Error Semantics¶
Middleware exceptions propagate to the classification pipeline. ClassificationResult.action decides: retry, escalate, or fail. No silent swallowing.
Review Pipeline¶
The review pipeline provides a configurable chain of review stages for tasks
in IN_REVIEW status. See the Client Simulation design
page for the full architecture, including ReviewStage protocol, pipeline
execution semantics, and metadata tracking.
Key design decisions:
- No new TaskStatus values for pipeline tracking; tasks stay
IN_REVIEWthroughout, with progress tracked in task metadata. - Short-circuit on FAIL: first failing stage sends the task back to
IN_PROGRESSfor rework with the stage name and reason in metadata. - Default fallback: when no pipeline is configured, the existing
ReviewGateServicesingle-stage behaviour runs. - Automatic vs human-gated:
engine.auto_review_on_completion(default on, hot-reloadable) controls who acts on a task reachingIN_REVIEW. On, the staged pipeline runs automatically and applies its verdict so a verified task self-completes without a human; off, the review is opened and decided by a human. It is on by default so the review pipeline (the completion oracle included) runs automatically rather than parking every task inIN_REVIEWfor a human. The setting only decides whether the pipeline runs automatically: the oracle gate (see below) enforces on both paths, since a human approval still invokes the same gate throughcomplete_review.
Beyond the review pipeline, the lifecycle exposes additional human gates that all
route through the same signal_resume_intent approvals-resume path. The
plan-approval gate (ApprovalSource.PLAN_REVIEW,
coordination.plan_approval_required, off by default) persists a decomposed team plan
as a durable, versioned, human-editable Plan entity and parks an approval referencing
it before any team builds, so an operator can review, rework, or send the plan back for
changes through the /plans API and Plan Review workspace before approving (see
Plan Review). The other two are on by default, because an agent
that cannot proceed without a human's answer should ask rather than guess: the
mid-task clarification pause (AWAITING_INPUT, engine.clarification_enabled) lets
an agent ask a human an open-ended question, and the project-decision gate
(engine.scoping_enabled) puts a mid-build implementation fork to a human, who picks
structurally from the agent-supplied options (each with a trade-off write-up, one
recommended) and records the choice as a project-brain DECISION entry. Both carry a
declared reversibility and are answerable in the unified conversation as well as in the
approvals queue (see The Org Asks).
Intake Engine¶
The intake engine processes ClientRequest submissions through an independent
state machine (RequestStatus) before creating tasks in the task engine. The
synthetic-client work-entry path (POST /requests/{id}/approve, a benchmark
door gated off by default behind simulations.client_intake_enabled) approves
a request and runs it through the IntakeEntryAdapter into the work pipeline
spine so an agent executes it; the terminal state lands asynchronously. See
Client Simulation for the full request lifecycle,
intake strategy contracts, and the gated work-entry path.
Vision Verifier Gate¶
The vision verifier is the UI cousin of the adversarial red-team gate: where the
red-team gate attacks a text deliverable, the vision gate judges whether a running
GUI deliverable matches its brief. It is opt-in
(CompanyConfig.security.vision_verify.enabled, off by default) and fires after the
red-team gate, before the IN_REVIEW -> COMPLETED transition.
A pluggable VisionVerifier (security/visionverify/) follows the standard
protocol + strategy + factory + config discriminator pattern:
noop(default): inert; returns a clean report.heuristic: deterministic, no LLM. Checks structuredVisualExpectationentries (e.g. dominant colour) against the captured screenshots. Used by the acceptance test so a brief-mismatch BLOCK is reproducible.llm_vision: sends the screenshots (as multimodalimage_parts) plus the fenced brief to a vision-capable model and parses a structured verdict from a tool call. Gated onModelCapabilities.supports_vision.
The VisionVerifierGate maps the report's findings to a verdict
(PASS / PASS_WITH_FINDINGS / BLOCK) via the same severity x autonomy routing
matrix as the red-team gate. Self-evaluation is rejected (the verifier identity
must differ from the deliverable's generator). A verifier fault fails OPEN (a
synthetic INFO finding) so a fault never blocks completion. SEC-1: the untrusted
brief / criteria are wrapped with wrap_untrusted before reaching the model;
screenshot bytes travel as structured image_parts, not as prompt text, and are
elided from the cassette's human-readable copy.
Completion Oracle Gate¶
The completion oracle sets what a task must show before it may be recorded as
done: for code work, a recorded test run that actually happened and passed,
plus a sign-off from an agent that is not the one that did the work. The
alternative it replaces is "the run produced some artifacts", which is
satisfied by an agent that wrote files and said it was finished. Both halves
are mechanisms with known limits: the first proves a test command ran and
exited zero, not that the tests are adequate; the second records that a
second agent read the deliverable and formed a view, not that the view is
correct. It is on by default (opt-out via
engine.completion_oracle_enabled) and is two composed gates that run first in
the completion chain, before the red-team and vision gates, on every path to
COMPLETED (both the auto-review run_pipeline and the human-driven
complete_review), because they live on ReviewGateService, not on the
auto-review trigger. Its natural home is the autonomous flow: with
engine.auto_review_on_completion on by default a verified task self-completes
and the oracle gates that completion; a human opening a review is gated by the
same two gates. All the oracle settings (completion_oracle_enabled,
_shadow_mode, _min_stakes) are hot-reloadable: an edit rebuilds the runtime
and re-attaches the gates to the persistent review service on the next task, no
restart. Who reviews is not among them, because it is not a setting: the gate
selects a roster holder per review, so a role assignment takes effect on the
next task without any reload at all.
Layer 1: execution-grounded build/test gate¶
A deterministic gate (engine/completion_oracle/evaluator.py
BuildTestOracle) that is a pure function of a task's grounding
classification and its already-persisted CodeExecutionRecords (the
purpose="tests" rows), so it needs no new persistence.
Those rows are written from what the agent ran, not from what it
declared. A shared classifier (tools/_test_run_capture.py::is_test_run)
recognises a test command by its shape (pytest, go test, cargo test,
npm test, gradle ... test, and the rest), and both code_runner and
shell_command record through it. Letting the model's own purpose
argument arm the gate meant an agent that ran its suite through
shell_command, or through code_runner without setting the flag,
produced a green suite and zero evidence: the oracle then correctly failed
closed and blocked a build that genuinely passed. The gate's verdict is not
supposed to depend on which tool the model happened to pick, and
model-supplied input is the wrong thing to let decide whether a gate has
evidence at all.
The command is untrusted too, so recognition reads the invoked program and
its verb rather than searching the line for a word. A compound command is
refused outright (pytest || true exits 0 whatever the suite did), and a
package manager's test counts only as its own subcommand or the script
run names: test is a real npm package, so npm install test succeeds
and would otherwise mint passing evidence for a command that ran no tests.
Build tools (mvn, gradle, make) keep positional target matching,
because their arguments are phase names rather than verbs.
classify_grounding_requirement marks a task REQUIRED when it declares (or
produced) a CODE / TESTS artifact; a docs / plan / decision task is
NOT_APPLICABLE and the oracle abstains. The verdict uses NEWEST-run semantics
(the newest test run decides), so a task that failed, was reworked, and now
passes is VERIFIED rather than blocked forever. A REQUIRED task whose newest
test run failed (BUILD_TEST_FAILED) or that has no passing test evidence
(UNVERIFIED, the stub the oracle exists to catch) is routed back to
IN_PROGRESS. This gate fails CLOSED: absent, failing, or unreadable test
evidence for a code task blocks; only the structural absence of the record
store (a persistence-less boot, CHECKER_UNAVAILABLE) passes through.
The build/test verdict is also the source of truth for a run's RunOutcome:
derive_run_outcome takes an oracle_blocked flag so the approvals read
surface shows a code task that does not build as FAILED even when it produced
artifacts, mirroring how EMPTY is resolved at read time.
Layer 2: agent-session peer reviewer¶
The independent reviewer is a real agent session (AgentEngine.run on a
transient REVIEW task carrying the reviewed task's own stakes and complexity),
not a single complete_* call, mirroring the red-team gate's shape. It is an
ordinary roster agent holding the built-in Completion Reviewer role,
selected per review (see Selecting the reviewer).
The reviewer reads the deliverable, may build it and run its tests, and files
exactly one verdict (APPROVE / APPROVE_WITH_NOTES / REJECT / ESCALATE) via the
single terminal tool submit_completion_oracle_verdict, guarded by a
trusted-runtime-context contextvar so the reviewer cannot be spoofed into
filing under a different execution and cannot spoof who reviewed whom (the
identities are seeded by the gate, not taken from the tool arguments). The
untrusted deliverable / criteria are wrapped with wrap_untrusted at the
prompt boundary (SEC-1).
Selecting the reviewer¶
The reviewer is a roster agent holding the Completion Reviewer role, chosen
per review. It is never an identity built at boot from the catalogued role:
such a thing is registered nowhere, staffed by nobody, and absent from
GET /agents/active, so "peer review" would be performed by something that is
not a peer, holding a role no operator could grant, and producing verdicts
comparable with nothing.
scripts/check_no_synthetic_agent_identity.py is what keeps it out.
A gate role is staffed, and it is still not an executor¶
Holding a gate role confers judging authority, and being staffed is what makes that authority real. The two facts pull in opposite directions everywhere a roster is read: the reviewers ARE on the roster, so any rule that lists staffed roles without asking what a role CONFERS hands the planner a judge to assign work to.
The exposure is concrete. DecompositionContext.available_roles becomes the
enum of the required_role field, which every subtask must carry, so a roster
listing that includes a gate role offers the planner a judge to assign work to,
and a planner offered one takes it. The no-self-review invariant does not cover
this: it is a CHECK on a verdict ROW, and an assignment happens a layer
earlier, while the plan is being written. What breaks is not only the
assignment: the party that judges becomes the author of what it judges.
engine/decomposition/context.py::roster_from_agents excludes gate roles from
what a planner is OFFERED, and it is the only place that can: _role_field and
_roster_guidance are pure functions over the roster they are handed, so both
inherit the answer rather than re-deciding it.
describe_unroutable_role is the exception, and deliberately: it calls
role_is_gate_role directly, BEFORE it looks at available_roles at all.
Inheriting there would leave the rule open in the state it most needs to hold.
An org whose active agents are all judges derives an EMPTY roster, and an empty
roster means "no roster known" and passes every declared owner, so the filter
alone would wave through the very role it removed. Asking the question first
also covers the paths no derivation reaches: an operator editing a plan item's
owner by hand supplies the role directly.
scripts/check_gate_roles_not_assignable.py holds the tree to one derivation of
that roster, across evals/ as well as src/synthorg/. The harness is in scope
because a second derivation there is worse than a mis-assigned item: a benchmark
arm meant to run without plan-level verification that quietly staffs a judge as
a builder contaminates the contrast it exists to measure, and the contamination
is invisible from inside the product.
Selection lives in hr/role_staffing.py, shared with the red-team gate so the
two cannot drift, and the rule is declared and logged on every call:
- Candidates are ACTIVE holders of the role, minus the executor. The exclusion here is a convenience; the invariant stays structural (below).
- Reach: holders who already worked the reviewed initiative are preferred,
read from the tasks that left the queue on it
(
engine/initiative/contributors.py::initiative_contributors, which drops the statuses proving no execution happened, so an assignment nobody has started confers no preference) rather than from anything stored on the project. When the reviewed work names an initiative with contributors and none of them qualify, the search widens org-wide and logshr.staffing.widenedwith the project and the reason, so a widening away from a set that existed is never silent. Work on no project, or on one nobody has worked yet, has no narrower set to widen from, so there is nothing to report. A momentarily unreadable task store costs only this preference, never the selection. The two roles are declared gate roles (core/role_catalog.py::role_is_gate_role): quality assurance judges work across the org rather than being confined to the initiative it happens to have contributed to. It is a property of the role an operator can see and grant, never a flag on the identity. - Capability fit against what the reviewed TASK needs (its stakes and
complexity, judged by the single org-wide
CapabilityPolicy, so the bar is the same one selection applied to the work in the first place and an operator's tuned floors reach it too): an exact rung first, failing that the nearest HIGHER rung, failing that the nearest LOWER rung, logged ashr.staffing.under_capabilitynaming both rungs. The agent's rung comes from the model catalogue rather than the roster's cached claim, so a re-graded model is judged as it is now. A pair nothing grades counts below every rung, so it never silently outranks a graded one. Ties break on the agent id, so the choice is reproducible.
Capability decides WHO reviews and never what model anybody runs. The selected
agent's own bound (provider, model) pair IS the dispatch target, and nothing
on this path rewrites it. There is no reviewer-model setting: the roster
already names the pair, and a second setting deciding "which model reviews"
would be a second owner for a decision that has one.
The reviewing session is narrowed¶
The gate dispatches under a narrowed COPY of the selected agent
(engine/review_session.py::as_review_session), not the agent as the roster
holds it. The reason is that the content a judge reads is attacker-controlled
in the way any deliverable is: an injection planted in the work under review
executes inside the reviewing session, and what it can reach is decided
entirely by the identity the gate dispatched. A roster agent carries whatever
its day job needs, which can be ELEVATED tool access, wildcard MCP
capabilities and FULL autonomy. Judging needs none of that.
The copy holds:
ToolAccessLevel.STANDARD, which covers reading the deliverable. What it also grants is withdrawn below: a judge that writes or runs inside the tree under review is authoring what it judges, and a recorded corpus put 36 file-writing shell calls in sessions whose only job was to file a verdict.- No MCP capabilities (
mcp_capabilities=()). The internal MCP surface is how an agent reaches the rest of the org, and judging one deliverable needs no part of it. REVIEW_DENIED_CATEGORIES(ToolCategory.EXTERNAL_DATA,ToolCategory.TERMINAL,ToolCategory.CODE_EXECUTION): every governed connection tool (forge, chat, deploy, publish) plus the external-API and research tools, and every way of running a command. Withheld by CATEGORY rather than by name, because a name list re-opens the hole the day a tool joins the category.REVIEW_DENIED_TOOLS(write_file,edit_file,delete_file,git_commit,git_branch): the mutating members of the two categories the reviewer keeps,FILE_SYSTEMfor opening the artefact andVERSION_CONTROLfor reading its history.git_branchis there becauseGitAccess.LOCAL_ONLYwithholds only what reaches a remote, and creating, switching or deleting a branch rewrites the checkout under review without leaving the machine. Held by name only because withholding the category would takeread_fileandgit_diffwith it.REVIEW_SUB_CONSTRAINTS(TerminalAccess.NONE,GitAccess.LOCAL_ONLY), holding the same line at the sub-constraint enforcer that runs after category gating.AutonomyLevel.SUPERVISED, so anything the session attempts beyond reading meets the ordinary approval gate rather than an autonomy grant written for the agent's other work.
The build and test evidence a verdict rests on is therefore not something
the reviewer produces. The completion gates run the project's declared
commands before the review opens and record each run
(CodeExecutionRecord); the stage reads the reviewed execution's runs
(OracleStageConfig.records) and hands them to the session in a fenced
<verification-runs> block, newest first, with each command's exit status
and output tail. The verdict names what it cited (build_evidence_cited,
test_evidence_cited, test_command), and the prompt makes an absent or
failing test run grounds for reject, never for approve. The output tails are
fenced with the deliverable because the code under review printed them.
- The verdict tool by name.
submit_completion_oracle_verdictisToolCategory.OTHER, which only ELEVATED admits, so the one thing a judging session exists to do is allowed explicitly. Raising the level instead would hand the reviewer every other category, which is what the narrowing exists to prevent.
Identity, role, department and bound model are untouched, so the verdict is
still attributed to the real agent and still runs on the pair its operator
chose. This narrows the SESSION and never the roster: the agent keeps its own
grants everywhere else. The red-team gate dispatches through the same helper
(security/redteam/runner.py), so the two cannot drift.
What "the deliverable" is¶
The reviewed deliverable is the content of the files the task produced at
its declared paths, with the agent's closing message alongside rather than
instead. engine/artifacts/deliverable_content.py reads each
ExpectedArtifact.path inside the task's project workspace and
engine/review_gate_inputs.py assembles the two into one SEC-1 fenced block
(wrap_untrusted(TAG_TOOL_RESULT, ...)), which every downstream consumer of
the review input shares: the peer reviewer, the red-team gate, and the
output-policy observation.
The files travel typed as well as composed (RedTeamReviewInput
.produced_artifacts), read once and used for both, because a consumer asking
a per-file question cannot get the files back out of the composed JSON
document without parsing the thing it was just handed.
The reviewer reads the thing it is approving. A gate shown only the closing
prose grades the summary rather than the deliverable, and an APPROVE then means
the agent wrote a convincing account of its own work. This is the most
load-bearing gate in the chain (fail-closed, on by default,
min_stakes=low), so the files are what it reads.
The closing message comes from the run being judged, passed in by the
caller that is holding it (attempt_deliverable, bound onto the builder for
that one review). Asking the flight recorder instead gives the question two
owners, and the second is an observability store: a recorder that stored
nothing makes delivered work indistinguishable from an agent that produced
nothing, which sends it to rework as empty, and a checkpoint-resumed attempt
is answered for by the pre-recovery FAILED attempt, whose turns are the
highest ones recorded. The recorder remains the fallback for a review with no
run in hand (a later, detached read), and there it is a real dependency:
with cockpit.flight_recorder_enabled off, or
cockpit.flight_recorder_sink_strategy set to noop, no frame exists, the
builder returns None and the gate applies its on_missing_deliverable
posture instead of reviewing content. That path logs at WARNING, because it
is a fact about the system rather than about the task.
Size is bounded by two live settings, so an operator can tune what the
reviewer receives without a restart: engine.review_artifact_max_chars_per_file
(default 20000) and engine.review_artifact_max_chars_total (default 60000).
Truncation, omission, an absent path, a directory, and an unreadable file
each produce an explicit note in the assembled text rather than silently
shrinking the deliverable, because a reviewer that cannot tell "empty" from
"not shown" cannot judge either.
The reviewer-is-distinct invariant is enforced at three independent layers, which cover different paths rather than each covering all of them:
- Type-level.
_forbid_self_reviewinengine/completion_oracle/review_models.pyrejects construction of aCompletionOracleReport, its verdict payload, or the gate'sruntime_contextwhen the reviewer and executor ids match. Any caller that builds one of these objects meets the check, whatever route it took. - Gate-level.
CompletionOracleGate._validate_verdictcompares all four pinned identities on the filed report (execution, task, reviewer, executor) against the trusted context the gate seeded. Without the reviewer and executor comparisons, a filed report could carry forged ids that satisfy the type-level check while the real executor reviewed its own work. - Row-level.
completion_oracle_reportscarriesCHECK (reviewer_agent_id IS NULL OR executor_agent_id IS NULL OR executor_agent_id != reviewer_agent_id)in both backends, the twin of thedecision_recordsCHECK. It guards every row that names both parties; a row naming neither is admitted, because NULL there means "not recorded" rather than "same agent".
Drawing reviewers from a roster where any agent can hold any role makes the row-level check matter more, not less: it is the layer that still holds when something upstream lies. What all three establish is that the reviewer was a different agent, and nothing more. Each verdict is archived (failure-tolerant) in that append-only, dual-backend table so an operator can answer "why was this deliverable sent back?" long after the run; an archive-write failure is logged but never blocks or alters the verdict (fail-OPEN, the one fail-open path in an otherwise fail-closed gate).
Comparable verdicts¶
A row is one review EVENT, not one execution: a task decided, re-opened and
decided again is reviewed twice and archives twice, so the table carries a
surrogate report_id and the read order closes on it.
Alongside the reviewer and executor ids, each row records the
(reviewer_provider, reviewer_model_id, reviewer_capability) the review
actually ran on. The reviewer's current roster binding is not evidence of
what ran months ago, and without the pair on the row "verdict quality per
model" has nothing to group by. All three are nullable, because a row can
genuinely not know what ran: NULL is the honest value there rather than a
fabricated attribution.
GET /completion-oracle/reports reads the archive (filters: execution_id,
task_id, verdict, reviewer_agent_id; cursor-paginated), and
GET /completion-oracle/reports/summary counts a reviewer's verdicts by kind
at the storage layer, because a tally over one page would report a window as a
total. GET /red-team/reports and its /summary are the exact twins. On the
dashboard an agent holding either gate role gains a verdicts panel on its
detail page, so its record sits beside the rest of its work.
Fail-CLOSED posture and mapping¶
Unlike the red-team and vision gates, which fail OPEN so a verifier defect can
never block completion, the peer-review gate fails CLOSED: a dispatch
failure, a missing verdict, or an unresolvable distinct reviewer yields an
ESCALATE verdict, never a silent pass. The two failing verdicts part company
at that point, because they ask different things of different people: a REJECT
reroutes the task to IN_PROGRESS rework with the reviewer's summary as the
reason, which the agent acts on, while an ESCALATE parks it at BLOCKED with
blocked_reason=oracle_escalated, which a human acts on. The human's answer
rejoins the review it came from over the BLOCKED -> IN_REVIEW edge, and the
judge is not re-run on it: re-judging the answer re-escalates and discards the
decision the escalation existed to obtain. The reason is what makes that skip
safe, since a coordination wave releasing a subtask parks a task at BLOCKED
too, and keying on the status alone would exempt it from review it never had.
An unstaffed park (reviewer_unstaffed / red_team_unstaffed) is the same
distinction from the other direction: nobody answered it, so it MUST be
re-judged once the role is filled, which is why it carries its own reason
rather than borrowing the escalation's. The two staffing reasons stay apart
from each other for the same kind of reason: filling one role releases nothing
parked on the other.
APPROVE / APPROVE_WITH_NOTES lets completion proceed. completion_oracle_min_stakes
(default low, so every task is reviewed) gates the expensive agent-session
review; the deterministic build/test gate runs regardless of it.
completion_oracle_shadow_mode runs the reviewer and surfaces the verdict
without enforcing it, for an observation period before enforcement.
Nobody holds the role¶
Peer review has exactly one way to be unavailable: nobody holds the role. That state is visible in the roster and fixable through the ordinary role-assignment surface, and it is the only such state because there is no reviewer-model setting to be left unset alongside it.
Unstaffed is fail-closed and says so. The gate returns an ESCALATE verdict
whose summary names the condition, logs
completion_oracle.review.reviewer_unstaffed with the role, project, executor
and candidate count, and parks the task at BLOCKED with
blocked_reason=reviewer_unstaffed. That reason is load-bearing rather than
decorative: the human-answered skip keys on oracle_escalated alone, so an
unstaffed park is re-judged once the role is filled instead of being read as a
decision somebody already made. Falling back to a built-in identity is the
tempting alternative and is rejected: an identity nobody staffs is not a peer,
so it would convert "no reviewer" into a verdict comparable with nothing.
Two things then happen without an operator watching:
-
A hire is requested, once per unstaffed role org-wide and approval-gated. Nothing hires itself: the request opens an
ORG_HIREapproval item and a human decides. Approval is what instantiates and registers the agent, so the tail from "human approves" to "agent exists" is reachable; without that tail an auto-hire would be theatre.An approved hire can still be unbindable, and that is a third outcome rather than a failure to retry: the request names no
(provider, model)pair, or names one whose connection or model the operator no longer has. No sweep changes that, so retrying it forever leaves the operator with a request that fails every pass and says so on no surface. It is instead withdrawn and the operator told, because the hire the human authorised cannot happen as written and the decision belongs back with them. A transient failure is still just retried. - The park heals, level-triggered.engine/review_staffing/reconciler.pysweeps tasks parked on either staffing reason and walks each oneBLOCKED -> IN_REVIEWonce an eligible holder exists, so the review runs properly rather than being waved through. Every pass logs what it moved and what it left, and a park that persists says why. The sweep is periodic (engine.review_staffing_resync_interval_seconds, default 900) and is additionally nudged when the roster changes, so a hire that lands is picked up immediately rather than at the next tick.
A parked gate with no human is the harness's problem, not the product's.
Every park above heals because something can eventually answer it: a human
decision, a staffing reconciler, an operator hire. The recursion-depth eval
harness's gated arm reviews each merge through OracleMergeReviewer
(evals/recursion_depth/gate.py), which reaches this same completion-oracle
ESCALATE path when its review session starves; when that happens the merge
parks (parked=True, no verdict) exactly as the product's own parks do, and
nothing in the harness ever answers it -- there is no human, no reconciler, no
staffing sweep. The ungated arm's BlindMergeReviewer never parks by
construction (it returns approved=None, parked=False on every attempt,
having asked no question to escalate), so this is a gated-arm-only failure
mode. A merge whose every attempt parks is therefore UNJUDGED rather than
gated-and-approved, and the report excludes it from the depth curve rather
than reading the absence of an answer as one. That is a deliberate property
of the measurement, not a gap in this design: the product's own parks always
have a party that can release them.
Order of Operations¶
Quality and approval surfaces operate at distinct points in the task
lifecycle: the verification stage, the review pipeline, the mid-execution
AUTH_REQUIRED park, the post-completion IN_REVIEW gate, the completion
oracle (build/test then peer review), and the adversarial red-team gate.
| Phase | Surface | Trigger | Task status during | Exit | Where documented |
|---|---|---|---|---|---|
| Mid-execution | AUTH_REQUIRED park |
Agent calls a tool that requires approval at runtime (e.g. deploy, db:admin). Driven by ApprovalGate middleware. |
AUTH_REQUIRED |
Approved: returns to ASSIGNED. Denied / timeout: CANCELLED. |
Security: Approval Workflow |
| Agent done | Verification stage | Workflow blueprint has a VERIFICATION control-flow node. Runs as a separate evaluator agent with its own context. |
IN_PROGRESS (engine-internal) |
Pass: continue to next node. Fail: regenerate. Refer: hand to human via VERIFICATION_REFER edge. |
This page; Workflow Node and Edge Types |
| Agent done | Review pipeline | Task transitions IN_PROGRESS to IN_REVIEW. Chain of ReviewStage instances runs. |
IN_REVIEW |
First-failing stage returns the task to IN_PROGRESS; all-pass moves to COMPLETED. |
This page, Review Pipeline |
| Review pipeline PASS | Completion oracle gate | On by default (engine.completion_oracle_enabled). Two composed gates, first in the chain: the deterministic build/test gate (always) and the agent-session peer reviewer (when task.stakes >= completion_oracle_min_stakes, default low). Fires on both the auto-review and human-approve paths. |
IN_REVIEW |
Build/test BUILD_TEST_FAILED / UNVERIFIED (fail-CLOSED) or reviewer REJECT: routes back to IN_PROGRESS with the reason. Reviewer ESCALATE: parks at BLOCKED with blocked_reason=oracle_escalated, because it asks a human rather than requesting rework; the answer rejoins the review through BLOCKED -> IN_REVIEW. Nobody holding the Completion Reviewer role: parks at BLOCKED with blocked_reason=reviewer_unstaffed; the staffing reconciler opens the approval-gated hire, releases the park and re-drives the review once a holder exists. VERIFIED + APPROVE: proceeds. Shadow mode: verdict surfaced, not enforced. |
This page, Completion Oracle Gate |
| Completion oracle PASS | Output-style observation | Deterministic (no LLM), always on when the policy is wired and enabled. NOT a gate: it reads the produced files, one per declared path, and reports what still carries a hard-rule violation. Style is enforced in-session at the tool that wrote the file, where the agent can fix it on its next turn; here the session has ended and the only correction left would be a whole re-dispatch. | IN_REVIEW |
Always the prior verdict, unchanged. It returns no outcome, so it cannot reroute, un-approve or fail a task. A blocking finding emits one output_style.backstop.observed WARNING. |
Output-Style Policy |
| Completion oracle PASS | Red-team gate | Opt-in (CompanyConfig.security.red_team.enabled) AND stakes-gated: fires when the review pipeline returns its COMPLETED verdict and the completion oracle has not blocked, BEFORE the task-engine transition lands, only when task.stakes >= engine.red_team_min_stakes (default HIGH). |
IN_REVIEW |
BLOCK: routes back to IN_PROGRESS with the red-team summary as the rework reason. PASS / PASS_WITH_FINDINGS: pipeline's verdict stands. Nobody holding the Red Team role: parks at BLOCKED with blocked_reason=red_team_unstaffed; the staffing reconciler opens the hire, releases the park and re-drives the review once a holder exists. Below the stakes threshold: SKIP (logs RED_TEAM_GATE_SKIPPED), pipeline's verdict stands. |
Security: Adversarial Red-Team Gate |
| Red-team gate PASS | Vision verifier gate | Opt-in (CompanyConfig.security.vision_verify.enabled). The UI cousin of the red-team gate: fires after the red-team gate for GUI deliverables that carry screenshots (vision_input). Pluggable VisionVerifier (noop / heuristic / llm_vision) judges whether the running app matches the brief. |
IN_REVIEW |
BLOCK: routes back to IN_PROGRESS with the vision summary as the rework reason. PASS / PASS_WITH_FINDINGS: prior verdict stands. Absent screenshots: SKIP (non-GUI deliverable). |
This page, Vision Verifier Gate |
| Human decision | Review-gate decision | A human approves/rejects the parked review item via ReviewGateService.complete_review. Both a completed run (review:task_completion) and a failed run (review:task_failed) reach the queue. |
IN_REVIEW, BLOCKED (oracle escalation) or FAILED |
Completed: approve IN_REVIEW -> COMPLETED, reject IN_REVIEW -> IN_PROGRESS. Escalated: the decision walks BLOCKED -> IN_REVIEW first, so COMPLETED stays reachable only through the review the oracle guards. Failed: approve acknowledges (stays FAILED), reject retries FAILED -> ASSIGNED. |
Security: Failed-run review decisions |
Key invariants:
AUTH_REQUIREDis the mid-execution park reason and uses theApprovalGatemiddleware in the agent harness. The review pipeline is the post-completion quality gate and usesReviewGateService. The two are independent: a single task can encounter both (e.g. pause for deploy approval mid-task, then enterIN_REVIEWonce the agent finishes).- The verification stage runs BEFORE the review pipeline when both
are configured for the same workflow. Verification is a workflow
blueprint construct (a node in the graph); the review pipeline
fires on the
IN_PROGRESStoIN_REVIEWtransition that happens after the workflow's last node completes. - The review pipeline does not mint new
TaskStatusvalues; the task stays atIN_REVIEWthroughout, with stage progress in metadata. - The gates split on fail policy, and a maintainer must not invert them by copy-paste. The red-team and vision gates fail OPEN on an internal fault (a verifier defect must never block completion). The completion oracle fails CLOSED: the build/test gate blocks a code task it cannot confirm builds, and the peer-review gate escalates to a human when no distinct reviewer or verdict is resolvable, because its whole purpose (an independent reviewer must exist) would otherwise be silently defeated.
See Also¶
- Task & Workflow Engine: task dispatch, state coordination
- Agent Execution: per-agent execution loop
- Coordination: multi-agent topology, decomposition
- Design Overview: full index