Clarify sub-agent reply routing in MCP tool descriptions

A sub-agent's reply to send_message lands in the caller's own pane
tagged [sub-agent:<name>], not in the sub-agent's output. The
descriptions for wait_for_pattern, send_message, both
timer_fire_when_idle_*, and the server-instructions preamble now
spell this out, along with the canonical send_message →
timer_fire_when_idle_any → read-own-pane pattern. help('readiness')
and help('coordination') updated to match. Previously agents reached
for wait_for_pattern on the sub-agent and deadlocked until timeout
because the reply had already been delivered to their own pane.
This commit is contained in:
2026-05-15 16:08:07 +01:00
parent b05065a601
commit 0c960fa859
3 changed files with 30 additions and 11 deletions

View File

@@ -1134,9 +1134,10 @@ func helpFor(topic string) mcp.HelpResponse {
}
case "coordination":
return mcp.HelpResponse{
Topic: "coordination",
Content: "send_message tags the message with the caller's role (parent → [orchestrator], child → [sub-agent:<name>]). Siblings must route through their parent. request_human_attention raises a UI notification when you can't safely decide.",
RelatedTools: []string{"send_message", "request_human_attention"},
Topic: "coordination",
Content: "send_message tags the message with the caller's role (parent → [orchestrator], child → [sub-agent:<name>]). Siblings must route through their parent. request_human_attention raises a UI notification when you can't safely decide.\n\n" +
"Reply routing: a sub-agent's reply to your send_message lands in YOUR pane tagged `[sub-agent:<name>]`, not in the sub-agent's output. Anti-pattern: `wait_for_pattern(sub_agent, …)` to wait for a reply — the sub-agent is already idle, its output won't change, and the call spins to timeout. Pattern: send_message → timer_fire_when_idle_any([sub_agent_id], body=\"[system] sub-agent finished\") → when the timer fires, the reply is already queued as your next user turn (or visible via get_process_output on your own pane).",
RelatedTools: []string{"send_message", "request_human_attention", "timer_fire_when_idle_any", "timer_fire_when_idle_all"},
}
case "scratchpads":
return mcp.HelpResponse{
@@ -1161,9 +1162,14 @@ func helpFor(topic string) mcp.HelpResponse {
}
case "readiness":
return mcp.HelpResponse{
Topic: "readiness",
Content: "A pane is 'idle' once nothing has been written to its PTY for ~1s (SPEC §11). Treat idle as a signal to read, not a guarantee of completion. wait_for_pattern lets you wait on a known terminal marker for stronger evidence.",
RelatedTools: []string{"wait_for_pattern", "get_process_status"},
Topic: "readiness",
Content: "A pane is 'idle' once nothing has been written to its PTY for ~1s (SPEC §11). Treat idle as a signal to read, not a guarantee of completion.\n\n" +
"Waiting for a sub-agent's reply (canonical pattern):\n" +
" 1. send_message(sub_agent_id, request)\n" +
" 2. timer_fire_when_idle_any(watched=[sub_agent_id], body=\"[system] sub-agent done\")\n" +
" 3. When the timer fires you re-enter as a fresh user turn; the sub-agent's reply is already in your own pane tagged `[sub-agent:<name>]` (read via get_process_output on yourself if you need it explicitly).\n\n" +
"wait_for_pattern is for waiting on text a process emits in its OWN output (a shell prompt, a build's \"tests passed\" line). It does NOT see send_message replies, because those land in the caller's pane, not the target's — calling wait_for_pattern on a sub-agent to wait for its reply deadlocks until timeout.",
RelatedTools: []string{"wait_for_pattern", "get_process_status", "timer_fire_when_idle_any", "send_message"},
}
case "permissions":
return mcp.HelpResponse{