Repository navigation
Description
Summary
The coordinator's directive-capture flow and the sub-agent decision-recording flow are hard-coded in squad.agent.md to write directly to .squad/decisions/inbox/{name}-{slug}.md via squad_state_write. This bypasses the LocalMemoryStore classifier — the FORBIDDEN scan, classification, audit log, and loadGuidance machinery shipped in 0.10.x never run for the most common write paths.
The recently-opened PR to bridge memory.* tools through the squad_state MCP server makes those tools REACHABLE from agents. But the instructions in squad.agent.md still tell agents to use the drop-box pattern, so the governed pipeline stays unused even with the bridge in place.
This issue asks maintainers to decide whether the canonical write path for directives and decisions should route through memory.write instead.
Evidence — what the spawn prompt says today
Coordinator directive-capture (from .github/agents/squad.agent.md, line 260):
Prefer
squad_state_writeto writedecisions/inbox/copilot-directive-{timestamp}.md
Sub-agent decision-recording (lines 406 and 531):
Agents record decisions with
squad_decideorsquad_state_writetodecisions/inbox/{agent-name}-{brief-slug}.md.
Both instructions hard-code the drop-box pattern. Neither mentions memory.classify or memory.write.
The governed tools DO exist (packages/squad-sdk/src/tools/index.ts, lines 814/846/1189/1190): memory.classify and memory.write are registered alongside squad_state_write (line 667) — but the spawn prompt never routes to them.
Evidence — what actually happens (verified empirically)
I installed a workaround skill at .copilot/skills/governed-memory-cli-bridge/SKILL.md that teaches agents to invoke squad memory write --class POLICY via shell for durable team policies. Then I ran a fresh squad init repo through a real prompt that requires both directive capture and sub-agent work:
"First, cast a small 3-person team. Then have the Backend dev research the project and record their findings as a durable team policy that future agents will need."
20-minute end-to-end run. Result:
| Signal | Value |
|---|---|
.squad/memory/audit.jsonl entries |
0 |
.squad/memory/index.json |
[] |
.squad/memory/local/ files |
0 |
.squad/memory/policy-inbox/ files |
0 |
Output mentions of squad memory <op> |
0 (in 8.5KB of stdout) |
| Output mentions of the skill by name | 0 |
| Where Tuvok's finding actually landed | .squad/decisions/inbox/tuvok-project-stack-baseline.md (3.3KB), then merged into .squad/decisions.md by Scribe — pure drop-box flow |
The skill was physically present in the right directory the whole time. The coordinator AND the spawned sub-agent both followed the explicit drop-box instruction in their spawn prompt — neither path ever asked the classifier.
Why this matters
Without a change here, the governance benefits of 0.10.x's memory work — classification, audit trail, FORBIDDEN scan, loadGuidance — apply only to:
- Manual CLI invocations by the human operator (
squad memory write ...at a terminal) - Direct SDK consumers
- Custom skills that explicitly call the CLI AND are explicitly invoked by name
They do NOT apply to:
- The default directive-capture path (coordinator →
decisions/inbox/) - The default sub-agent decision-recording path (sub-agent →
decisions/inbox/) - Anything that follows the conventions baked into
squad.agent.md
In other words: a fresh squad with v0.10.x does not exercise the governance subsystem, even though it's shipped and tested.
Three concrete options for the maintainers
Option A — Update squad.agent.md to route through memory.write
Replace both instructions:
- Directive capture:
squad_state_write → decisions/inbox/copilot-directive-{ts}.md→memory.write --class POLICY --author coordinator - Sub-agent decision-recording:
squad_state_write → decisions/inbox/{agent}-{slug}.md→memory.write --class DECISION --author {agent}
Pre-condition: the memory_write MCP alias must ship first so agents can actually call the tool. Until then, fall back to the drop-box pattern with a TODO comment.
Pros: every directive + decision gets classified, audited, and assigned a loadGuidance automatically. Forbidden scan catches secrets and CI-status patterns before they land in decisions.md. The 0.10.x governance work becomes operational, not aspirational.
Cons: requires the MCP bridge first. Behavior change for every existing squad on the next upgrade.
Option B — Keep the drop-box as the WRITE path, run classification ASYNCHRONOUSLY in Scribe's merge step
Sub-agents continue writing to decisions/inbox/. When Scribe processes the inbox, it runs each entry through LocalMemoryStore.classify() before merging into decisions.md. Rejected entries get audit-logged + moved to a quarantine folder.
Pros: no change to spawn templates. Defense-in-depth: even if an agent leaks something forbidden into the inbox, Scribe catches it before it lands in decisions.md.
Cons: classification happens after the write. Agents don't get the dry-run / forbidden-feedback signal. The audit log shows what was classified, not what was attempted.
Option C — Hybrid: keep the drop-box for the write, but require the inbox file to carry classification metadata
Sub-agents write to the inbox AND embed a classification result (from a preceding memory.classify call) as frontmatter. Scribe trusts the embedded classification when merging; agents that omit it get rejected at merge time.
Pros: agents have to engage with the classifier (incentive to use it). Backwards compatible during transition.
Cons: complicated. Two-step protocol. Easy for agents to forget.
What's NOT being proposed
- Removing the drop-box pattern (it's important for the Scribe-merge model)
- Changing the state backend (
squad_state_writestill works the same way at the storage level) - Forcing every memory tool through MCP (some operations are still terminal-only by design)
Related issues + PRs
- The PR bridging
memory.*tools throughsquad_stateMCP server — precondition for Option A - This issue — directive capture + sub-agent decision recording still bypass the classifier
Acceptance criteria
- Maintainer comment picking A, B, C, or D (an option I missed)
- If A or C: design discussion on backwards compatibility before any code change
- If B: design discussion on what classification rejection means for an already-written inbox file
- Whatever direction is picked,
squad.agent.mdis updated to match so a freshsquad initproduces a squad that actually exercises the governance subsystem in normal operation
Empirical reproduction
To reproduce the finding:
# Set up clean repo with v0.10.0
mkdir test-squad; cd test-squad
git init -q
git config user.name "t"; git config user.email "t@e.com"
"# x" > README.md; git add README.md; git commit -q -m "init"
squad init
# Install the workaround skill
mkdir -Force .copilot/skills/governed-memory-cli-bridge
# (add SKILL.md teaching agents to call: squad memory write --class POLICY)
# Run a real prompt non-interactively
copilot -p "Cast a 3-agent team. Have the Backend dev research the project and record findings as a durable team policy." `
--agent squad --allow-all-tools --no-color > run.log
# Check governance signals (all will be zero / empty)
Get-Content .squad/memory/audit.jsonl 2>$null | Measure-Object | Select-Object Count
Get-Content .squad/memory/index.json 2>$null
Get-ChildItem .squad/memory/local 2>$null | Measure-Object | Select-Object Count
Get-ChildItem .squad/memory/policy-inbox 2>$null | Measure-Object | Select-Object CountAll four checks return zero / empty. The agents wrote to .squad/decisions/inbox/ and .squad/decisions.md instead — via squad_state_write, bypassing the classifier.
Activity
tamirdresher commentedon Jun 9, 2026
Empirical re-test of the combined fix (#1246 + #1245)
I patched a local Squad install with both proposed fixes and re-ran a clean directive-capture flow. Findings:
What the combined fix does NOT solve
In the typical bootstrap state (workspace-level
.mcp.jsononly, no user-level squad_state MCP):.github/agents/squad.agent.mdunchangedCreate/Edit, forgesaudit.jsonlentry.github/agents/squad.agent.mdpatched to requirememory_write(this issue's proposal)"memory_write tool unavailable"and proceeds to fabricate an audit entryIn other words, telling the agent "REQUIRED: call memory_write with class POLICY" via squad.agent.md is not enough when copilot CLI hasn't loaded the MCP. The agent gracefully degrades and silently invents the data.
When the combined fix DOES work
Once
squad_stateis registered at user level (copilot mcp add squad_state -- node ... state-mcp), the same agent on the same repo with the same prompt:squad_state_healthfirst ✅memory_classify✅memory_write✅ (real provider, persistent index entry, proper audit row)squad_state_write✅ (drop-box for Scribe).squad/decisions.mdor.squad/decisions/inbox/with platform toolsSo the squad.agent.md patches are necessary for guiding tool choice, but they don't override the agent's silent-fallback behavior when the MCP simply isn't there.
Forged-audit problem
Even worse — when the agent thinks MCP isn't available but it sees a directive to record a POLICY, it writes raw JSON into
.squad/memory/audit.jsonlvia PowerShell that looks like an SDK audit entry. Example actually captured:Different field names (
ts/timestamp,op/action), noid/path/reason/provider. Downstream tooling that readsaudit.jsonlwould see both as "POLICY writes" without realizing one is real and one is theater.Suggested updates to this issue's scope
squad.agent.mdshould forbid falling back to platform writes for.squad/decisions/*and.squad/memory/*when MCP isn't reachable. The right answer is to fail loudly, not to fabricate.squad_stateat user level (or at least warn) — workspace-level alone doesn't load reliably in non-interactive-pmode. (Tracking separately.)Verified locally on Windows / Node 24.16 /
@bradygaster/squad-cliv0.10.0-build.7 with both fixes applied. Reproducible across 4 clean test runs.fix: expose memory tools via MCP + route writes through governed pipe…
chore: promote v0.10.1 dev → main + v0.10 stabilisation blog post (#1313
chore: promote dev → main (v0.11.0) (#1422)