Skip to content

Add dispatch enforcement (Layer A + B + C) to prevent coordinator from doing domain work inline - #1537

Draft
tamirdresher wants to merge 5 commits into
bradygaster:devfrom
tamirdresher:squad/dispatch-enforcement
Draft

Add dispatch enforcement (Layer A + B + C) to prevent coordinator from doing domain work inline#1537
tamirdresher wants to merge 5 commits into
bradygaster:devfrom
tamirdresher:squad/dispatch-enforcement

Conversation

@tamirdresher

Copy link
Copy Markdown
Collaborator

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 tamresearch1 worktree 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):

tools:
  - agent      # dispatch tool — the coordinator's primary output
  - read       # context reads
  - search     # context searches
  - skill      # skill loading
  - squad_state/*          # state bridge (MCP)
  - squad_state_c3c25b85/* # state bridge variant
  - squad_state_e7f10a1f/* # state bridge variant
  - github-mcp-server/*    # GitHub context reads

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 agent but cannot create, 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:

Criterion Condition
wrote-without-dispatch write_tools_used.length > 0 AND task_calls_since_last_turn == 0
dispatch-drift Last 3 turns all have task_calls_since_last_turn == 0 (mode ≠ Direct)
inline-hallucination mode == Direct AND domain_artifact_declared.value == true

Verdicts are appended to a per-session JSONL file consumed by Ralph, which alerts the coordinator on warn/block verdicts.

Layer C v2 — Dispatch Contract Wording (behavioral reinforcement)

The coordinator prompt (squad.agent.md) now includes:

  • An exhaustive Direct-Mode whitelist (5 cases where inline output is permitted)
  • The Domain-Artifact rule (everything not on the whitelist must dispatch)
  • The Narrow inbox exemption (≤500-word .squad/decisions/inbox/*.md only)
  • Verb triggers (explicit list requiring dispatch when paired with domain-artifact objects)
  • The Read-Only Probe Budget (max 2 reads before dispatch is required)
  • Anti-pattern prohibition (enumerated rationalizations explicitly blocked)
  • Session Init DispatchGuard Auto-Bootstrap (mandatory Scribe spawn every session)
  • Bootstrap Verification (coordinator must confirm Scribe DispatchGuard is live)

Empirical Findings

Tested in tamresearch1 worktree, 2026-07-27 (Ralph E2E report).

Test 1 — 3 turns (analyze → propose → apply):

Turn Verb Pre-Layer-C Layer-C only Layer-A+B+C Result
1 analyze drift (inline gh+git) drift DISPATCHED (→Worf) ✅ Fixed
2 propose drift drift DISPATCHED (→Worf+BElanna) ✅ Fixed
3 apply dispatched (pre-C) drift REGRESSED (30+ min inline) DISPATCHED ✅ Regression reversed

Session ID for Test 1: d3fd4c46-1a95-4f40-8e8a-a585a0af9622

Verbatim tool-block errors confirming Layer A enforcement:

● Unknown tool name in the tool allowlist: "create"
● Unknown tool name in the tool allowlist: "edit"
● Unknown tool name in the tool allowlist: "grep"

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):

  • Layer A doesn't apply — SquadShort coordinator was not invoked (no squad:* label on external repo)
  • Coverage gap: Layer A only takes effect when the coordinator is explicitly invoked via the agent tool

Meta-Gap Disclosure

Layer A and Layer B are mutually incompatible in Phase 1 (accepted trade-off):

Because create and edit are blocked at the coordinator level by Layer A, the DispatchGuard ledger (Layer B) cannot be written by the coordinator itself. Running dispatch-audit.ps1 against 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.

grep tool is an unintended casualty of Layer A: The allowlist contains read and search but not grep (the CLI-native search tool). Scribe and sub-agents use grep from their own (unrestricted) sessions. A future iteration should decide whether to add grep to the coordinator allowlist.


Coverage Gap Disclosure

Layer A only applies when the coordinator is explicitly invoked via the agent tool. External repo issues without squad:* 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 — added dispatchEnforcement: "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 spec

Layer A + C:

  • .github/agents/squad.agent.mdtools: 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 sessions

Chore:

  • .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

# Windows (PowerShell 7+)
cd <repo-root>
.\.squad\hooks\dispatch-audit.ps1 -LedgerPath .squad\hooks\tests\compliant.jsonl -Mode warn -SessionId test
# Expected: {"verdict":"ok","would_block":false,...}

.\.squad\hooks\dispatch-audit.ps1 -LedgerPath .squad\hooks\tests\criterion1-triggered.jsonl -Mode warn
# Expected: {"verdict":"warn","triggered_criteria":[{"id":1,"name":"wrote-without-dispatch",...}],...}
# Linux/macOS (bash + jq ≥ 1.6)
./squad/hooks/dispatch-audit.sh --ledger-path .squad/hooks/tests/compliant.jsonl --mode warn

Parity tests (validates ps1/sh produce identical verdicts)

.\.squad\hooks\tests\run-tests.ps1
bash .squad/hooks/tests/run-tests.sh

Layer A verification

With squad.agent.md in 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:

  • A1: runSubagent direct domain invocation (VS Code client bypass)
  • A2: Sub-agent inheritance of coordinator's blocked tools (not an issue — sub-agents get full tools)
  • A3: Coordinator using read to compose a domain artifact mentally then "dictating" it
  • A4: Bootstrap omission (addressed by mandatory bootstrap + verification in Layer C v2)
  • A5-A12: Various social-engineering and prompt-injection vectors documented in decisions.md

Empirical validation: Ralph E2E report, 2026-07-27 | Decision: tamresearch1/dispatch-enforcement .squad/decisions.md

tamirdresher and others added 5 commits July 27, 2026 19:38
…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
@github-actions

Copy link
Copy Markdown
Contributor

🟠 Impact Analysis — PR #1537

Risk tier: 🟠 HIGH

📊 Summary

Metric Count
Files changed 22
Files added 14
Files modified 8
Files deleted 0
Modules touched 3

🎯 Risk Factors

  • 22 files changed (21-50 → HIGH)
  • 3 modules touched (2-4 → MEDIUM)

📦 Modules Affected

ci-workflows (3 files)
  • .github/agents/squad.agent.md
  • .github/copilot-instructions.md
  • .github/instructions/squad-routing-guard.instructions.md
root (1 file)
  • .gitignore
squad-state (18 files)
  • .squad/agents/ralph/charter.md
  • .squad/agents/scribe/charter.md
  • .squad/config.json
  • .squad/decisions/inbox/dispatch-enforcement-decision.md
  • .squad/hooks/README.md
  • .squad/hooks/dispatch-audit.ps1
  • .squad/hooks/dispatch-audit.sh
  • .squad/hooks/tests/compliant.jsonl
  • .squad/hooks/tests/criterion1-triggered.jsonl
  • .squad/hooks/tests/criterion2-triggered.jsonl
  • .squad/hooks/tests/criterion3-triggered.jsonl
  • .squad/hooks/tests/empty.jsonl
  • .squad/hooks/tests/malformed.jsonl
  • .squad/hooks/tests/mixed-with-verdicts.jsonl
  • .squad/hooks/tests/run-tests.ps1
  • .squad/hooks/tests/run-tests.sh
  • .squad/routing.md
  • .squad/templates/orchestration-log.md

This report is generated automatically for every PR. See #733 for details.

@github-actions

Copy link
Copy Markdown
Contributor

🏗️ Architectural Review

⚠️ Architectural review: 1 warning(s).

Severity Category Finding Files
🟡 warning sweeping-refactor This PR touches 22 files (22 modified/added, 0 deleted). Large PRs are harder to review — consider splitting if possible.

Automated architectural review — informational only.

@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Squad File Leakage Detected

The following .squad/ files were modified in this PR:

  • .squad/agents/ralph/charter.md
  • .squad/agents/scribe/charter.md
  • .squad/config.json
  • .squad/decisions/inbox/dispatch-enforcement-decision.md
  • .squad/hooks/README.md
  • .squad/hooks/dispatch-audit.ps1
  • .squad/hooks/dispatch-audit.sh
  • .squad/hooks/tests/compliant.jsonl
  • .squad/hooks/tests/criterion1-triggered.jsonl
  • .squad/hooks/tests/criterion2-triggered.jsonl
  • .squad/hooks/tests/criterion3-triggered.jsonl
  • .squad/hooks/tests/empty.jsonl
  • .squad/hooks/tests/malformed.jsonl
  • .squad/hooks/tests/mixed-with-verdicts.jsonl
  • .squad/hooks/tests/run-tests.ps1
  • .squad/hooks/tests/run-tests.sh
  • .squad/routing.md
  • .squad/templates/orchestration-log.md

These files affect team routing, agent charters, and decisions.
If intentional, ensure approval from the team lead.

@github-actions

Copy link
Copy Markdown
Contributor

🛫 PR Readiness Check

ℹ️ This comment updates on each push. Last checked: commit c69720d

PR Scope: 🔧 Infrastructure

⚠️ 4 item(s) to address before review

Status Check Details
Single commit 5 commits — consider squashing before review
Not in draft PR is still in draft — mark as ready for review when done
Branch up to date Up to date with dev
Copilot review No Copilot review yet — it may still be processing
Changeset present No source files changed — changeset not required
Scope clean ⚠️ PR includes 18 .squad/ file(s) — ensure these are intentional
No merge conflicts No merge conflicts
Copilot threads resolved No Copilot review threads
CI passing 8 check(s) still running

Files Changed (22 files, +2013 −20)

File +/−
.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: +2013 −20


This check runs automatically on every push. Fix any ❌ items and push again.
See CONTRIBUTING.md and PR Requirements for details.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant