Description
Symptom
A coordinator session was opened against a Squad with stateBackend: "two-layer". The coordinator wrote .squad/decisions.md, four .squad/agents/{name}/history.md files, and a .squad/decisions/inbox/copilot-directive-…md file using raw create/edit tools — never calling any squad_state_* or memory.* tool. The pre-commit hook caught the contract violation; the agent treated it as a "git problem" instead of recognizing it as the symptom it was.
Self-analysis from the agent that did it (verbatim):
"The tools weren't visible in my default function list. My session loaded with
powershell,view,create,edit,grep,glob,task,store_memory,vote_memory, etc. — the squad MCP tools (squad_state_*) were registered but not advertised in the tool definitions block at the top of my prompt. I only found them just now by runningtool_search_tool_regex.""I interpreted 'not available' [in
squad.agent.md] as 'not in my visible tool list' instead of proactively probing. The instant I sawstateBackend: two-layerinconfig.json, I should have stopped and confirmed the runtime tools existed."
Root cause (two failure modes stacked)
-
Mechanical (Copilot CLI behavior): Copilot CLI loads MCP server tools but doesn't preload them all into the model's initial function list — they're behind
tool_search_tool_regex-style lazy discovery. Confirmed empirically:.mcp.jsonhassquad_stateregistered withtools: ["*"], server responds correctly toinitializeandtools/listvia JSON-RPC, but model's tool block doesn't list them upfront. -
Behavioral (squad.agent.md is too soft):
squad.agent.mdsays "When memory tools are available, use them before writing durable memory by hand" and "If memory.write is not available, fall back tosquad_decideorsquad_state_write". The conditional"available"is ambiguous — models interpret it as "if listed in my tool prompt" instead of "after probing to find out". There's no hard refusal clause for the case where the agent is about to violate the state-backend contract.
Why this matters for non-local backends
For stateBackend: "two-layer", orphan, or git-notes, mutable state files are NOT on disk in the working tree — they live in the orphan branch / git notes / external store. Writing to .squad/decisions.md via create/edit either:
- Hits the pre-commit hook and fails the user
- Or worse, succeeds locally and creates phantom state that the runtime overwrites at next read
This is a contract violation that the agent cannot recover from without manual cleanup. The wrong-tool problem must be prevented at the prompt level, not detected at commit time.
Proposed fix
Three layers (one is out of our control; two we can ship):
1. Session-init probe step in squad.agent.md (in scope)
After resolving STATE_BACKEND, the coordinator MUST probe for squad_state_* availability. Pseudo-procedure inserted near the top of the Coordinator Identity section:
State-backend handshake (run on every session, before any state mutation):
- Read
.squad/config.json. IfstateBackendis absent or"local"/"worktree", skip the rest of this step — file ops on.squad/are valid for these backends.- If
stateBackendis"two-layer","orphan", or"git-notes": probe forsquad_state_healthviatool_search_tool_regex(or any model-equivalent tool-discovery mechanism). If unavailable, halt — output: "Squad's runtime state bridge is missing for backend{name}. Thesquad_stateMCP server is not advertised in this session. Restart Copilot CLI with.mcp.jsonloaded, or setstateBackendtolocalin.squad/config.json." — and stop until the user acknowledges.
2. Hard refusal clause in the State Backend section (in scope)
Hard rule: If
stateBackend ∈ {two-layer, orphan, git-notes}and thesquad_state_*tools are not loaded, you MUST NOT write to.squad/decisions.md,.squad/decisions/inbox/,.squad/agents/*/history.md,.squad/casting/*.json,.squad/identity/*.md, or any.squad/memory/*path usingcreate/edit/write_filetools. Report the missing bridge instead. This is a contract violation; the runtime owns persistence for these backends.
3. Auto-advertise MCP tools at handshake (OUT OF SCOPE for Squad — file as Copilot CLI feature request)
This is the only complete fix for the mechanical root cause, but it's a Copilot CLI architectural decision (lazy-discovery vs. preload). Out of Squad's control. Will file as a separate request against github/copilot-cli if maintainers agree it's worth it. With #1 + #2 in place, this becomes a nice-to-have rather than load-bearing.
Also worth doing (small, in scope)
- Reinforce the same rules in
.squad/skills/init-mode/SKILL.mdand.squad/skills/agent-conduct/SKILL.mdso even a stalesquad.agent.mdinstall gets the right behavior via the skill loader.
Acceptance criteria
- A fresh
squad init --state-backend two-layer+copilot --agent squadsession, opened with the upgraded coordinator, MUST attempt to probe forsquad_state_*tools before writing any state file. If unavailable, MUST halt and tell the user. - Test by stripping
squad_statefrom.mcp.jsonand verifying the coordinator refuses to writedecisions.mdinstead of trying.
Activity
Merge PR github#1306: state-backend handshake + hard refusal (closes g…
Merge PR github#1306: state-backend handshake + hard refusal (closes g…
fix(prompt): coordinator must probe for squad_state/memory tools befo…