Team Spawning & Review Gates
The orchestrator's parallelism only works if every teammate has crisp boundaries and the review gates have evidence to enforce. This skill defines both.
Operating context (v1.0.0) — for teammate agents
This section is the canonical long-lived-teammate framing every teammate agent in the architect-team pipeline references. Each agents/*.md body carries a one-line pointer here rather than re-stating the paragraph; this file is the single source of truth, so wording changes ship in one place.
You are a long-lived teammate in an architect-team run — not a one-shot subagent. The Lead spawns you and assigns work via the shared task list (teams mode) or dispatches you per-task (subagents mode); either way, you stay in your role across multiple tasks within this run and your 1M context window accumulates the run's prior decisions, maps, and review evidence. You receive tasks from the Lead; if your work surfaces a follow-up that needs a different agent type, you write a solution requirement and return to the Lead — you do NOT spawn other agents or teams yourself. Internal short-lived Agent subagents for sub-research within your task are permitted (per Claude Code's standard semantics) and are NOT a nested team.
Non-overlapping file scopes
Two teammates MUST NEVER edit the same file. Period.
How to assign scopes
- Read
tasks.mdand the coverage map. - For each task, list every file it will create or modify (use the design.md's Reuse Decisions as the canonical list).
- Group tasks by overlapping file sets. Each non-overlapping group becomes one teammate's scope.
- If a single task forces overlap (e.g., a contract file that backend writes and frontend consumes), assign the task to ONE owner and have the other consume the result — see "Direct messaging" below.
What to put in the teammate's brief
First line (MANDATORY, v3.30.0): the run-continuity teammate token. Every teams-mode spawn brief AND every subagents-mode dispatch prompt begins with the literal line:
[CT6-TEAMMATE <teammate-name> RUN <run-id-or-slug>]
The CT6-TEAMMATE token is how the run-continuity enforcement (common-pipeline-conventions ## Run continuity discipline (v3.30.0)) recognizes a teammate session and stands down — teammates never invoke Skills, so without the token the PreToolUse sticky arm could block the run's own workers. A brief that omits the token risks its teammate being told to resume the pipeline instead of executing its slice; the Lead re-issues the brief with the token if a teammate reports that block.
Then the brief fields:
task_ids: the exact task IDs fromtasks.mdit owns.files_owned: the explicit list of files it may write. Anything not in this list is read-only for this teammate.files_consumed: files it reads but does not write (with the owning teammate's name where relevant).acceptance_criteria: verbatim from the coverage map.relevant_codebase_map_sections: paths into CODEBASE_MAP.md.reuse_decisions: the relevant entries fromdesign.md's Reuse Decisions section.plan_approval_mode:trueif any of the triggers below apply.vao_adversarial_role: the adversarial-reviewer shape paired with this teammate (see the selection rules below).
Every brief MUST carry the message-payload rule (see ## Direct teammate-to-teammate messaging → ### Message payload discipline). A teammate that does not know SendMessage is user-visible will route its evidence through it by default, and the human running the pipeline pays for that in attention. State it in the brief in one line — escalate through SendMessage with the decision and the single fact that forces it, plus the path to your evidence file; put measurement in the file, not the message; never send a completion report — your final report already reaches the Lead. The escalation obligation itself is unchanged and outranks brevity: a brief that is wrong, or an approach that cannot work, is still reported rather than absorbed.
Adversarial-reviewer shape selection (v2.0.0 + v3.10.0 security-hunter)
Every Phase 3 teammate is paired with an adversarial-reviewer whose vao_adversarial_role is chosen by the task shape (the agent body agents/adversarial-reviewer.md documents the six shapes). The pairing rules:
| Task shape | vao_adversarial_role(s) to spawn |
|---|---|
parity-verb (match / rebuild / mirror / parity / replicate) |
oracle-divergence-hunter |
backend-dep (the slice depends on a backend endpoint / data) |
BOTH fake-data-hunter AND security-hunter |
dynamic-value (renders per-user / per-record values) |
hardcoded-literal-hunter |
shared-tree (always-on, every teammate) |
git-discipline-hunter |
| any other | general-anti-pattern-hunter |
security-hunter trigger rules (v3.10.0) — mandatory when ANY holds:
- The task is a
backend-depshape → spawn BOTHfake-data-hunterANDsecurity-hunter(a backend-touching slice is exactly where authz / injection / secret defects land). - The teammate's diff touches
auth/or any security-sensitive path (auth, session, crypto, permission, RBAC, token, password, secret) →security-hunteris mandatory regardless of task shape. - The change adds a third-party dependency (a new entry in any package manifest) →
security-hunteris mandatory; it checks the dependency-addition justification (a Reuse Decision + a stated reason).
A confirmed security-hunter finding is routed as a solution requirement with origin.kind: "security-finding" (see ## Solution Requirements); it is NOT a Layer 3 verify_* verdict severity.
CDLG overlap — shared callees, not just shared files (lineage roadmap P4 — REQ-CDL-09 / REQ-PARA-01)
File-path scoping (above) catches the case where two teammates edit the SAME
file. It does NOT catch the more subtle case the lineage roadmap surfaced: two
work-items edit DIFFERENT files but share a hot callee — item A's function
transitively calls a function that item B's slice also touches. Dispatching those
two in parallel is a hidden overlap: a change A makes to the shared callee's
behavior can break B even though their files_owned sets are disjoint.
When a Code & Data Lineage Graph (CDLG) exists for the work in scope (built by
endpoint-trace-mapping into lineage-graph.json), the parallel-execution graph
- the
hooks/locks.pylock layer consult CDLG overlap as an ADDITIONAL signal alongside the file-path check — never in place of it. The two signals compose: a pair of work-items is safe to run in parallel only when they are disjoint on BOTH file scope AND call-graph reachability.
The call-graph overlap verdict is the stdlib helper cdlg_overlap(graph, funcs_a, funcs_b) in hooks/locks.py:
- Inputs: the CDLG (
lineage-graph.jsonshape) and the two work-items'func://node sets (the functions each slice owns/touches). - Rule (REQ-PARA-01): two items overlap iff they share a
func://node OR one item's function set reaches — viacallsedges in the graph — a function in the other's set. The reachability walk reuses the CDLG'scalls-edge vocabulary fromhooks/lineage_graph.py(theREACHABILITY_EDGE_KINDSconcept), so the lock layer consumes the lineage graph rather than re-deriving the edge model. - Output:
{"overlap": bool, "shared_functions": [...], "shared_subtree": [...]}—shared_functionsnames the directly-shared nodes,shared_subtreenames the transitively-reached shared nodes (the callee two items edit different files but both depend on). A non-empty either list means the orchestrator serializes the two items (or assigns the shared callee to ONE owner and has the other consume the result, exactly as the file-overlap rule prescribes).
So the headline rule becomes: two items that edit different files but share a hot callee are flagged as overlapping and are NOT dispatched in true parallel — closing the file-path-only blind spot.
Canonical front→back traversal (lineage roadmap P4 — REQ-CDL-09 / REQ-PARA-02)
The CDLG also enables a single navigable traversal that chains the whole stack:
UI element → endpoint → function tree → data_asset
- UI element → endpoint is the inter-service seam — the REQ-DOC-07
serves_routeedge resolved by route/contract matching (NOT call-graph traversal), reusingINTEGRATION_MAP+ the user-confirmedINTERACTION_INTUITION_MAPas priors. This is the REQ-DOC-07 seam the traversal is built on: eachserves_routeedge carries itsmatch_basis(route pattern / contract) + aconfidence, and unresolved seams are surfaced, never silently bridged. - endpoint → function tree is the intra-service call-hierarchy: the
servesedge (endpoint → handler) and the recursivecallsedges (endpoint-trace-mapping's nested call-trees). - function tree → data_asset is the asset-lineage layer: the
reads/writes/modifies/originatesedges from afunc://node to anasset://node (data-lineage-mapping).
Walking that chain on the CDLG produces one validated traversal from a UI control
(an INTERACTION_INTUITION_MAP element) all the way to the data_asset it
ultimately reads or writes — for at least the bug subset — with the FE→BE hop
carrying its match basis + confidence (REQ-PARA-02). This is the "canonical
front→back map" the roadmap's C5.b calls for: the fetch() → handler edge is
finally resolved to a function, so the traversal does not stop at the endpoint
boundary.
Plan-approval-mode triggers (any one)
If a teammate's scope touches ANY of:
- Authentication / authorization code.
- DB schema (migrations, model changes).
- API contracts (OpenAPI / GraphQL SDL / gRPC proto / RPC schemas).
- Cross-service contracts (queue message schemas, shared event types).
- External integrations (third-party APIs, webhooks).
- Secrets / config / env-var schemas.
→ spawn the teammate in plan-approval mode. The orchestrator reviews and explicitly approves the plan before any tool calls run.
Direct teammate-to-teammate messaging
When two teammates need to coordinate (e.g., backend defines a contract, frontend consumes it):
- The owning teammate publishes its result to a known path (e.g., the contract file, plus a brief in
.architect-team/handoffs/<owner>-to-<consumer>.md). - The consuming teammate is told in its brief: "Wait for the handoff from
<owner>at<path>before starting tasks T-X, T-Y." - Every cross-team message MUST be written to
.architect-team/handoffs/<from>-to-<to>-<timestamp>.md— this is the primary coordination primitive and survives across sessions. - If the harness exposes a teammate-messaging mechanism (e.g.,
SendMessage), use it as an optional shortcut in ADDITION to (not in place of) the handoff file. The orchestrator does NOT proxy.
Message payload discipline — the visible channel carries the decision, the file carries the evidence
SendMessage is rendered into the USER's transcript; a final report is not. That asymmetry is the whole rule. A subagent's return value reaches the Lead privately, but every SendMessage — teammate-to-teammate, teammate-to-Lead, escalation — is surfaced to the human watching the run. So the channel's cost is the user's attention, and its payload must be priced accordingly.
Send the decision and the one fact that forces it. Put everything else in the file. The handoff / evidence / verdict file is already the primary primitive (above); it is also where measurement belongs. A message that carries the decision AND its full derivation pays the visible cost twice and buries the ask.
| Send in the message | Write to the file, cite by path |
|---|---|
| The decision, refusal, or question — stated first, in one sentence | Tables, matrices, per-arm hashes, row counts, byte ledgers |
| The single number or fact that forces it | The full derivation, the alternatives considered, the search log |
| What you need from the reader, and what is blocked until you get it | Test output, command transcripts, per-file diffs |
| The path to the evidence | Anything the reader does not need in order to answer |
Do NOT use SendMessage for a completion report. "I finished, here is what I did" belongs in your final report and your evidence file — both already reach the Lead. Reserve the visible channel for what genuinely needs a reader: an escalation, a refusal, a blocking question, a correction to the brief, a cross-team handoff the consumer is waiting on.
This does not weaken escalation. A teammate that discovers its brief is wrong, or that a mandated approach cannot work, MUST still say so rather than absorbing it silently — that is the docs/ETHOS.md ## Evidence integrity obligation and it outranks brevity. Say it in three lines with a path, not thirty with a table.
Same rule for the orchestrator writing back to a teammate: a review-gate failure sends the verdict, the one measurement that proves it, and the path to the rest — not the whole reproduction.
Reading teammate state
An orchestrator coordinating parallel teammates is constantly tempted to convert absence of a report into a finding about the teammate. It is not one. A teammate is in exactly one of three KNOWABLE states, and only two of them support a conclusion:
| State | How you know it | What it supports |
|---|---|---|
| (a) Reported | A handoff file under .architect-team/handoffs/, a review-evidence file under .architect-team/reviews/, or the teammate's own dispatch report is on disk |
Any claim the artifact substantiates — including "it left this broken", quoting the artifact |
| (b) Idle-event fired | The SubagentStop / teammate-idle hook ran for that teammate (its output is the record) |
"It went idle with work outstanding", quoting the hook's structured gap list |
| (c) In-flight | Neither (a) nor (b) has happened yet | Nothing. Not "stalled", not "failed", not "stuck", not "did no work" |
A claim that a teammate stalled, failed, went idle, or left work broken MUST cite (a) or (b); state (c) supports no conclusion. In-flight is indistinguishable from a teammate that is mid-edit, mid-tool-call, or whose report crossed yours in flight — silence is not a finding. If you need to know, ASK the teammate directly (SendMessage) and wait for the answer; the answer is then evidence of kind (a). Reporting a state-(c) teammate as stalled to the user is the silence conversion anti-pattern in docs/ETHOS.md ## Evidence integrity.
Corollary — the mid-edit read. A suite run on the SHARED working tree while any teammate is in-flight on intersecting scope is a MID-EDIT READ: it samples a tree that is half-way between one teammate's implementation change and its test/expectation update. Red from such a run is unattributable until the owner reports — it may be the owner's transient state, not a defect, and it is never attributable to a DIFFERENT teammate whose files the run also swept. Options, in order: scope the run to files no in-flight teammate owns (git diff $BASELINE_SHA -- <files> tells you who owns what); wait for the owner's state-(a) report; or run and record the result as provisional, pending the owner's report — never as a finding against a named teammate. The non-overlapping file-scope rule at the top of this skill is what makes the scoped option available; the baseline-SHA discipline is what makes it cheap.
Review-gate evidence file
Path: <cwd>/.architect-team/reviews/<task-id>.json.
The teammate writes this BEFORE its TaskUpdate flips the task to completed. The PostToolUse(TaskUpdate) hook reads it and exits 2 (blocks completion) if it's missing or any field is invalid.
The 17 top-level review fields are the teammate's OWN self-review — a cheap first pass that catches the obvious. They do NOT gate on their own: the independent_review block (added in v5) is the verdict of an independent task-reviewer agent, and the hook requires it present with reviewer != teammate and verdict == "pass". See "## Independent review — the task-reviewer" below.
Schema (v7 — v2.0.0 added the 5 required Verified Agent Output fields oracle_match_review / baseline_clean_review / no_fake_data_review / adversarial_review / skill_invocation_audit, each accepting either the pass/n/a/fail string OR a {verdict, verdict_path} dict citing the on-disk Layer-3 / Layer-6 tool verdict; v0.9.19 added the required ui_interaction_review field + optional ui_interaction_review_note; v0.9.13 added the required independent_review block — an independent task-reviewer's verdict, so the gate cannot pass on self-attestation; v0.9.5 added integration_testing_review + optional integration_testing_review_note; v0.9.0 added test_completeness_review + optional test_completeness_review_note; v0.5.0 added visual_fidelity_review + optional visual_fidelity_review_note). Ground truth is hooks/review_evidence_schema.py (SCHEMA_VERSION = 7, 17 members in REQUIRED_EVIDENCE_FIELDS, 5 OPTIONAL_VAO_FIELDS):
{
"task_id": "T-3",
"teammate": "backend-auth",
"spec_review": "pass",
"quality_review": "pass",
"real_not_stubbed": true,
"tests": { "added": 4, "passing": 4 },
"demo_artifact": "curl -s localhost:8000/auth/login -d '{...}' | jq",
"files_changed": ["backend/auth.py", "tests/test_auth.py"],
"reuse_compliance": "ok",
"visual_fidelity_review": "n/a",
"visual_fidelity_review_note": "backend-only slice; no DESIGN_MAP",
"test_completeness_review": "pass",
"integration_testing_review": "pass",
"ui_interaction_review": "n/a",
"ui_interaction_review_note": "no frontend interactive surface in this slice",
"oracle_match_review": "n/a",
"baseline_clean_review": { "verdict": "pass", "verdict_path": ".architect-team/vao-verdicts/<run>-baseline-clean.json" },
"no_fake_data_review": "n/a",
"adversarial_review": { "verdict": "pass", "verdict_path": ".architect-team/vao-verdicts/<run>-adversarial.json" },
"skill_invocation_audit": { "verdict": "pass", "verdict_path": ".architect-team/vao-verdicts/<run>-skill-invocation-audit.json" },
"independent_review": {
"reviewer": "task-reviewer-1",
"verdict": "pass",
"spec_review": "pass",
"quality_review": "pass",
"real_not_stubbed": true,
"reuse_compliance": "ok",
"reviewed_at": "2026-06-09T00:00:00Z"
}
}
The 5 VAO fields accept BOTH forms shown above: the legacy pass/n/a/fail string (oracle_match_review, no_fake_data_review above) AND the canonical {verdict, verdict_path} dict citing the on-disk tool verdict (baseline_clean_review, adversarial_review, skill_invocation_audit above). The 5 OPTIONAL VAO fields (interactions_honored_review, live_verification_review, appearance_scope_review, check_integrity_review, claim_instrument_binding_review) are present-only-when-applicable — interactions_honored_review only when the run's oracle spec carries a non-empty interactions[], live_verification_review only when the evidence claims "verified live", appearance_scope_review (v3.14.0) only when the slice's diff touches frontend presentation surface (styling files, components, templates, routes, assets), and check_integrity_review (v3.47.0) only when the slice's diff adds test files or cites a verification command, citing the verify-check-can-fail verdict, and claim_instrument_binding_review (v3.59.0) only when the slice makes a verification CLAIM, citing the verify-claim-instrument-binding verdict — the borrowed green, where a real check's real green is lent to a claim that check never measured — so they are omitted from this minimal example.
The 17 top-level fields are the teammate's self-review. The independent_review block is written by the task-reviewer agent, NOT the teammate.
Required field validity:
spec_reviewandquality_reviewmust be"pass".real_not_stubbedmust betrue.tests.addedmust equaltests.passing.tests.addedmust be ≥ 1.demo_artifactmust be a non-empty string.files_changedmust be a non-empty array.reuse_compliancemust be"ok".visual_fidelity_reviewmust be one of"pass"/"n/a"/"fail". The hook BLOCKS"fail"— drift / gaps detected byvisual-fidelity-reconciliationMUST be escalated via handoff, not marked complete. Re-run reconciliation after the architect-routed fix and only mark complete when verdict is"pass".visual_fidelity_review_noteis required (non-empty string) WHENvisual_fidelity_review == "n/a". It must explain which branch applies (no frontend touched, OR no DESIGN_MAP.md exists for the codebase). Not required when value is"pass"(the reconciliation JSON paths carry the evidence).test_completeness_reviewmust be one of"pass"/"n/a"/"fail". The hook BLOCKS"fail"— test-kind completeness gaps detected bytest-completeness-verifierMUST be escalated via the SR auto-spawn (origin.kind: "test-completeness-failure"), not marked complete. The verifier writes the SR automatically; wait for the orchestrator to re-spawn the fix loop, then re-run the verifier to reach"pass".test_completeness_review_noteis required (non-empty string) WHENtest_completeness_review == "n/a". It must explain which kind(s) are inapplicable and why (e.g., backend-only slice so Playwright is n/a, OR no testable pure-logic surface for unit tests). Not required when value is"pass"(the verifier verdict JSON carries the evidence).integration_testing_reviewmust be one of"pass"/"n/a"/"fail". The hook BLOCKS"fail"— aboth-layer feature whose happy-path user-flow tests ran against a mocked / fake backend (page.routehappy-path stubs, MSW, an in-memory fake API server, hardcoded fixtures) instead of the real running backend MUST be re-authored against the real backend, or escalated via the SR auto-spawn (origin.kind: "integration-testing-failure"), not marked complete. Front-to-back integration testing is the DEFAULT for everyboth-layer feature perplaywright-user-flows's "Real backend by default" discipline — it is overridden only by an explicit authorization in the requirements folder.integration_testing_review_noteis required (non-empty string) WHENintegration_testing_review == "n/a". It must give ONE of three legitimate reasons: (1) the slice has no cross-layer surface (pure static frontend with no backend, OR backend-only slice with no frontend); (2) Phase 3 per-team gate where the counterpart layer is not yet integrated — the note says the integration test is DEFERRED TO PHASE 5 (a debt Phase 5 must settle against the real backend;n/ais never valid for aboth-layer slice at Phase 5); (3) the requirements folder explicitly authorizes isolated / mock-backed testing for this requirement — the note quotes the authorization. Not required when value is"pass"(the verifier verdict JSON + the demo artifact's real-backend reference carry the evidence).ui_interaction_reviewmust be one of"pass"/"n/a"/"fail"(added in v0.9.19 at schema v6; the current schema is v7). It is the gate that every interactive element the slice ships is genuinely user-flow-tested (a realpage.click/page.fillpath, not apage.request.*direct API call, not a vacuous navigate-and-assert) and correctly wired, every page is the real live page rather than a placeholder, and every displayed value is correctly a static literal or a dynamically-bound value — or a user-confirmed stub. It gates a genuinely orthogonal axis tointegration_testing_review(real-interaction-vs-fake-interaction, not real-backend-vs-mock — a test can be real-backend + fake-interaction, or mock-backed + real-interaction). The hook BLOCKS"fail"— an unwired control, an unconfirmed placeholder page, or a hardcoded value the context shows should be dynamically bound, detected by theinteraction-completenessteam, MUST be escalated via a solution requirement (origin.kind: "unwired-control"/"placeholder-page"/"hardcoded-dynamic-value"), not marked complete. Re-run the interaction-completeness team after the routed fix lands and only mark complete when the verdict is"pass".ui_interaction_review_noteis required (non-empty string) WHENui_interaction_review == "n/a". It must explain why — the slice has no UI/frontend interactive surface (no interactive elements, no pages / screens / routes — e.g., a backend-only or pure-infra slice). Not required when value is"pass"(the interaction-completeness team's converged map carries the evidence).appearance_scope_review(v3.14.0 — OPTIONAL; validated when present) must be one of"pass"/"n/a"/"fail", string OR{verdict, verdict_path}dict shape. Required whenever the slice's diff touches frontend presentation surface;"pass"means every appearance-affecting delta (visual styling, UI-surface additions/removals/relocations, displayed copy the requirement does not name, asset swaps) traces to one of the three sanctioned mandate sources (requirement text / spec restoration / mandated-capability minimum), anapprovedappearance proposal, or — innovate mode — a loggedimplemented-innovateproposals entry. The hook BLOCKS"fail"— revert the unsolicited delta or route it as a proposal percommon-pipeline-conventions## Appearance-change policy discipline (v3.14.0); do not mark complete.appearance_scope_review_noteis required (non-empty string) WHEN the value is"n/a"(the slice touches no frontend presentation surface).independent_reviewis a REQUIRED object — the verdict of the independenttask-revieweragent (not the teammate). The hook blocks evidence with noindependent_reviewblock. Its required sub-fields:reviewer— a non-empty string naming the reviewing agent. It MUST NOT equal the top-levelteammatefield — the producer cannot be its own checker. The hook blocksreviewer == teammate.verdict— must be"pass". A non-"pass"verdict means thetask-reviewerfound the task incomplete; the hook blocks it, and the teammate re-engages on the reviewer's per-gap notes (this is a normal Phase 3 review-gate failure — no SR, no diagnostic-research routing).spec_reviewandquality_review— must be"pass";real_not_stubbed— must betrue;reuse_compliance— must be"ok". These are the reviewer's INDEPENDENT findings on the same checks, made after reading the teammate's diff.reviewed_at— a non-empty string (ISO 8601 UTC).criteria_findings,checks_run,notesare recommended evidence fields (per-criterion trace, the commands the reviewer ran, a summary) — the hook does not require them, but atask-revieweralways writes them so the verdict is auditable.
Any missing or failing field → hook blocks. Re-engage on the failing item, fix, update evidence, retry. A failing independent_review means re-engage on the reviewer's notes; once fixed, the task-reviewer re-reviews and re-writes the block.
Gates the orchestrator names during the run are recorded, not remembered. Any condition stated as gating ship / deploy / merge / completion — in a spawn brief, a phase decision, or user-facing prose — is appended to <workspace>/.architect-team/declared-gates.json when it is declared and carries satisfied_at + an existing non-empty evidence_path before the run may complete, per common-pipeline-conventions ## Declared-gates discipline (v3.47.0). The Phase-8 / B8 / M7 ship step reads the registry first, and the _audit_declared_gates Stop arm blocks on an unsatisfied entry quoting the gate's own words.
Red-first — a new guard is not evidence until it has been shown to fail
tests.added and tests.passing say a test exists and is green. Neither says the test can ever be anything else. A test that asserts something already true, targets a path the runner never collects, or exercises a code path the change did not touch is green on day one and green after the feature is deleted — it is a green light wired to nothing. This is the same rule the bug-fix pipeline has always enforced (the reproduction artifact must reproduce BEFORE it regresses); it applies to every NEW test in every pipeline, not only to bug reproductions.
For every new test, capture its failure before you trust its pass. There are exactly three acceptable sources of that red run — name which one you used:
- TDD red (
red_source: "tdd-red"). The test was written and run BEFORE the implementation existed, and its failure output was captured. This is the default and the cheapest; it is also the only one available for a genuinely new capability whose pre-state cannot be built. - Pre-change checkout (
red_source: "pre-change-checkout"). The test was run against the run's baseline —git stash-free, via the orchestrator-providedbaseline_sha(agit worktree addat that SHA, or agit show $BASELINE_SHA:<path>reconstruction) — and failed there. Use this when the test was authored after the implementation landed. - Assertion inversion / mutation (
red_source: "assertion-inversion"). The assertion was deliberately inverted, or the line under test deliberately broken, the test was run and observed to FAIL, and the mutation was reverted. Use this when the pre-change state cannot build or the test cannot run against it — it is the fallback that keeps the rule satisfiable, not a shortcut around 1 and 2.
Those three quoted tokens are the only values verify-check-can-fail recognizes in a red_run block's red_source. The field is OPTIONAL — omitting it leaves a minimal artifact valid — but an UNRECOGNIZED value is itself a finding (new-guard-never-shown-red), because a red source the tool cannot name is a red source nobody can audit. Name yours.
Cite the captured failure output in the review evidence — the artifact path plus which of the three sources produced it. A red run you ran but did not capture is indistinguishable from one you did not run. The deterministic gate is the Layer-3 verify-check-can-fail tool (see verified-agent-output): a diff-added test file with no cited red run is new-guard-never-shown-red, and a cited red-run output containing no failure signature is red-run-not-red. Both name the exact test file, so the fix is always concrete.
The same discipline runs one level up, at the CHECK: capture the output of every verification command and read it. collected 0 items, no tests found, No test files found, and a tsc --noEmit against a solution-shaped tsconfig.json all exit 0 while verifying nothing — a vacuous-check. An exit code is not a result.
Teammate manifest
Path: <cwd>/.architect-team/teammates/<teammate-name>.json.
The orchestrator writes this when spawning. The SubagentStop hook reads it on subagent stop to validate the teammate didn't go idle with uncompleted work.
Schema:
{
"schema_version": 1,
"teammate": "backend-auth",
"spawned_at": "<ISO 8601 UTC>",
"task_ids": ["T-10", "T-11", "T-12"],
"files_owned": ["src/auth/login.py", "tests/auth/test_login.py", "..."],
"expected_review_evidence": ["T-10", "T-11", "T-12"]
}
The hook checks that for every task_id in expected_review_evidence, there's a valid review-evidence file. If not → exit 2 with a structured error naming the gaps. The harness re-engages the teammate.
Baseline SHA capture
The orchestrator captures a single immutable SHA reference at run start and includes it in every teammate's spawn brief. Teammates use it to diff their own work against the run's baseline WITHOUT touching shared git state — no git stash, no git reset, no race.
This sub-section documents the orchestrator-side mechanics. The forbidden-operations list and the failure-mode worked example live in common-pipeline-conventions ## Teammate git discipline — read that first if you haven't.
When the capture runs
At pipeline entry, BEFORE the first teammate is dispatched (so every teammate's spawn brief can carry the captured value):
- architect-team-pipeline: Phase −2 (Triage & Routing) prelude, alongside the dispatch-mode selection. The capture is one of the first orchestrator actions after the dispatch-mode banner prints.
- bug-fix-pipeline: Phase B−1 entry, before B0 (intake).
- mini-architect-team-pipeline: Phase M0 entry, before M1 (read prompt + brief).
The orchestrator runs git rev-parse HEAD once and records the SHA. Re-running the capture mid-run is forbidden — the baseline is the run-start anchor; if it slides, the teammates' diffs become meaningless.
Capture command
BASELINE_SHA=$(git rev-parse HEAD)
The same command resolves in any worktree the run might be executing in (the v1.2.0 auto-worktree, the main checkout, or a --no-worktree invocation). git rev-parse HEAD returns the SHA of the current branch's tip; it is a read-only probe and safe to call from any state.
Persisting the captured value
The orchestrator records the SHA in two places:
<workspace>/.architect-team/intake-state.jsonas thebaseline_shafield — the same file that already holds thedispatch_modedecision (v1.0.0) and the run's other startup metadata.- Every teammate's spawn brief at
<workspace>/.architect-team/teammates/<teammate>.json(the v0.9.13 teammate manifest schema). The brief gains abaseline_shafield carrying the same value verbatim.
The teammate manifest schema (from ## Teammate manifest above) is extended to carry the baseline_sha — and, beside it, the spec_fingerprint recording the SPEC state the teammate was briefed against:
{
"schema_version": 1,
"teammate": "backend-auth",
"spawned_at": "<ISO 8601 UTC>",
"task_ids": ["T-10", "T-11", "T-12"],
"files_owned": ["src/auth/login.py", "tests/auth/test_login.py", "..."],
"expected_review_evidence": ["T-10", "T-11", "T-12"],
"baseline_sha": "0a21702abc...def",
"spec_fingerprint": "3def77ec62e2...cda0"
}
The teammate reads baseline_sha from its manifest at spawn and uses it for all baseline-diff verification within its tasks.
spec_fingerprint (additive). The two fields are the same idea one axis apart: baseline_sha freezes the CODE the teammate started from; spec_fingerprint freezes the SPEC it was briefed against — a SHA-256 over the sorted (posix relative path, content) pairs of openspec/changes/<active-slug>/, computed by hooks/spec_fingerprint.py and written by the orchestrator at Phase 2 dispatch. The field is purely additive: a pre-upgrade manifest without it stays valid, and the _audit_spec_currency arm fails open when no manifest carries one. When the orchestrator amends an openspec artifact after dispatch, the fingerprint moves — and every teammate whose scope the amendment touches is owed a re-brief handoff plus an updated manifest fingerprint, per common-pipeline-conventions ## Spec currency discipline (mid-run) (v3.47.0). A teammate still working against a superseded fingerprint with no re-brief record is the failure that discipline exists to catch: two implementations built to two different readings of one line.
How teammates use it
Teammates substitute baseline-SHA diffs for git stash everywhere they would have stashed:
# What have I changed in MY files since the run started?
git diff $BASELINE_SHA -- <my-files>
# What does the current head differ from baseline?
git diff $BASELINE_SHA..HEAD
# Which commits in this run touched my files?
git log $BASELINE_SHA..HEAD --oneline -- <my-files>
# Has another teammate's commit already landed in a file I depend on?
git log $BASELINE_SHA..HEAD --oneline -- <upstream-file>
Each of these is a read-only operation on the shared git state. Two teammates running git diff $BASELINE_SHA concurrently cannot corrupt each other; the operation is idempotent and side-effect-free, the exact opposite of git stash.
What teammates MUST NOT do
For the canonical forbidden-operations list (the 6 forbidden destructive git operations and the rationale for each), see common-pipeline-conventions ## Teammate git discipline. The headline rule: no git stash / git stash pop, no git reset --hard, no git rebase, no git commit --amend, no git checkout <other-branch> / git checkout ., no git clean -f. The baseline_sha value the spawn brief carries is the alternative the orchestrator provides so teammates have a real way to verify their work without touching shared mutable state.
Solution Requirements — auto-spawn the dev loop on any surfaced issue
Whenever an agent surfaces an issue during testing — a Playwright failure, an integration test failure, a live-dev-API regression, a visual-fidelity drift, an RCA product-bug verdict — the agent does NOT just write a handoff and wait. It ALSO writes a structured solution requirement that the orchestrator automatically picks up and feeds back into Phase 2 of the dev loop. The loop is closed: issue → solution requirement → fix team spawned → fix flows through Phase 2 → Phase 5 → original test re-runs → verdict pass → originating teammate's task unblocks.
This converts "alert the user" into "fix the system." Alerts that don't trigger remediation are process failures; the discipline is to spawn the remediation, not log the alert.
File location
<cwd>/.architect-team/solution-requirements/SR-<short-id>-<ISO-8601-UTC>.json
where <short-id> is derived from the originating test ID, drifted screen+element, or affected requirement. Use _safe_id()-compatible characters only (no /, \, leading ., or ..).
Schema
{
"schema_version": 1,
"solution_id": "SR-test_user_completes_first_login-2026-05-18T15:00:00Z",
"created_at": "<ISO 8601 UTC>",
"origin": {
"kind": "playwright-failure" | "integration-test-failure" | "live-dev-regression" | "visual-fidelity-drift" | "rca-product-bug" | "visual-qa-audit" | "test-completeness-failure" | "integration-testing-failure" | "editability-gap" | "unwired-control" | "placeholder-page" | "hardcoded-dynamic-value" | "missing-api-for-frontend-element" | "security-finding" | "a11y-gap" | "spec-drift" | "cross-layer-backend-required" | "cross-layer-frontend-required" | "incomplete-implementation-scope-required" | "live-data-wiring-gap" | "affordance-coverage-gap",
"discovered_in": "Phase 3" | "Phase 5" | "/architect-team:visual-qa" | "ad-hoc",
"discovered_by": "<teammate-name or 'integration' or 'visual-qa'>",
"test_id": "<failing test ID, if applicable>",
"rca_artifact": "<path to rca/<test-id>-<ts>.json, if applicable>",
"reconciliation_artifact": "<path to visual-fidelity/<screen>-<viewport>-<ts>.json, if applicable>",
"handoff_artifact": "<path to .architect-team/handoffs/<from>-to-<to>-<ts>.md if also written>"
},
"problem_summary": "<one-paragraph user-facing description: what the user sees that they should not, in product terms not implementation terms>",
"expected_behavior": "<one-paragraph spec citation: what DESIGN_MAP / coverage-map / proposal.md says SHOULD happen>",
"evidence": [
"<path to log excerpt / screenshot / captured payload>",
"<file:line citation>",
"..."
],
"affected_requirements": ["REQ-012", "REQ-019"],
"affected_screens": ["/login", "/dashboard"],
"scope": {
"files_to_change": ["src/auth/login.py", "src/auth/__init__.py"],
"files_to_test": ["tests/integration/test_login_dev_api.py", "tests/playwright/test_login.spec.ts"]
},
"acceptance_criteria": [
"POST /api/auth/login with a soft-deleted account returns 410 Gone with body {error: 'account_deactivated'}",
"POST /api/auth/login with a valid account returns 200 with body.user.name as a non-null string matching the DB row's name column",
"Ex
…(truncated)