Skip to content

Coordinator does not probe for squad_state/memory tools — silently uses raw file ops on non-local state backends #1305

Closed

Description

@tamirdresher

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 running tool_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 saw stateBackend: two-layer in config.json, I should have stopped and confirmed the runtime tools existed."

Root cause (two failure modes stacked)

  1. 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.json has squad_state registered with tools: ["*"], server responds correctly to initialize and tools/list via JSON-RPC, but model's tool block doesn't list them upfront.

  2. Behavioral (squad.agent.md is too soft): squad.agent.md says "When memory tools are available, use them before writing durable memory by hand" and "If memory.write is not available, fall back to squad_decide or squad_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):

  1. Read .squad/config.json. If stateBackend is absent or "local"/"worktree", skip the rest of this step — file ops on .squad/ are valid for these backends.
  2. If stateBackend is "two-layer", "orphan", or "git-notes": probe for squad_state_health via tool_search_tool_regex (or any model-equivalent tool-discovery mechanism). If unavailable, halt — output: "Squad's runtime state bridge is missing for backend {name}. The squad_state MCP server is not advertised in this session. Restart Copilot CLI with .mcp.json loaded, or set stateBackend to local in .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 the squad_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 using create/edit/write_file tools. 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)

  1. Reinforce the same rules in .squad/skills/init-mode/SKILL.md and .squad/skills/agent-conduct/SKILL.md so even a stale squad.agent.md install gets the right behavior via the skill loader.

Acceptance criteria

  • A fresh squad init --state-backend two-layer + copilot --agent squad session, opened with the upgraded coordinator, MUST attempt to probe for squad_state_* tools before writing any state file. If unavailable, MUST halt and tell the user.
  • Test by stripping squad_state from .mcp.json and verifying the coordinator refuses to write decisions.md instead of trying.

Activity

  1. added 2 commits that reference this issue on Jun 13, 2026
    b44c798
    596a1c9
  2. added a commit that references this issue on Jun 13, 2026
    918cbd4
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions