# Team Spawning And Review Gates

> Use when the orchestrator is dispatching teammates in Phase 2 or capturing review-gate evidence in Phase 3. Defines non-overlapping file-scope rules, plan-approval-mode triggers, direct teammate-to-teammate messaging conventions, the review-gate evidence file schema (v7 — the teammate's 17-field self-review including the 5 Verified Agent Output fields, plus an independent task-reviewer verdict), the independent task-reviewer dispatch, the teammate manifest format the SubagentStop hook reads, and escalation policy on repeated hook rejection.

- Skill: `paulingram/team-spawning-and-review-gates` (Agent Skill)
- Install (CLI): `npx skillmds@latest add paulingram/team-spawning-and-review-gates`
- Raw SKILL.md: https://api.skillmd.com/api/skills/paulingram/team-spawning-and-review-gates/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: paulingram (https://skillmd.com/u/paulingram)
- Updated: 2026-09-21
- Page: https://skillmd.com/skills/paulingram/team-spawning-and-review-gates

---


# 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

1. Read `tasks.md` and the coverage map.
2. For each task, list every file it will create or modify (use the design.md's Reuse Decisions as the canonical list).
3. Group tasks by overlapping file sets. Each non-overlapping group becomes one teammate's scope.
4. 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 from `tasks.md` it 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 from `design.md`'s Reuse Decisions section.
- `plan_approval_mode`: `true` if 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:**

1. The task is a **`backend-dep`** shape → spawn BOTH `fake-data-hunter` AND `security-hunter` (a backend-touching slice is exactly where authz / injection / secret defects land).
2. The teammate's diff **touches `auth/` or any security-sensitive path** (auth, session, crypto, permission, RBAC, token, password, secret) → `security-hunter` is mandatory regardless of task shape.
3. The change **adds a third-party dependency** (a new entry in any package manifest) → `security-hunter` is 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.py` lock 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.json` shape) 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 — via `calls` edges in the graph — a function
  in the other's set**. The reachability walk reuses the CDLG's `calls`-edge
  vocabulary from `hooks/lineage_graph.py` (the `REACHABILITY_EDGE_KINDS`
  concept), so the lock layer consumes the lineage graph rather than re-deriving
  the edge model.
- Output: `{"overlap": bool, "shared_functions": [...], "shared_subtree": [...]}`
  — `shared_functions` names the directly-shared nodes, `shared_subtree` names 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_route` edge resolved by route/contract matching (NOT call-graph
  traversal), reusing `INTEGRATION_MAP` + the user-confirmed
  `INTERACTION_INTUITION_MAP` as priors. This is the REQ-DOC-07 seam the
  traversal is **built on**: each `serves_route` edge carries its `match_basis`
  (route pattern / contract) + a `confidence`, and unresolved seams are surfaced,
  never silently bridged.
- **endpoint → function tree** is the intra-service call-hierarchy: the `serves`
  edge (endpoint → handler) and the recursive `calls` edges
  (`endpoint-trace-mapping`'s nested call-trees).
- **function tree → data_asset** is the asset-lineage layer: the `reads` /
  `writes` / `modifies` / `originates` edges from a `func://` node to an
  `asset://` 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`):

```json
{
  "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_review` and `quality_review` must be `"pass"`.
- `real_not_stubbed` must be `true`.
- `tests.added` must equal `tests.passing`.
- `tests.added` must be ≥ 1.
- `demo_artifact` must be a non-empty string.
- `files_changed` must be a non-empty array.
- `reuse_compliance` must be `"ok"`.
- `visual_fidelity_review` must be one of `"pass"` / `"n/a"` / `"fail"`. The hook BLOCKS `"fail"` — drift / gaps detected by `visual-fidelity-reconciliation` MUST 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_note` is required (non-empty string) WHEN `visual_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_review` must be one of `"pass"` / `"n/a"` / `"fail"`. The hook BLOCKS `"fail"` — test-kind completeness gaps detected by `test-completeness-verifier` MUST 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_note` is required (non-empty string) WHEN `test_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_review` must be one of `"pass"` / `"n/a"` / `"fail"`. The hook BLOCKS `"fail"` — a `both`-layer feature whose happy-path user-flow tests ran against a mocked / fake backend (`page.route` happy-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 every `both`-layer feature per `playwright-user-flows`'s "Real backend by default" discipline — it is overridden only by an explicit authorization in the requirements folder.
- `integration_testing_review_note` is required (non-empty string) WHEN `integration_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/a` is never valid for a `both`-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_review` must 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 real `page.click` / `page.fill` path, not a `page.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 to `integration_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 the `interaction-completeness` team, 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_note` is required (non-empty string) WHEN `ui_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), an `approved` appearance proposal, or — innovate mode — a logged `implemented-innovate` proposals entry. The hook BLOCKS `"fail"` — revert the unsolicited delta or route it as a proposal per `common-pipeline-conventions` `## Appearance-change policy discipline (v3.14.0)`; do not mark complete. `appearance_scope_review_note` is required (non-empty string) WHEN the value is `"n/a"` (the slice touches no frontend presentation surface).
- `independent_review` is a REQUIRED object — the verdict of the independent `task-reviewer` agent (not the teammate). The hook blocks evidence with no `independent_review` block. Its required sub-fields:
  - `reviewer` — a non-empty string naming the reviewing agent. It **MUST NOT equal the top-level `teammate` field** — the producer cannot be its own checker. The hook blocks `reviewer == teammate`.
  - `verdict` — must be `"pass"`. A non-`"pass"` verdict means the `task-reviewer` found 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_review` and `quality_review` — must be `"pass"`; `real_not_stubbed` — must be `true`; `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`, `notes` are recommended evidence fields (per-criterion trace, the commands the reviewer ran, a summary) — the hook does not require them, but a `task-reviewer` always 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:

1. **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.
2. **Pre-change checkout** (`red_source: "pre-change-checkout"`). The test was run against the run's baseline — `git stash`-free, via the orchestrator-provided `baseline_sha` (a `git worktree add` at that SHA, or a `git show $BASELINE_SHA:<path>` reconstruction) — and failed there. Use this when the test was authored after the implementation landed.
3. **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:

```json
{
  "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

```bash
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:

1. **`<workspace>/.architect-team/intake-state.json`** as the `baseline_sha` field — the same file that already holds the `dispatch_mode` decision (v1.0.0) and the run's other startup metadata.
2. **Every teammate's spawn brief** at `<workspace>/.architect-team/teammates/<teammate>.json` (the v0.9.13 teammate manifest schema). The brief gains a `baseline_sha` field 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:

```json
{
  "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:

```bash
# 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

```json
{
  "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)
