Add dispatch enforcement (Layer A + B + C) to prevent coordinator from doing domain work inline - #1537
Conversation
…astructure Add the full Layer B enforcement stack: - .squad/config.json: add dispatchEnforcement: warn - .squad/agents/scribe/charter.md: add Tool Access section + DispatchGuard audit loop specification (ledger read, ps1/sh invocation, self-respawn, runaway guards, output format) - .squad/agents/ralph/charter.md: replace minimal stub with full charter including DispatchGuard Verdict Consumer section and Skills listing - .squad/hooks/dispatch-audit.ps1: 410-line PowerShell audit script - .squad/hooks/dispatch-audit.sh: bash port with parity guarantee - .squad/hooks/README.md: platform guide (ps1 vs sh, prerequisites, invocation, output schema, parity test instructions) - .squad/hooks/tests/: 7 JSONL fixtures + ps1 and bash parity test runners - .squad/routing.md: extend Routing Principles with DispatchGuard notes for Scribe (bootstrap) and Ralph (verdict consumption) - .squad/templates/orchestration-log.md: append DispatchGuard ledger schema and verdicts file specification Empirically validated in tamresearch1 worktree (Ralph E2E report 2026-07-27). Smoke test: dispatch-audit.ps1 against compliant.jsonl returns verdict: ok. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a91fd081-d3e6-4432-809f-9590b4e06a2f
…ction Apply tools: allowlist to .github/agents/squad.agent.md frontmatter. Restricts the coordinator to dispatch-safe tools only: - agent (dispatch tool) - read, search, skill (context tools) - squad_state/*, squad_state_c3c25b85/*, squad_state_e7f10a1f/* (state bridge) - github-mcp-server/* (GitHub context reads) Effect: The Copilot runtime physically blocks any tool call outside this list with a hard error (Unknown tool name in the tool allowlist: 'create'). No behavioral instruction needed — enforcement is mechanical. Empirically verified in tamresearch1 worktree (2026-07-27): - All 3 Test 1 turns dispatched correctly; previously-regressed Turn 3 fixed - Verbatim errors: 'Unknown tool name in the tool allowlist: create/edit/grep' - Meta-gap: coordinator cannot write DispatchGuard ledger (accepted trade-off) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a91fd081-d3e6-4432-809f-9590b4e06a2f
…routing guard - .github/copilot-instructions.md: add Identity Lock section (named agent charter anchoring), routing guard reference, and adversarial input handling to the existing Copilot coding agent instructions. Existing squad-squad content (branch naming, git safety, protected files, changesets) preserved. - .github/instructions/squad-routing-guard.instructions.md: new file. Explicit routing decision tree for generic Copilot sessions. Routes work to the correct squad specialist (EECOM, Procedures, FIDO, PAO, Flight, etc.) before handling it inline. Prevents the CLI routing conflict pattern. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a91fd081-d3e6-4432-809f-9590b4e06a2f
Add explicit .squad/orchestration-log/dispatchguard/ entry to .gitignore. Ledger and verdict files are session-ephemeral and must never be committed. The parent .squad/orchestration-log/ entry was already present; the new entry is belt-and-suspenders for the dispatchguard/ subdirectory specifically, matching the path the audit script and Scribe use. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a91fd081-d3e6-4432-809f-9590b4e06a2f
Add decision record to .squad/decisions/inbox/ capturing the Layer A+B+C design, empirical evidence, known limitations, and accepted trade-offs. Scribe will merge this into .squad/decisions.md at next session. Force-added: .squad/ is in .git/info/exclude for consult-mode isolation; -f is required for any new .squad/ files from this branch. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a91fd081-d3e6-4432-809f-9590b4e06a2f
🟠 Impact Analysis — PR #1537Risk tier: 🟠 HIGH 📊 Summary
🎯 Risk Factors
📦 Modules Affectedci-workflows (3 files)
root (2 files)
squad-state (18 files)
This report is generated automatically for every PR. See #733 for details. |
🏗️ Architectural Review
Automated architectural review — informational only. |
|
🛫 PR Readiness Check
PR Scope: 🔧 Infrastructure
|
| Status | Check | Details |
|---|---|---|
| ❌ | Single commit | 6 commits — consider squashing before review |
| ✅ | Not in draft | Ready for review |
| ❌ | Branch up to date | dev is 33 commit(s) ahead — rebase recommended |
| ❌ | Copilot review | No Copilot review yet — it may still be processing |
| ✅ | Changeset present | Changeset file found |
| ✅ | Scope clean | |
| ✅ | No merge conflicts | No merge conflicts |
| ✅ | Copilot threads resolved | No Copilot review threads |
| ❌ | CI passing | 4 check(s) still running |
Files Changed (23 files, +2021 −20)
| File | +/− |
|---|---|
.changeset/fix-dispatch-enforcement-policy-gate.md |
+8 −0 |
.github/agents/squad.agent.md |
+80 −4 |
.github/copilot-instructions.md |
+36 −1 |
.github/instructions/squad-routing-guard.instructions.md |
+113 −0 |
.gitignore |
+1 −0 |
.squad/agents/ralph/charter.md |
+61 −12 |
.squad/agents/scribe/charter.md |
+36 −1 |
.squad/config.json |
+2 −1 |
.squad/decisions/inbox/dispatch-enforcement-decision.md |
+94 −0 |
.squad/hooks/README.md |
+167 −0 |
.squad/hooks/dispatch-audit.ps1 |
+436 −0 |
.squad/hooks/dispatch-audit.sh |
+598 −0 |
.squad/hooks/tests/compliant.jsonl |
+1 −0 |
.squad/hooks/tests/criterion1-triggered.jsonl |
+1 −0 |
.squad/hooks/tests/criterion2-triggered.jsonl |
+3 −0 |
.squad/hooks/tests/criterion3-triggered.jsonl |
+1 −0 |
.squad/hooks/tests/empty.jsonl |
+0 −0 |
.squad/hooks/tests/malformed.jsonl |
+2 −0 |
.squad/hooks/tests/mixed-with-verdicts.jsonl |
+2 −0 |
.squad/hooks/tests/run-tests.ps1 |
+200 −0 |
.squad/hooks/tests/run-tests.sh |
+150 −0 |
.squad/routing.md |
+2 −1 |
.squad/templates/orchestration-log.md |
+27 −0 |
Total: +2021 −20
This check runs automatically on every push. Fix any ❌ items and push again.
See CONTRIBUTING.md and PR Requirements for details.
Add the missing changeset for the dispatch-enforcement PR so the changelog gate recognizes the governed coordinator/template changes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR introduces a three-layer “dispatch enforcement” system to prevent the Squad coordinator from performing domain work inline, by (A) restricting coordinator tool access, (B) adding DispatchGuard audit scripts + fixtures to mechanically detect violations, and (C) strengthening coordinator contract wording and routing guidance.
Changes:
- Add DispatchGuard audit implementations (
dispatch-audit.ps1/dispatch-audit.sh) plus parity test runners and JSONL fixtures. - Update Squad coordinator prompt + project instructions to reinforce dispatch-only behavior and routing guardrails.
- Update Scribe/Ralph charters and orchestration log templates/docs to describe the DispatchGuard ledger/verdict workflow.
Show a summary per file
| File | Description |
|---|---|
| .squad/templates/orchestration-log.md | Adds DispatchGuard ledger/verdict schema documentation. |
| .squad/routing.md | Extends routing principles with DispatchGuard notes. |
| .squad/hooks/tests/run-tests.sh | Adds bash parity test runner for audit script outputs. |
| .squad/hooks/tests/run-tests.ps1 | Adds PowerShell parity test runner for audit script outputs. |
| .squad/hooks/tests/mixed-with-verdicts.jsonl | Adds fixture mixing coordinator turns and audit verdict records. |
| .squad/hooks/tests/malformed.jsonl | Adds fixture containing malformed JSON + a valid turn record. |
| .squad/hooks/tests/empty.jsonl | Adds empty-ledger fixture. |
| .squad/hooks/tests/criterion3-triggered.jsonl | Adds fixture to trigger inline-hallucination criterion. |
| .squad/hooks/tests/criterion2-triggered.jsonl | Adds fixture to trigger dispatch-drift criterion. |
| .squad/hooks/tests/criterion1-triggered.jsonl | Adds fixture to trigger wrote-without-dispatch criterion. |
| .squad/hooks/tests/compliant.jsonl | Adds compliant fixture (no criteria triggered). |
| .squad/hooks/README.md | Documents platform usage, invocation, output, and parity testing. |
| .squad/hooks/dispatch-audit.sh | Adds bash implementation of DispatchGuard audit logic. |
| .squad/hooks/dispatch-audit.ps1 | Adds PowerShell implementation of DispatchGuard audit logic. |
| .squad/decisions/inbox/dispatch-enforcement-decision.md | Adds decision record describing enforcement layers and limitations. |
| .squad/config.json | Adds dispatchEnforcement: "warn" configuration. |
| .squad/agents/scribe/charter.md | Adds DispatchGuard responsibilities and audit loop spec to Scribe. |
| .squad/agents/ralph/charter.md | Expands Ralph charter; adds DispatchGuard verdict consumption behavior. |
| .gitignore | Ignores DispatchGuard runtime log directory under .squad/orchestration-log/. |
| .github/instructions/squad-routing-guard.instructions.md | Adds routing decision tree for generic Copilot sessions. |
| .github/copilot-instructions.md | Adds identity lock, routing guard reference, and injection resistance guidance. |
| .github/agents/squad.agent.md | Restricts coordinator tools (Layer A) and adds stronger dispatch contract wording (Layer C). |
| .changeset/fix-dispatch-enforcement-policy-gate.md | Adds changeset entry describing the dispatch-enforcement governance changes. |
Review details
Suppressed comments (1)
.squad/hooks/README.md:55
- Same path mismatch as the PowerShell example above: this Bash example points at
.squad/orchestration-log/ledger-...jsonl, but the DispatchGuard ledger path in this PR is under.squad/orchestration-log/dispatchguard/.
```bash
# Warn mode (default)
./dispatch-audit.sh --ledger-path .squad/orchestration-log/ledger-sess-8841.jsonl --mode warn
- Files reviewed: 21/23 changed files
- Comments generated: 12
- Review effort level: Lite
|
|
||
| 1. **Eager by default** — spawn agents who could usefully start work, including anticipatory downstream work. | ||
| 2. **Scribe always runs** after substantial work, always as `mode: "background"`. Never blocks. | ||
| 2. **Scribe always runs** after substantial work, always as `mode: "background"`. Never blocks. Also spawned in **DispatchGuard mode** at every session start (see `squad.agent.md` §Session Init) to mechanically audit coordinator turns for dispatch compliance. |
|
|
||
| ## DispatchGuard | ||
|
|
||
| **Scribe is the mechanical audit engine for dispatch compliance.** When spawned in DispatchGuard mode (see `### Session Init — DispatchGuard Auto-Bootstrap` in `squad.agent.md`), Scribe reads the session's ledger and audits each coordinator turn against the dispatch contract. |
| "session_id": "string — Copilot session ID (from spawn prompt or environment)", | ||
| "timestamp": "ISO-8601 UTC datetime of the turn", | ||
| "mode": "Direct | Lightweight | Standard | Full", | ||
| "task_calls_since_last_turn": 0, // integer — count of task/runSubagent/create_session calls |
| ```powershell | ||
| # Warn mode (default) | ||
| .\dispatch-audit.ps1 -LedgerPath ".squad/orchestration-log/ledger-sess-8841.jsonl" -Mode warn | ||
|
|
| specialist agent via `task` / `runSubagent` / `create_session`. | ||
|
|
||
| The script reads the session's ledger at `.squad/orchestration-log/ledger-{session-id}.jsonl` |
| **Bootstrap spawn (fire once per session, in the acknowledgment turn):** | ||
| ``` | ||
| task: | ||
| name: scribe | ||
| agent_type: general-purpose | ||
| description: "Scribe running DispatchGuard mechanical audit for this session" | ||
| mode: background | ||
| prompt: | | ||
| You are Scribe. Read .squad/agents/scribe/charter.md — specifically the DispatchGuard section. |
| 'block' { | ||
| $verdictStr = 'block' | ||
| $wouldBlock = $false | ||
| $exitCode = 1 | ||
| $recommendedAction = "Dispatch contract violation(s) detected ($names). Enforcement mode is 'block' — abort this turn and require the coordinator to dispatch to the correct specialist." |
| block) | ||
| verdict_str="block" | ||
| would_block="false" | ||
| exit_code=1 | ||
| recommended_action="Dispatch contract violation(s) detected ($names_str). Enforcement mode is 'block' — abort this turn and require the coordinator to dispatch to the correct specialist." | ||
| ;; |
| # NOTE ON KNOWN PLATFORM DIFFERENCES (not bugs): | ||
| # * turn_snapshot.timestamp: PowerShell auto-parses ISO 8601 strings into DateTime | ||
| # objects and re-formats them in locale format ("07/26/2026 20:00:00"). | ||
| # The bash implementation preserves the original ISO string. This is a PS1 | ||
| # quirk, not intentional -- the enforcement decision is unaffected. | ||
| # * recommended_action: contains the --ledger-path argument verbatim; paths | ||
| # differ between WSL Linux paths and Windows paths. Non-functional. |
| # Semantic comparison: compare verdict, would_block, triggered criteria (id+name), flags (sorted) | ||
| # Deliberately excludes turn_snapshot.timestamp (PS1 localizes ISO dates) and | ||
| # recommended_action (contains OS-specific paths). These are known platform differences. | ||
| function Get-SemanticKey([string]$json) { |
Add dispatch enforcement (Layer A + B + C) to prevent coordinator from doing domain work inline
Motivation
Fixes issue #1498 (Coordinator can bypass agent dispatch). The Squad coordinator has been observed repeatedly drifting into inline domain work: writing code, generating PR bodies, running git commands, and producing analysis directly — all without dispatching to a specialist agent. This is a fundamental contract violation: the coordinator ROUTES, it does not BUILD.
Three complementary enforcement layers prevent this. All three were empirically validated in the
tamresearch1worktree before this PR.Design Summary
Layer A — Coordinator Tool Profile Restriction (physical enforcement)
Applied via a
tools:allowlist in the coordinator's frontmatter (.github/agents/squad.agent.md):Effect: The Copilot runtime physically blocks any tool call outside this list with a hard error. No behavioral instruction needed — enforcement is mechanical. The coordinator can dispatch via
agentbut cannotcreate,edit, or use any write tool directly.Layer B — Scribe DispatchGuard Mechanical Audit (observability)
Scribe is spawned in DispatchGuard mode at every session start (mandatory bootstrap). It reads the coordinator's turn ledger and audits each turn against three violation criteria via
.squad/hooks/dispatch-audit.ps1/.squad/hooks/dispatch-audit.sh:wrote-without-dispatchwrite_tools_used.length > 0ANDtask_calls_since_last_turn == 0dispatch-drifttask_calls_since_last_turn == 0(mode ≠ Direct)inline-hallucinationdomain_artifact_declared.value == trueVerdicts are appended to a per-session JSONL file consumed by Ralph, which alerts the coordinator on
warn/blockverdicts.Layer C v2 — Dispatch Contract Wording (behavioral reinforcement)
The coordinator prompt (
squad.agent.md) now includes:.squad/decisions/inbox/*.mdonly)Empirical Findings
Tested in
tamresearch1worktree, 2026-07-27 (Ralph E2E report).Test 1 — 3 turns (analyze → propose → apply):
Session ID for Test 1:
d3fd4c46-1a95-4f40-8e8a-a585a0af9622Verbatim tool-block errors confirming Layer A enforcement:
These appeared in all 3 Test 1 turns, always at the Scribe maintenance phase. No other tool-block errors observed.
Test 2 — meta-recursive (external repo without squad routing labels):
squad:*label on external repo)agenttoolMeta-Gap Disclosure
Layer A and Layer B are mutually incompatible in Phase 1 (accepted trade-off):
Because
createandeditare blocked at the coordinator level by Layer A, the DispatchGuard ledger (Layer B) cannot be written by the coordinator itself. Runningdispatch-audit.ps1against a real session returns"verdict": "indeterminate", "would_block": true— per Q Recommendation #6: unverifiable compliance ≠ free pass.This is an accepted consequence. In Phase 1, Layer A's physical tool-block errors ARE the enforcement evidence. Layer B becomes opt-in observability when Layer A is off, or when sub-agents (which are not restricted) write the ledger on behalf of the coordinator.
greptool is an unintended casualty of Layer A: The allowlist containsreadandsearchbut notgrep(the CLI-native search tool). Scribe and sub-agents usegrepfrom their own (unrestricted) sessions. A future iteration should decide whether to addgrepto the coordinator allowlist.Coverage Gap Disclosure
Layer A only applies when the coordinator is explicitly invoked via the
agenttool. External repo issues withoutsquad:*routing labels don't trigger the Squad coordinator → Layer A doesn't apply. Test 2 confirmed this gap; it is acknowledged and documented, not a regression.Files Added/Modified
Layer B infrastructure:
.squad/config.json— addeddispatchEnforcement: "warn".squad/agents/scribe/charter.md— Tool Access section + DispatchGuard audit loop spec.squad/agents/ralph/charter.md— DispatchGuard Verdict Consumer section + Skills listing.squad/hooks/dispatch-audit.ps1— PowerShell 7+ audit script (canonical implementation).squad/hooks/dispatch-audit.sh— bash port (jq≥ 1.6; parity-verified against.ps1).squad/hooks/README.md— platform guide (prerequisites, invocation, output schema, parity tests).squad/hooks/tests/— 7 JSONL fixtures + PowerShell and bash parity test runners.squad/routing.md— Routing Principles extended with DispatchGuard notes.squad/templates/orchestration-log.md— DispatchGuard ledger schema + verdicts file specLayer A + C:
.github/agents/squad.agent.md—tools:allowlist frontmatter + Layer C v2 body prose (Team Mode opening, Session Init DispatchGuard Auto-Bootstrap, Direct-Mode whitelist + Domain-Artifact rule + Anti-pattern prohibition in Response Mode Selection)Docs:
.github/copilot-instructions.md— Identity lock + routing guard reference + adversarial input handling.github/instructions/squad-routing-guard.instructions.md— NEW: explicit routing decision tree for generic Copilot sessionsChore:
.gitignore— added.squad/orchestration-log/dispatchguard/Decision record:
.squad/decisions/inbox/dispatch-enforcement-decision.md— decision record (will be merged by Scribe)How to Test
Audit script smoke test
# Linux/macOS (bash + jq ≥ 1.6) ./squad/hooks/dispatch-audit.sh --ledger-path .squad/hooks/tests/compliant.jsonl --mode warnParity tests (validates ps1/sh produce identical verdicts)
.\.squad\hooks\tests\run-tests.ps1Layer A verification
With
squad.agent.mdin this PR applied, attempt any write tool call through the coordinator and observe the hard error:Unknown tool name in the tool allowlist: "create".Relation to PR #1529
This PR is standalone and does NOT stack on PR #1529 (compression-only). Per Q's recommendation, dispatch enforcement and coordinator compression are orthogonal concerns and land separately to simplify review and revert paths.
Residual Attack Surface (Phase 3 gaps)
Q's review identified A1-A12 residual attack vectors not addressed by this PR. These are documented for Phase 3 work:
runSubagentdirect domain invocation (VS Code client bypass)readto compose a domain artifact mentally then "dictating" itEmpirical validation: Ralph E2E report, 2026-07-27 | Decision: tamresearch1/dispatch-enforcement
.squad/decisions.mdCloses #1498