From 4ff72413abc0f5adf890cf1d29721de2c4f35b2f Mon Sep 17 00:00:00 2001 From: Shane McCarron Date: Fri, 29 May 2026 13:03:48 -0500 Subject: [PATCH 1/6] fix(645): degrade SendMessage shutdown handshake gracefully outside agent teams MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The teammate shutdown_request/shutdown_response handshake requires the SendMessage tool, which only exists inside a live Claude Code agent team (CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1). The six team agent prompts carried an unconditional "you MUST call the SendMessage tool" mandate, so in subagent mode (e.g. prefer_teams=never) or when agent teams are disabled, agents and readers saw an instruction that cannot be followed (issue #645). Make the handshake conditional on actually being in a team, delivered through channels that know the runtime mode: - agents/vbw-{dev,qa,scout,lead,debugger,docs}.md: remove the static "Shutdown Handling" section entirely. The teammate-side response protocol is no longer baked into static prompts (SubagentStart is unreliable for team-spawned teammates, so a hook injector would miss them). - references/execute-protocol.md: the orchestrator now appends the teammate shutdown-response protocol to each teammate's spawn prompt in the true-team-mode branch only — gated by construction, reliably delivered (it is the spawn prompt). Also add a hard precondition that team mode is never requested unless CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1. - scripts/compaction-instructions.sh: the post-compaction shutdown reminder is now gated on a live team marker (delegated-workflow.sh status-json: live && delegation_mode=team && team_name) instead of firing for any team role. Non-team runs get zero shutdown tokens. - references/handoff-schemas.md: point the conditional-refusal note at the new protocol location. Also fixes a latent PreCompact bug uncovered here: snapshot-resume.sh's stdout (the snapshot path) leaked into the hook JSON whenever .execution-state.json existed (every live execute), corrupting the PreCompact additionalContext output. Its stdout is now discarded. tests/shutdown-protocol.bats: rewritten to assert the new design — no static SendMessage mandate in agent prompts, protocol present in execute-protocol's team-spawn block, and compaction injecting only with a live team marker (with subagent/no-marker negative cases). Co-Authored-By: Claude Opus 4.8 --- agents/vbw-debugger.md | 12 -- agents/vbw-dev.md | 12 -- agents/vbw-docs.md | 12 -- agents/vbw-lead.md | 12 -- agents/vbw-qa.md | 12 -- agents/vbw-scout.md | 12 -- references/execute-protocol.md | 5 + references/handoff-schemas.md | 2 +- scripts/compaction-instructions.sh | 23 ++- tests/shutdown-protocol.bats | 258 ++++++++++++----------------- 10 files changed, 126 insertions(+), 234 deletions(-) diff --git a/agents/vbw-debugger.md b/agents/vbw-debugger.md index 3a97d0e84..56bdace87 100644 --- a/agents/vbw-debugger.md +++ b/agents/vbw-debugger.md @@ -105,17 +105,5 @@ You have a limited turn budget. If you've been investigating for many turns with ## Effort Follow effort level in task description (max|high|medium|low). Re-read files after compaction. -## Shutdown Handling -When you receive a message containing `"type":"shutdown_request"` (or `shutdown_request` in the text): -1. Finish any in-progress tool call -2. **Call the SendMessage tool** with this JSON body (fill in your status and echo back the request ID): - ```json - {"type": "shutdown_response", "approved": true, "request_id": "", "final_status": "complete"} - ``` - Use `final_status` value `"complete"`, `"idle"`, or `"in_progress"` as appropriate. Checkpoint your investigation progress (hypotheses, evidence, current status) in the message so work isn't lost. -3. Then STOP — do NOT continue investigating or apply fixes - -**CRITICAL: Plain text acknowledgement is NOT sufficient.** You MUST call the SendMessage tool. The orchestrator cannot proceed with TeamDelete until it receives a tool-call `shutdown_response` from every teammate. - ## Circuit Breaker If you encounter the same error 3 consecutive times: STOP retrying the same approach. Try ONE alternative approach. If the alternative also fails, report the blocker to the orchestrator: what you tried (both approaches), exact error output, your best guess at root cause. Never attempt a 4th retry of the same failing operation. diff --git a/agents/vbw-dev.md b/agents/vbw-dev.md index 75d2ff0da..ee5792fea 100644 --- a/agents/vbw-dev.md +++ b/agents/vbw-dev.md @@ -107,18 +107,6 @@ Your frontmatter denylist explicitly bans recursive delegation, team-management, ## Effort Follow effort level in task description (max|high|medium|low). After compaction (marker appears), re-read PLAN.md and context files from disk. -## Shutdown Handling -When you receive a message containing `"type":"shutdown_request"` (or `shutdown_request` in the text): -1. Finish any in-progress tool call -2. **Call the SendMessage tool** with this JSON body (fill in your status and echo back the request ID): - ```json - {"type": "shutdown_response", "approved": true, "request_id": "", "final_status": "complete"} - ``` - Use `final_status` value `"complete"`, `"idle"`, or `"in_progress"` as appropriate. -3. Then STOP. Do NOT start new tasks, fix unrelated issues, commit additional changes, or take any further action - -**CRITICAL: Plain text acknowledgement is NOT sufficient.** You MUST call the SendMessage tool. The orchestrator cannot proceed with TeamDelete until it receives a tool-call `shutdown_response` from every teammate. - ## Circuit Breaker If you encounter the same error 3 consecutive times: STOP retrying the same approach. Try ONE alternative approach. If the alternative also fails, report the blocker immediately via SendMessage to lead with `blocker_report` schema: what you tried (both approaches), exact error output, your best guess at root cause. Never attempt a 4th retry of the same failing operation. diff --git a/agents/vbw-docs.md b/agents/vbw-docs.md index 85ebf6b5c..7b7d0b392 100644 --- a/agents/vbw-docs.md +++ b/agents/vbw-docs.md @@ -94,17 +94,5 @@ Before each task: if `.vbw-planning/.compaction-marker` exists, re-read PLAN.md Follow effort level in task description (max|high|medium|low). After compaction (marker appears), re-read PLAN.md and context files from disk. -## Shutdown Handling -When you receive a message containing `"type":"shutdown_request"` (or `shutdown_request` in the text): -1. Finish any in-progress tool call -2. **Call the SendMessage tool** with this JSON body (fill in your status and echo back the request ID): - ```json - {"type": "shutdown_response", "approved": true, "request_id": "", "final_status": "complete"} - ``` - Use `final_status` value `"complete"`, `"idle"`, or `"in_progress"` as appropriate. -3. Then STOP. Do NOT start new doc tasks, commit additional changes, or take any further action - -**CRITICAL: Plain text acknowledgement is NOT sufficient.** You MUST call the SendMessage tool. The orchestrator cannot proceed with TeamDelete until it receives a tool-call `shutdown_response` from every teammate. - ## Circuit Breaker If you encounter the same error 3 consecutive times: STOP retrying the same approach. Try ONE alternative approach. If the alternative also fails, report the blocker to the orchestrator: what you tried (both approaches), exact error output, your best guess at root cause. Never attempt a 4th retry of the same failing operation. diff --git a/agents/vbw-lead.md b/agents/vbw-lead.md index ff2cc0d06..fd775f5a7 100644 --- a/agents/vbw-lead.md +++ b/agents/vbw-lead.md @@ -94,17 +94,5 @@ When receiving `execution_update`, `qa_verdict`, `blocker_report`, or `debugger_ ## Effort Follow effort level in task description (max|high|medium|low). Re-read files after compaction. -## Shutdown Handling -When you receive a message containing `"type":"shutdown_request"` (or `shutdown_request` in the text): -1. Finish any in-progress tool call -2. **Call the SendMessage tool** with this JSON body (fill in your status and echo back the request ID): - ```json - {"type": "shutdown_response", "approved": true, "request_id": "", "final_status": "complete"} - ``` - Use `final_status` value `"complete"`, `"idle"`, or `"in_progress"` as appropriate. -3. Then STOP. Do NOT start new plans, revise existing ones, or take any further action - -**CRITICAL: Plain text acknowledgement is NOT sufficient.** You MUST call the SendMessage tool. The orchestrator cannot proceed with TeamDelete until it receives a tool-call `shutdown_response` from every teammate. - ## Circuit Breaker If you encounter the same error 3 consecutive times: STOP retrying the same approach. Try ONE alternative approach. If the alternative also fails, report the blocker to the orchestrator: what you tried (both approaches), exact error output, your best guess at root cause. Never attempt a 4th retry of the same failing operation. diff --git a/agents/vbw-qa.md b/agents/vbw-qa.md index a49d3ef8b..cfbcdac49 100644 --- a/agents/vbw-qa.md +++ b/agents/vbw-qa.md @@ -170,17 +170,5 @@ Substitute `` and `` from your task description (e.g., ## Effort Follow effort level in task description (max|high|medium|low). Re-read files after compaction. -## Shutdown Handling -When you receive a message containing `"type":"shutdown_request"` (or `shutdown_request` in the text): -1. Finish any in-progress tool call -2. **Call the SendMessage tool** with this JSON body (fill in your status and echo back the request ID): - ```json - {"type": "shutdown_response", "approved": true, "request_id": "", "final_status": "complete"} - ``` - Use `final_status` value `"complete"`, `"idle"`, or `"in_progress"` as appropriate. -3. Then STOP. Do NOT start new checks, report additional findings, or take any further action - -**CRITICAL: Plain text acknowledgement is NOT sufficient.** You MUST call the SendMessage tool. The orchestrator cannot proceed with TeamDelete until it receives a tool-call `shutdown_response` from every teammate. - ## Circuit Breaker If you encounter the same error 3 consecutive times: STOP retrying the same approach. Try ONE alternative approach. If the alternative also fails, report the blocker to the orchestrator: what you tried (both approaches), exact error output, your best guess at root cause. Never attempt a 4th retry of the same failing operation. diff --git a/agents/vbw-scout.md b/agents/vbw-scout.md index 9b447c907..b36e29572 100644 --- a/agents/vbw-scout.md +++ b/agents/vbw-scout.md @@ -112,18 +112,6 @@ Write only to files specified in `` or `` inside `.vb ## Effort Follow effort level in task description (max|high|medium|low). Re-read files after compaction. -## Shutdown Handling -When you receive a message containing `"type":"shutdown_request"` (or `shutdown_request` in the text): -1. Finish any in-progress tool call -2. **Call the SendMessage tool** with this JSON body (fill in your status and echo back the request ID): - ```json - {"type": "shutdown_response", "approved": true, "request_id": "", "final_status": "complete"} - ``` - Use `final_status` value `"complete"`, `"idle"`, or `"in_progress"` as appropriate. -3. Then STOP. Do NOT start new searches, report additional findings, or take any further action - -**CRITICAL: Plain text acknowledgement is NOT sufficient.** You MUST call the SendMessage tool. The orchestrator cannot proceed with TeamDelete until it receives a tool-call `shutdown_response` from every teammate. - ## Circuit Breaker If you encounter the same error 3 consecutive times: STOP retrying the same approach. Try ONE alternative approach. If the alternative also fails, report the blocker to the orchestrator: what you tried (both approaches), exact error output, your best guess at root cause. Never attempt a 4th retry of the same failing operation. diff --git a/references/execute-protocol.md b/references/execute-protocol.md index 64205d475..1e67086a0 100644 --- a/references/execute-protocol.md +++ b/references/execute-protocol.md @@ -166,6 +166,7 @@ Team request policy from helper output: - Unknown normalized values preserve the raw value, use `delegation_mode=subagent`, and report `unknown_prefer_teams:`. Determine whether **real team semantics** are available in the live tool set before spawning anything: +- **Hard precondition — harness feature flag:** agent teams (and the `SendMessage`/`TeamCreate`/`TeamDelete` tools) only exist when the `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS` environment variable equals `1` (it is `0` or unset by default). Confirm it before considering team mode. If it is not `1`, real team semantics are **NOT** available regardless of any segment's requested `delegation_mode` — go directly to the team-tooling-unavailable fallback and do not attempt `TeamCreate`. Only when the flag is `1` does it make sense to inspect the live tool set: - Real team semantics are available when either: 1. `TeamCreate` + teammate task spawning are available, **or** 2. the live teammate spawn tool accepts both `team_name` and per-teammate `name` parameters (for example `Agent(...)` with `team_name:` and `name:`) @@ -190,6 +191,10 @@ Branch each segment into exactly one runtime path and persist that segment's act bash "${VBW_PLUGIN_ROOT}/scripts/delegated-workflow.sh" set execute {segment_effort} team "$TEAM_NAME" ``` - All Dev and QA teammates below MUST carry `team_name: "$TEAM_NAME"` plus `name: "dev-{MM}"` (from plan number) or `name: "qa"` / `name: "qa-wave{W}"` on the live spawn call. No plain task-management `TaskCreate` may happen after the team marker is set unless it carries the selected `TEAM_NAME` and teammate `name`. + - **Teammate shutdown-response protocol (team mode only):** Because `SendMessage` exists only inside a live agent team, the teammate-side shutdown handler is NOT baked into the static agent prompts — it is delivered here, at spawn time, only when a real team is created. Append the following block verbatim to every teammate's spawn task description (Dev, QA, Scout, Lead, Debugger, Docs): + > Team shutdown: when you receive a message containing `"type":"shutdown_request"` (or `shutdown_request` in the text), (1) finish any in-progress tool call, (2) **call the `SendMessage` tool** with body `{"type": "shutdown_response", "approved": true, "request_id": "", "final_status": "complete|idle|in_progress"}` using the `final_status` that matches your state, then (3) STOP — take no further action. Plain text acknowledgement does NOT satisfy the handshake; you MUST call `SendMessage`. + + Do NOT append this block in explicit non-team or team-tooling-unavailable runs — `SendMessage` does not exist there and the orchestrator never sends `shutdown_request`, so the instruction would be an un-followable no-op. 2. **Explicit non-team mode** - Use this path when `prefer_teams='never'`, `prefer_teams='auto'` with `max_parallel_width <= 1`, unknown `prefer_teams`, no delegate-eligible plans, segment route `turbo`/internal `direct`, or team-tooling-unavailable fallback. diff --git a/references/handoff-schemas.md b/references/handoff-schemas.md index 7ddbc7297..17baa6863 100644 --- a/references/handoff-schemas.md +++ b/references/handoff-schemas.md @@ -331,7 +331,7 @@ Claude Code delivers `shutdown_request` as JSON text in the SendMessage inbox. T On receiving `shutdown_request`: finish any in-progress tool call, **call the SendMessage tool** with `shutdown_response` (`request_id` echoed from the request, `approved: true`, `final_status`), then STOP all further work. Do NOT start new tasks, fix additional issues, or take any action after responding. The orchestrator will call TeamDelete after collecting all responses. **Plain text acknowledgement does NOT satisfy the shutdown protocol — you MUST call the SendMessage tool.** -> **Conditional refusal:** The schema allows `approved: false` with `pending_work` describing what remains. Currently all agents are instructed to always approve. The orchestrator retries up to 3 times on rejection before proceeding. If a future agent needs to delay shutdown (e.g., mid-write to disk), update its Shutdown Handling section to allow conditional refusal with `approved: false`. +> **Conditional refusal:** The schema allows `approved: false` with `pending_work` describing what remains. Currently all agents are instructed to always approve. The orchestrator retries up to 3 times on rejection before proceeding. If a future agent needs to delay shutdown (e.g., mid-write to disk), update the teammate shutdown-response protocol the orchestrator delivers at spawn (see `references/execute-protocol.md`) to allow conditional refusal with `approved: false`. ## Backward Compatibility diff --git a/scripts/compaction-instructions.sh b/scripts/compaction-instructions.sh index 438369a12..25e2d16c7 100755 --- a/scripts/compaction-instructions.sh +++ b/scripts/compaction-instructions.sh @@ -12,6 +12,7 @@ set -u # - Compaction markers for Dev re-read guards PLANNING_DIR="${VBW_PLANNING_DIR:-.vbw-planning}" +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" INPUT=$(cat) NATIVE_AGENT_TYPE=$(echo "$INPUT" | jq -r '.agent_type // ""' 2>/dev/null) @@ -201,11 +202,21 @@ case "$AGENT_ROLE" in ;; esac -# Inject shutdown protocol reminder for team agents (survives compaction). -# NOTE: Keep this reminder in sync with the Shutdown Handling section in agents/vbw-*.md. +# Inject shutdown protocol reminder — ONLY for agents in a live VBW agent team. +# The teammate shutdown handshake (and the SendMessage tool it requires) exist +# only in team mode; the orchestrator's team-mode spawn prompt delivers this block +# initially (see references/execute-protocol.md), and this re-injects it so it +# survives compaction. Gated on the delegation marker so non-team subagent runs +# never receive an un-followable SendMessage instruction. case "$AGENT_ROLE" in scout|dev|qa|lead|debugger|docs) - PRIORITIES="$PRIORITIES. SHUTDOWN PROTOCOL: If you receive a message containing \"shutdown_request\", you MUST call the SendMessage tool with a JSON body: {\"type\": \"shutdown_response\", \"approved\": true, \"request_id\": \"\", \"final_status\": \"complete|idle|in_progress\"}. Use the final_status value that matches your current state. Plain text responses do NOT satisfy the shutdown protocol." + _vbw_team_marker=$(VBW_PLANNING_DIR="$PLANNING_DIR" bash "$SCRIPT_DIR/delegated-workflow.sh" status-json 2>/dev/null) || _vbw_team_marker="" + if [ -n "$_vbw_team_marker" ] \ + && [ "$(printf '%s' "$_vbw_team_marker" | jq -r '.live // false' 2>/dev/null)" = "true" ] \ + && [ "$(printf '%s' "$_vbw_team_marker" | jq -r '.delegation_mode // ""' 2>/dev/null)" = "team" ] \ + && [ -n "$(printf '%s' "$_vbw_team_marker" | jq -r '.team_name // ""' 2>/dev/null)" ]; then + PRIORITIES="$PRIORITIES. SHUTDOWN PROTOCOL: If you receive a message containing \"shutdown_request\", you MUST call the SendMessage tool with a JSON body: {\"type\": \"shutdown_response\", \"approved\": true, \"request_id\": \"\", \"final_status\": \"complete|idle|in_progress\"}. Use the final_status value that matches your current state. Plain text responses do NOT satisfy the shutdown protocol." + fi ;; esac @@ -286,11 +297,13 @@ if [ -d "$PLANNING_DIR" ]; then fi # --- Save agent state snapshot --- -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" if [ -f "$PLANNING_DIR/.execution-state.json" ] && [ -f "$SCRIPT_DIR/snapshot-resume.sh" ]; then SNAP_PHASE=$(jq -r '.phase // ""' "$PLANNING_DIR/.execution-state.json" 2>/dev/null) if [ -n "$SNAP_PHASE" ]; then - bash "$SCRIPT_DIR/snapshot-resume.sh" save "$SNAP_PHASE" "$PLANNING_DIR/.execution-state.json" "$AGENT_ROLE" "$TRIGGER" 2>/dev/null || true + # Discard stdout (snapshot-resume.sh save echoes the snapshot path): this is a + # PreCompact hook whose stdout must be valid JSON only. When .execution-state.json + # exists (every live execute), an unredirected path would corrupt the hook JSON. + bash "$SCRIPT_DIR/snapshot-resume.sh" save "$SNAP_PHASE" "$PLANNING_DIR/.execution-state.json" "$AGENT_ROLE" "$TRIGGER" >/dev/null 2>&1 || true TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || date +"%Y-%m-%d %H:%M:%S") echo "[$TIMESTAMP] Snapshot saved: phase=$SNAP_PHASE agent=${AGENT_ROLE:-$AGENT_INSTANCE_NAME}" >> "$PLANNING_DIR/.hook-errors.log" 2>/dev/null || true fi diff --git a/tests/shutdown-protocol.bats b/tests/shutdown-protocol.bats index 3762438e4..943adc1c5 100644 --- a/tests/shutdown-protocol.bats +++ b/tests/shutdown-protocol.bats @@ -25,97 +25,59 @@ teardown() { } # ============================================================================= -# Agent definitions: all 6 team-participating agents have Shutdown Handling +# Issue #645: the teammate shutdown handshake is NOT baked into static agent +# prompts. SendMessage exists only inside a live agent team, so the handler is +# delivered at spawn time by the orchestrator (team mode only) — see +# references/execute-protocol.md. These guards prevent reintroducing an +# un-followable SendMessage mandate into the static agent prompts. # ============================================================================= -@test "vbw-dev has Shutdown Handling section" { - grep -q '^## Shutdown Handling$' "$PROJECT_ROOT/agents/vbw-dev.md" -} - -@test "vbw-qa has Shutdown Handling section" { - grep -q '^## Shutdown Handling$' "$PROJECT_ROOT/agents/vbw-qa.md" -} - -@test "vbw-scout has Shutdown Handling section" { - grep -q '^## Shutdown Handling$' "$PROJECT_ROOT/agents/vbw-scout.md" -} - -@test "vbw-lead has Shutdown Handling section" { - grep -q '^## Shutdown Handling$' "$PROJECT_ROOT/agents/vbw-lead.md" -} - -@test "vbw-debugger has Shutdown Handling section" { - grep -q '^## Shutdown Handling$' "$PROJECT_ROOT/agents/vbw-debugger.md" -} - -@test "vbw-docs has Shutdown Handling section" { - grep -q '^## Shutdown Handling$' "$PROJECT_ROOT/agents/vbw-docs.md" -} - -# ============================================================================= -# Agent handlers reference both message types -# ============================================================================= - -@test "all agent handlers reference shutdown_request" { +@test "no team agent .md carries a static Shutdown Handling section" { for agent in dev qa scout lead debugger docs; do - grep -q 'shutdown_request' "$PROJECT_ROOT/agents/vbw-${agent}.md" || { - echo "vbw-${agent}.md missing shutdown_request reference" + ! grep -q '^## Shutdown Handling$' "$PROJECT_ROOT/agents/vbw-${agent}.md" || { + echo "vbw-${agent}.md must NOT carry a static Shutdown Handling section (issue #645)" return 1 } done } -@test "all agent handlers reference shutdown_response" { +@test "no team agent .md carries the unconditional 'MUST call the SendMessage tool' mandate" { for agent in dev qa scout lead debugger docs; do - grep -q 'shutdown_response' "$PROJECT_ROOT/agents/vbw-${agent}.md" || { - echo "vbw-${agent}.md missing shutdown_response reference" + ! grep -qi 'MUST call the SendMessage tool' "$PROJECT_ROOT/agents/vbw-${agent}.md" || { + echo "vbw-${agent}.md must NOT carry an unconditional SendMessage shutdown mandate (issue #645)" return 1 } done } # ============================================================================= -# Agent handlers instruct STOP behavior +# execute-protocol.md delivers the teammate shutdown-response protocol at spawn, +# gated to true team mode. # ============================================================================= -@test "all agent shutdown handlers instruct to STOP" { - for agent in dev qa scout lead debugger docs; do - # Each handler must contain a STOP instruction - sed -n '/^## Shutdown Handling$/,/^## /p' "$PROJECT_ROOT/agents/vbw-${agent}.md" | grep -qi 'STOP' || { - echo "vbw-${agent}.md Shutdown Handling section missing STOP instruction" - return 1 - } - done -} - -@test "debugger handler includes checkpoint instruction" { - sed -n '/^## Shutdown Handling$/,/^## /p' "$PROJECT_ROOT/agents/vbw-debugger.md" | grep -qi 'checkpoint' +@test "execute-protocol.md team-mode branch documents the teammate shutdown-response protocol" { + local section + section=$(sed -n '/Teammate shutdown-response protocol/,/Explicit non-team mode/p' "$PROJECT_ROOT/references/execute-protocol.md") + [ -n "$section" ] || { echo "execute-protocol.md missing teammate shutdown-response protocol block"; return 1; } + echo "$section" | grep -q 'shutdown_request' + echo "$section" | grep -q 'shutdown_response' + echo "$section" | grep -qi 'SendMessage' + echo "$section" | grep -q 'request_id' + echo "$section" | grep -q 'approved' + echo "$section" | grep -q 'final_status' + echo "$section" | grep -qi 'STOP' +} + +@test "execute-protocol.md shutdown protocol is gated to team mode only" { + local section + section=$(sed -n '/Teammate shutdown-response protocol/,/Explicit non-team mode/p' "$PROJECT_ROOT/references/execute-protocol.md") + echo "$section" | grep -qi 'team mode only' + echo "$section" | grep -qi 'non-team' } -# ============================================================================= -# Shutdown Handling is positioned between Effort and Circuit Breaker -# ============================================================================= - -@test "shutdown handling section order: after Effort, before Circuit Breaker" { - for agent in dev qa scout lead debugger docs; do - local file="$PROJECT_ROOT/agents/vbw-${agent}.md" - local effort_line shutdown_line breaker_line - effort_line=$(grep -n '^## Effort' "$file" | head -1 | cut -d: -f1) - shutdown_line=$(grep -n '^## Shutdown Handling' "$file" | head -1 | cut -d: -f1) - breaker_line=$(grep -n '^## Circuit Breaker' "$file" | head -1 | cut -d: -f1) - [ -n "$effort_line" ] && [ -n "$shutdown_line" ] && [ -n "$breaker_line" ] || { - echo "vbw-${agent}.md missing one of Effort/Shutdown/Circuit sections" - return 1 - } - [ "$effort_line" -lt "$shutdown_line" ] || { - echo "vbw-${agent}.md: Shutdown Handling ($shutdown_line) not after Effort ($effort_line)" - return 1 - } - [ "$shutdown_line" -lt "$breaker_line" ] || { - echo "vbw-${agent}.md: Shutdown Handling ($shutdown_line) not before Circuit Breaker ($breaker_line)" - return 1 - } - done +@test "execute-protocol.md shutdown protocol warns plain text is not sufficient" { + sed -n '/Teammate shutdown-response protocol/,/Explicit non-team mode/p' "$PROJECT_ROOT/references/execute-protocol.md" \ + | grep -qi 'NOT satisfy\|NOT sufficient' } # ============================================================================= @@ -490,81 +452,24 @@ teardown() { } # ============================================================================= -# Issue #198: Mechanical tool-call instructions in agent Shutdown Handling -# Agents must be told to CALL the SendMessage tool, not just "respond" +# Issue #198 + #645: mechanical tool-call instructions now live in the +# orchestrator-delivered team-spawn block (execute-protocol.md), not agent .md. +# Agents must be told to CALL the SendMessage tool, not just "respond". # ============================================================================= -@test "all agent shutdown handlers require calling SendMessage tool" { - for agent in dev qa scout lead debugger docs; do - local section - section=$(sed -n '/^## Shutdown Handling$/,/^## /p' "$PROJECT_ROOT/agents/vbw-${agent}.md") - echo "$section" | grep -qi 'call.*SendMessage tool' || { - echo "vbw-${agent}.md Shutdown Handling missing 'call the SendMessage tool' instruction" - return 1 - } - done +@test "execute-protocol shutdown block uses calling-the-tool language (not just 'respond')" { + sed -n '/Teammate shutdown-response protocol/,/Explicit non-team mode/p' "$PROJECT_ROOT/references/execute-protocol.md" \ + | grep -qi 'call the `SendMessage` tool\|call the SendMessage tool\|call `SendMessage`' } -@test "all agent shutdown handlers warn plain text is NOT sufficient" { - for agent in dev qa scout lead debugger docs; do - local section - section=$(sed -n '/^## Shutdown Handling$/,/^## /p' "$PROJECT_ROOT/agents/vbw-${agent}.md") - echo "$section" | grep -qi 'NOT sufficient' || { - echo "vbw-${agent}.md Shutdown Handling missing 'NOT sufficient' warning" - return 1 - } - done -} - -@test "all agent shutdown handlers specify approved (not approve) field" { - for agent in dev qa scout lead debugger docs; do - local section - section=$(sed -n '/^## Shutdown Handling$/,/^## /p' "$PROJECT_ROOT/agents/vbw-${agent}.md") - echo "$section" | grep -q '"approved"' || { - echo "vbw-${agent}.md Shutdown Handling uses wrong field name (should be \"approved\", not \"approve\")" - return 1 - } - done -} - -@test "all agent shutdown handlers include request_id in template" { - for agent in dev qa scout lead debugger docs; do - local section - section=$(sed -n '/^## Shutdown Handling$/,/^## /p' "$PROJECT_ROOT/agents/vbw-${agent}.md") - echo "$section" | grep -q 'request_id' || { - echo "vbw-${agent}.md Shutdown Handling missing request_id in JSON template" - return 1 - } - done -} - -@test "all agent shutdown handler templates match schema payload_required fields in JSON block" { +@test "execute-protocol shutdown JSON includes all schema payload_required fields" { local schema_file="$CONFIG_DIR/schemas/message-schemas.json" - local required_fields + local required_fields section required_fields=$(jq -r '.schemas.shutdown_response.payload_required[]' "$schema_file") - for agent in dev qa scout lead debugger docs; do - # Extract only the fenced JSON block from the Shutdown Handling section - local json_block - json_block=$(sed -n '/^## Shutdown Handling$/,/^## /p' "$PROJECT_ROOT/agents/vbw-${agent}.md" | sed -n '/```json$/,/```$/p') - [ -n "$json_block" ] || { - echo "vbw-${agent}.md Shutdown Handling missing fenced JSON code block" - return 1 - } - for field in $required_fields; do - echo "$json_block" | grep -q "\"$field\"" || { - echo "vbw-${agent}.md JSON template missing schema-required field: $field" - return 1 - } - done - done -} - -@test "all agent shutdown handlers specify shutdown_response type" { - for agent in dev qa scout lead debugger docs; do - local section - section=$(sed -n '/^## Shutdown Handling$/,/^## /p' "$PROJECT_ROOT/agents/vbw-${agent}.md") - echo "$section" | grep -q 'shutdown_response' || { - echo "vbw-${agent}.md Shutdown Handling missing shutdown_response type" + section=$(sed -n '/Teammate shutdown-response protocol/,/Explicit non-team mode/p' "$PROJECT_ROOT/references/execute-protocol.md") + for field in $required_fields; do + echo "$section" | grep -q "\"$field\"" || { + echo "execute-protocol shutdown block missing schema-required field: $field" return 1 } done @@ -580,15 +485,27 @@ teardown() { echo "$section" | grep -qi 'NOT satisfy\|NOT sufficient\|not plain text' } -@test "compaction-instructions.sh injects shutdown protocol reminder for team agents" { +# Helper: write a live execute team marker (satisfies delegated-workflow.sh +# status-json 'live' criteria: active execute marker + running execution state +# with matching correlation_id, both freshly written so mtime is recent). +write_live_team_marker() { + local corr="$1" + printf '%s\n' "{\"phase\":1,\"status\":\"running\",\"effort\":\"balanced\",\"correlation_id\":\"$corr\",\"plans\":[]}" \ + > "$TEST_TEMP_DIR/.vbw-planning/.execution-state.json" + printf '%s\n' "{\"mode\":\"execute\",\"active\":true,\"effort\":\"balanced\",\"delegation_mode\":\"team\",\"team_name\":\"vbw-phase-01\",\"session_id\":\"s\",\"correlation_id\":\"$corr\",\"started_at\":\"2026-05-29T00:00:00Z\"}" \ + > "$TEST_TEMP_DIR/.vbw-planning/.delegated-workflow.json" +} + +@test "compaction-instructions.sh injects shutdown reminder for team agents in a live team" { cd "$TEST_TEMP_DIR" + write_live_team_marker "corr-645a" for agent in scout dev qa lead debugger docs; do echo '{"agent_name":"vbw-'"$agent"'","matcher":"auto"}' | \ - bash "$PROJECT_ROOT/scripts/compaction-instructions.sh" > "$TEST_TEMP_DIR/compaction-output.json" + VBW_PLANNING_DIR="$TEST_TEMP_DIR/.vbw-planning" bash "$PROJECT_ROOT/scripts/compaction-instructions.sh" > "$TEST_TEMP_DIR/compaction-output.json" local ctx ctx=$(jq -r '.hookSpecificOutput.additionalContext' "$TEST_TEMP_DIR/compaction-output.json") echo "$ctx" | grep -qi 'SHUTDOWN PROTOCOL' || { - echo "compaction-instructions.sh missing shutdown reminder for $agent" + echo "compaction-instructions.sh missing shutdown reminder for $agent (live team)" return 1 } echo "$ctx" | grep -qi 'SendMessage tool' || { @@ -605,6 +522,40 @@ teardown() { done } +@test "compaction-instructions.sh does NOT inject shutdown reminder without a live team marker" { + cd "$TEST_TEMP_DIR" + # Subagent / non-team run: no delegation marker present + rm -f "$TEST_TEMP_DIR/.vbw-planning/.delegated-workflow.json" "$TEST_TEMP_DIR/.vbw-planning/.execution-state.json" + for agent in scout dev qa lead debugger docs; do + echo '{"agent_name":"vbw-'"$agent"'","matcher":"auto"}' | \ + VBW_PLANNING_DIR="$TEST_TEMP_DIR/.vbw-planning" bash "$PROJECT_ROOT/scripts/compaction-instructions.sh" > "$TEST_TEMP_DIR/compaction-output.json" + local ctx + ctx=$(jq -r '.hookSpecificOutput.additionalContext' "$TEST_TEMP_DIR/compaction-output.json") + echo "$ctx" | grep -qi 'SHUTDOWN PROTOCOL' && { + echo "compaction-instructions.sh should NOT inject shutdown reminder for $agent without a live team" + return 1 + } + done + return 0 +} + +@test "compaction-instructions.sh does NOT inject shutdown reminder for subagent-mode marker" { + cd "$TEST_TEMP_DIR" + printf '%s\n' '{"phase":1,"status":"running","effort":"balanced","correlation_id":"corr-sub","plans":[]}' \ + > "$TEST_TEMP_DIR/.vbw-planning/.execution-state.json" + printf '%s\n' '{"mode":"execute","active":true,"effort":"balanced","delegation_mode":"subagent","team_name":"","session_id":"s","correlation_id":"corr-sub","started_at":"2026-05-29T00:00:00Z"}' \ + > "$TEST_TEMP_DIR/.vbw-planning/.delegated-workflow.json" + echo '{"agent_name":"vbw-dev","matcher":"auto"}' | \ + VBW_PLANNING_DIR="$TEST_TEMP_DIR/.vbw-planning" bash "$PROJECT_ROOT/scripts/compaction-instructions.sh" > "$TEST_TEMP_DIR/compaction-output.json" + local ctx + ctx=$(jq -r '.hookSpecificOutput.additionalContext' "$TEST_TEMP_DIR/compaction-output.json") + echo "$ctx" | grep -qi 'SHUTDOWN PROTOCOL' && { + echo "compaction-instructions.sh should NOT inject shutdown reminder in subagent mode" + return 1 + } + return 0 +} + @test "compaction-instructions.sh does NOT inject shutdown reminder for default/unknown agents" { cd "$TEST_TEMP_DIR" echo '{"agent_name":"unknown-agent","matcher":"auto"}' | \ @@ -755,22 +706,17 @@ teardown() { # Prompt-equivalence: compaction reminder must match agent final_status semantics # ============================================================================= -@test "compaction reminder includes all final_status values from agent prompts" { +@test "shutdown final_status trio is consistent across execute-protocol and compaction reminder" { cd "$TEST_TEMP_DIR" - # Verify ALL 6 team agents have the canonical final_status trio - for agent in dev lead qa scout debugger docs; do - local agent_statuses - agent_statuses=$(grep -o '"complete".*"idle".*"in_progress"' "$PROJECT_ROOT/agents/vbw-${agent}.md" || true) - [ -n "$agent_statuses" ] || { - echo "FAIL: vbw-${agent}.md missing final_status values" - return 1 - } - done + # The orchestrator-delivered team-spawn block carries the canonical trio + sed -n '/Teammate shutdown-response protocol/,/Explicit non-team mode/p' "$PROJECT_ROOT/references/execute-protocol.md" \ + | grep -q 'complete|idle|in_progress' - # Verify compaction reminder includes the same three values + # The compaction reminder (live team) carries the same trio + write_live_team_marker "corr-645b" local compaction_output compaction_output=$(echo '{"agent_name":"vbw-dev","matcher":"auto"}' \ - | bash "$SCRIPTS_DIR/compaction-instructions.sh" 2>/dev/null \ + | VBW_PLANNING_DIR="$TEST_TEMP_DIR/.vbw-planning" bash "$SCRIPTS_DIR/compaction-instructions.sh" 2>/dev/null \ | jq -r '.hookSpecificOutput.additionalContext' 2>/dev/null || true) echo "$compaction_output" | grep -q 'complete|idle|in_progress' } From af613617765539d4f5ef708479d9c4dad45f8a6c Mon Sep 17 00:00:00 2001 From: Shane McCarron Date: Fri, 29 May 2026 14:08:42 -0500 Subject: [PATCH 2/6] fix(645): address QA round 1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit QA round 1 (Claude Opus 4.8) found a major regression and two stale-doc issues, all confirmed: - [major] /vbw:map and /vbw:debug spawn teammates into real TeamCreate teams with their own inline spawn payloads and do not read execute-protocol.md, so they relied on the now-removed static "## Shutdown Handling" agent sections for the teammate-side handler. Their orchestrator teardown gate waits for a tool-call shutdown_response from teammates that were no longer told to send one. Fix: append the teammate shutdown handler to the map.md Scout communication section (duo/quad, team-mode only) and to the debug.md Path A hypothesis-investigator task body (team-mode only). - [minor] execute-protocol.md enforcement-limitation note still claimed enforcement relies on "mechanical SendMessage instructions in all 6 agent prompts" — removed in this PR. Updated to describe the spawn-prompt + gated compaction mechanism. - [minor] handoff-schemas.md Delivery format prose read as if the handler is an intrinsic agent obligation. Clarified it is delivered at spawn in team mode only (and mirrored in map/debug team-spawn prompts), not in static prompts. Verified: shutdown-protocol.bats 71/71, verify-commands-contract 521/521, verify-execute-delegation-routing pass, two-phase/typed-protocol/event-type bats pass, lint clean. Co-Authored-By: Claude Opus 4.8 --- commands/debug.md | 2 +- commands/map.md | 2 ++ references/execute-protocol.md | 2 +- references/handoff-schemas.md | 2 +- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/commands/debug.md b/commands/debug.md index b99f7f1c3..4a4e15ad9 100644 --- a/commands/debug.md +++ b/commands/debug.md @@ -241,7 +241,7 @@ If resuming a session with `session_status=complete`: STOP "This debug session i RESEARCH_CONTEXT=$(bash "{plugin-root}/scripts/compile-research-context.sh" .vbw-planning "{bug description from Step 1}" 2>/dev/null || echo "") ``` Replace `{bug description from Step 1}` with the actual parsed bug description. If `RESEARCH_CONTEXT` is non-empty, include it in each Debugger task prompt below. If empty, omit the `` block entirely. - - Create 3 tasks via TaskCreate, each with: bug report, standalone research context (include ONLY if RESEARCH_CONTEXT was non-empty: `Prior research findings from /vbw:research. Advisory — verify all claims against the current codebase before relying on them.\n{RESEARCH_CONTEXT}`), extended context from todo detail only when the detail has actual signal from Step 1 (include `detail.context` and `detail.files` — omit this section entirely when detail was absent or structurally empty), and if detail did not have signal but bounded sparse-context enrichment from Step 1 returned `ok`, include the enrichment summary plus matched files and markers as a compact hint. Omit this enrichment section entirely if the helper returned `no_signal` or `no_match`. Include ONE hypothesis only (no cross-contamination), working dir, codebase bootstrap instruction ("If `.vbw-planning/codebase/META.md` exists, read ARCHITECTURE.md, CONCERNS.md, PATTERNS.md, and DEPENDENCIES.md (whichever exist) from `.vbw-planning/codebase/` to bootstrap codebase understanding before investigating"), instruction to report via `debugger_report` schema (see `{plugin-root}/references/handoff-schemas.md`) including explicit `resolution_observation=already_fixed|needs_change|inconclusive`, instruction that `resolution_observation` is analysis-scoped only (teammates do not own the final command outcome or session status), and instruction that accepted-process-exception/backlog metadata alone is not enough for `already_fixed`; `already_fixed` requires fresh current evidence that the underlying issue no longer reproduces or the branch contains a real fix. Explicit report-only contract: "You are a hypothesis investigator, not the implementation owner. Do NOT edit files, apply fixes, run mutating Bash, commit, request implementation approval, or claim ownership of the final session outcome. Stop after diagnosis plus evidence reporting via `debugger_report`." Also include instruction: "If investigation reveals pre-existing failures unrelated to this bug, list them in your response under a 'Pre-existing Issues' heading with test name, file, and failure message." **Include `[analysis-only]` in each task subject** (e.g., "Hypothesis 1: race condition in sync handler [analysis-only]") so the TaskCompleted hook skips the commit-verification gate for report-only tasks. Keep `[analysis-only]` as an advisory hook marker only — it does NOT replace the report-only contract above. + - Create 3 tasks via TaskCreate, each with: bug report, standalone research context (include ONLY if RESEARCH_CONTEXT was non-empty: `Prior research findings from /vbw:research. Advisory — verify all claims against the current codebase before relying on them.\n{RESEARCH_CONTEXT}`), extended context from todo detail only when the detail has actual signal from Step 1 (include `detail.context` and `detail.files` — omit this section entirely when detail was absent or structurally empty), and if detail did not have signal but bounded sparse-context enrichment from Step 1 returned `ok`, include the enrichment summary plus matched files and markers as a compact hint. Omit this enrichment section entirely if the helper returned `no_signal` or `no_match`. Include ONE hypothesis only (no cross-contamination), working dir, codebase bootstrap instruction ("If `.vbw-planning/codebase/META.md` exists, read ARCHITECTURE.md, CONCERNS.md, PATTERNS.md, and DEPENDENCIES.md (whichever exist) from `.vbw-planning/codebase/` to bootstrap codebase understanding before investigating"), instruction to report via `debugger_report` schema (see `{plugin-root}/references/handoff-schemas.md`) including explicit `resolution_observation=already_fixed|needs_change|inconclusive`, instruction that `resolution_observation` is analysis-scoped only (teammates do not own the final command outcome or session status), and instruction that accepted-process-exception/backlog metadata alone is not enough for `already_fixed`; `already_fixed` requires fresh current evidence that the underlying issue no longer reproduces or the branch contains a real fix. Explicit report-only contract: "You are a hypothesis investigator, not the implementation owner. Do NOT edit files, apply fixes, run mutating Bash, commit, request implementation approval, or claim ownership of the final session outcome. Stop after diagnosis plus evidence reporting via `debugger_report`." Also include instruction: "If investigation reveals pre-existing failures unrelated to this bug, list them in your response under a 'Pre-existing Issues' heading with test name, file, and failure message." **Include `[analysis-only]` in each task subject** (e.g., "Hypothesis 1: race condition in sync handler [analysis-only]") so the TaskCompleted hook skips the commit-verification gate for report-only tasks. Keep `[analysis-only]` as an advisory hook marker only — it does NOT replace the report-only contract above. **Teammate shutdown handler (Path A team only):** because these investigators run inside a real team and the teardown gate below waits for a tool-call response, also include this in each task body: "When you receive a message containing `\"type\":\"shutdown_request\"` (or `shutdown_request` in the text), finish any in-progress tool call, then **call the `SendMessage` tool** with `{\"type\": \"shutdown_response\", \"approved\": true, \"request_id\": \"\", \"final_status\": \"complete|idle|in_progress\"}`, then STOP. Plain text acknowledgement does NOT satisfy the handshake — you MUST call `SendMessage`." - Spawn 3 vbw-debugger teammates, one task each. **Set `subagent_type: "vbw:vbw-debugger"` and `model: "${DEBUGGER_MODEL}"` on each Task spawn. If `DEBUGGER_MAX_TURNS` is non-empty, also pass `maxTurns: ${DEBUGGER_MAX_TURNS}`. If `DEBUGGER_MAX_TURNS` is empty, do NOT include maxTurns (omitting it = unlimited).** True-team spawn shape: pass the exact TeamCreate `team_name` and a unique per-teammate `name` for each hypothesis investigator (for example `debug-hypothesis-1`, `debug-hypothesis-2`, `debug-hypothesis-3`). Do not pass `isolation`, `cwd`, `working_dir`, `workingDirectory`, or `workdir`. - **Investigation phase:** Wait until ALL spawned hypothesis investigators have returned `debugger_report`. Then synthesize: strongest evidence + highest confidence wins. Multiple confirmed = contributing factors. After synthesis, choose one authoritative `RESOLUTION_OBSERVATION` value for the command from the teammate reports: `already_fixed` only when fresh current evidence proves the current branch already contains the fix and no new change is needed, `needs_change` when code changes were required or would still be required, `inconclusive` when the evidence is not yet strong enough. For selected accepted-exception/backlog or UAT-deviation items, accepted-process-exception/backlog metadata alone is not enough for `already_fixed`; require fresh current evidence that the underlying issue no longer reproduces or the branch contains a real fix. If any teammate finds the selected item still reproducible or actionable, choose `needs_change`. If remediation is blocked or unsafe without more input, choose `inconclusive` and leave the session unresolved. - Choose one authoritative analysis-scoped `RESOLUTION_OBSERVATION` summary for user-facing presentation. Collect pre-existing issues from all debugger responses. De-duplicate by test name and file (keep first error message when the same test+file pair has different messages) — if multiple debuggers report the same pre-existing failure, include it only once. diff --git a/commands/map.md b/commands/map.md index c891bb2ad..e9afcd70a 100644 --- a/commands/map.md +++ b/commands/map.md @@ -190,6 +190,8 @@ After calling `Skill(...)`, if the loaded skill's instructions reference additio Use targeted `message` not `broadcast`. Wait for all findings. Display ✓ per scout. +**Teammate shutdown handler (duo/quad only — append to each Scout task body):** Because these Scouts run inside a real team and the teardown gate above waits for a tool-call response, include this in each Scout's task prompt: "When you receive a message containing `\"type\":\"shutdown_request\"` (or `shutdown_request` in the text), finish any in-progress tool call, then **call the `SendMessage` tool** with `{\"type\": \"shutdown_response\", \"approved\": true, \"request_id\": \"\", \"final_status\": \"complete|idle|in_progress\"}`, then STOP. Plain text acknowledgement does NOT satisfy the handshake — you MUST call `SendMessage`." Solo mode spawns no team, so omit it there. + ### Step 3.5: Verify mapping documents written by Scouts **Skip if solo** (docs already written). Scouts wrote files directly via ``. Verify all 7 docs exist in `.vbw-planning/codebase/`: STACK.md, DEPENDENCIES.md, ARCHITECTURE.md, STRUCTURE.md, CONVENTIONS.md, TESTING.md, CONCERNS.md. If any are missing, log `⚠ Missing: {filename}` and write a placeholder from the `scout_findings` message content (fall back to cross_cutting text). Use `cross_cutting` findings from scout_findings messages for INDEX.md Validation Notes in Step 4. diff --git a/references/execute-protocol.md b/references/execute-protocol.md index 1e67086a0..1860ec8e0 100644 --- a/references/execute-protocol.md +++ b/references/execute-protocol.md @@ -1022,7 +1022,7 @@ Regardless of whether a real team was created, clear the execute delegation mark bash "${VBW_PLUGIN_ROOT}/scripts/delegated-workflow.sh" clear 2>/dev/null || true ``` -> **Runtime enforcement limitation:** Claude Code does not expose agent-team message tool calls (e.g., `SendMessage`) to `PreToolUse`/`PostToolUse` hooks with stable `tool_name` values. Therefore VBW cannot hook-validate malformed shutdown responses at runtime. Enforcement relies on: (1) mechanical SendMessage instructions in all 6 agent prompts, (2) compaction-instructions.sh reminders that survive context compaction, (3) orchestrator retry (re-send if teammate responds in plain text), and (4) `/vbw:doctor --cleanup` as a recovery path for stuck teams. +> **Runtime enforcement limitation:** Claude Code does not expose agent-team message tool calls (e.g., `SendMessage`) to `PreToolUse`/`PostToolUse` hooks with stable `tool_name` values. Therefore VBW cannot hook-validate malformed shutdown responses at runtime. Enforcement relies on: (1) the mechanical SendMessage handshake the orchestrator appends to each teammate's spawn prompt in true team mode (delivered only when a real team exists — also mirrored in the team-spawn prompts of `/vbw:map` and `/vbw:debug`), (2) compaction-instructions.sh reminders that survive context compaction (gated on a live team marker), (3) orchestrator retry (re-send if teammate responds in plain text), and (4) `/vbw:doctor --cleanup` as a recovery path for stuck teams. **Worktree merge and cleanup (post-TeamDelete):** If `worktree_isolation` is not `"off"` in config: For each plan that has a `worktree_path` entry in execution-state.json (completed or failed): diff --git a/references/handoff-schemas.md b/references/handoff-schemas.md index 17baa6863..cd42aedd0 100644 --- a/references/handoff-schemas.md +++ b/references/handoff-schemas.md @@ -327,7 +327,7 @@ Acknowledgment from a teammate that it will terminate. ### Delivery format -Claude Code delivers `shutdown_request` as JSON text in the SendMessage inbox. The delivered message may not include the full V2 envelope — it may arrive as a simpler JSON object with just `type`, `id`, `from` (corresponds to `author_role` in the V2 envelope), `reason`, and `timestamp`. Agents MUST recognize `"type":"shutdown_request"` in the received message text regardless of envelope structure, and respond by **calling the SendMessage tool** with a `shutdown_response` (not plain text). +This handshake applies only inside a live agent team, where the `SendMessage` tool exists. The orchestrator delivers the teammate-side handler to each teammate at spawn time (see `references/execute-protocol.md` and the `/vbw:map`/`/vbw:debug` team-spawn prompts) — it is not baked into the static agent prompts, so non-team subagent runs never receive it. Within a team, Claude Code delivers `shutdown_request` as JSON text in the SendMessage inbox. The delivered message may not include the full V2 envelope — it may arrive as a simpler JSON object with just `type`, `id`, `from` (corresponds to `author_role` in the V2 envelope), `reason`, and `timestamp`. Agents MUST recognize `"type":"shutdown_request"` in the received message text regardless of envelope structure, and respond by **calling the SendMessage tool** with a `shutdown_response` (not plain text). On receiving `shutdown_request`: finish any in-progress tool call, **call the SendMessage tool** with `shutdown_response` (`request_id` echoed from the request, `approved: true`, `final_status`), then STOP all further work. Do NOT start new tasks, fix additional issues, or take any action after responding. The orchestrator will call TeamDelete after collecting all responses. **Plain text acknowledgement does NOT satisfy the shutdown protocol — you MUST call the SendMessage tool.** From c1e4451c50ffe18ccf7db1452e03c775f6a1d0c1 Mon Sep 17 00:00:00 2001 From: Shane McCarron Date: Fri, 29 May 2026 14:12:59 -0500 Subject: [PATCH 3/6] fix(645): address QA round 2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit QA round 2 (Claude Opus 4.8) verdict: clean (approve). Team-spawn coverage confirmed complete (only debug.md/map.md/vibe.md use TeamCreate; vibe.md delegates to execute-protocol.md which carries the gated block; research/fix/qa use non-team subagents only). Two minor items, both optional: - [minor, addressed] Over-escaped JSON quotes inside inline code spans in the round-1 map.md/debug.md handler insertions (backslashes are literal inside a code span). Unescaped to clean JSON — functionally equivalent, cleaner to read and to copy into the spawned task body. - [minor, left as-is] map.md places the handler under the shared "Scout communication" section rather than inline like debug.md. The reviewer confirmed this is consistent with map.md's existing pattern of deferring all Scout task-body content to that later section; no functional risk. Verified: verify-commands-contract 521/521, shutdown-protocol.bats 71/71. Co-Authored-By: Claude Opus 4.8 --- commands/debug.md | 2 +- commands/map.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/commands/debug.md b/commands/debug.md index 4a4e15ad9..11c0555e1 100644 --- a/commands/debug.md +++ b/commands/debug.md @@ -241,7 +241,7 @@ If resuming a session with `session_status=complete`: STOP "This debug session i RESEARCH_CONTEXT=$(bash "{plugin-root}/scripts/compile-research-context.sh" .vbw-planning "{bug description from Step 1}" 2>/dev/null || echo "") ``` Replace `{bug description from Step 1}` with the actual parsed bug description. If `RESEARCH_CONTEXT` is non-empty, include it in each Debugger task prompt below. If empty, omit the `` block entirely. - - Create 3 tasks via TaskCreate, each with: bug report, standalone research context (include ONLY if RESEARCH_CONTEXT was non-empty: `Prior research findings from /vbw:research. Advisory — verify all claims against the current codebase before relying on them.\n{RESEARCH_CONTEXT}`), extended context from todo detail only when the detail has actual signal from Step 1 (include `detail.context` and `detail.files` — omit this section entirely when detail was absent or structurally empty), and if detail did not have signal but bounded sparse-context enrichment from Step 1 returned `ok`, include the enrichment summary plus matched files and markers as a compact hint. Omit this enrichment section entirely if the helper returned `no_signal` or `no_match`. Include ONE hypothesis only (no cross-contamination), working dir, codebase bootstrap instruction ("If `.vbw-planning/codebase/META.md` exists, read ARCHITECTURE.md, CONCERNS.md, PATTERNS.md, and DEPENDENCIES.md (whichever exist) from `.vbw-planning/codebase/` to bootstrap codebase understanding before investigating"), instruction to report via `debugger_report` schema (see `{plugin-root}/references/handoff-schemas.md`) including explicit `resolution_observation=already_fixed|needs_change|inconclusive`, instruction that `resolution_observation` is analysis-scoped only (teammates do not own the final command outcome or session status), and instruction that accepted-process-exception/backlog metadata alone is not enough for `already_fixed`; `already_fixed` requires fresh current evidence that the underlying issue no longer reproduces or the branch contains a real fix. Explicit report-only contract: "You are a hypothesis investigator, not the implementation owner. Do NOT edit files, apply fixes, run mutating Bash, commit, request implementation approval, or claim ownership of the final session outcome. Stop after diagnosis plus evidence reporting via `debugger_report`." Also include instruction: "If investigation reveals pre-existing failures unrelated to this bug, list them in your response under a 'Pre-existing Issues' heading with test name, file, and failure message." **Include `[analysis-only]` in each task subject** (e.g., "Hypothesis 1: race condition in sync handler [analysis-only]") so the TaskCompleted hook skips the commit-verification gate for report-only tasks. Keep `[analysis-only]` as an advisory hook marker only — it does NOT replace the report-only contract above. **Teammate shutdown handler (Path A team only):** because these investigators run inside a real team and the teardown gate below waits for a tool-call response, also include this in each task body: "When you receive a message containing `\"type\":\"shutdown_request\"` (or `shutdown_request` in the text), finish any in-progress tool call, then **call the `SendMessage` tool** with `{\"type\": \"shutdown_response\", \"approved\": true, \"request_id\": \"\", \"final_status\": \"complete|idle|in_progress\"}`, then STOP. Plain text acknowledgement does NOT satisfy the handshake — you MUST call `SendMessage`." + - Create 3 tasks via TaskCreate, each with: bug report, standalone research context (include ONLY if RESEARCH_CONTEXT was non-empty: `Prior research findings from /vbw:research. Advisory — verify all claims against the current codebase before relying on them.\n{RESEARCH_CONTEXT}`), extended context from todo detail only when the detail has actual signal from Step 1 (include `detail.context` and `detail.files` — omit this section entirely when detail was absent or structurally empty), and if detail did not have signal but bounded sparse-context enrichment from Step 1 returned `ok`, include the enrichment summary plus matched files and markers as a compact hint. Omit this enrichment section entirely if the helper returned `no_signal` or `no_match`. Include ONE hypothesis only (no cross-contamination), working dir, codebase bootstrap instruction ("If `.vbw-planning/codebase/META.md` exists, read ARCHITECTURE.md, CONCERNS.md, PATTERNS.md, and DEPENDENCIES.md (whichever exist) from `.vbw-planning/codebase/` to bootstrap codebase understanding before investigating"), instruction to report via `debugger_report` schema (see `{plugin-root}/references/handoff-schemas.md`) including explicit `resolution_observation=already_fixed|needs_change|inconclusive`, instruction that `resolution_observation` is analysis-scoped only (teammates do not own the final command outcome or session status), and instruction that accepted-process-exception/backlog metadata alone is not enough for `already_fixed`; `already_fixed` requires fresh current evidence that the underlying issue no longer reproduces or the branch contains a real fix. Explicit report-only contract: "You are a hypothesis investigator, not the implementation owner. Do NOT edit files, apply fixes, run mutating Bash, commit, request implementation approval, or claim ownership of the final session outcome. Stop after diagnosis plus evidence reporting via `debugger_report`." Also include instruction: "If investigation reveals pre-existing failures unrelated to this bug, list them in your response under a 'Pre-existing Issues' heading with test name, file, and failure message." **Include `[analysis-only]` in each task subject** (e.g., "Hypothesis 1: race condition in sync handler [analysis-only]") so the TaskCompleted hook skips the commit-verification gate for report-only tasks. Keep `[analysis-only]` as an advisory hook marker only — it does NOT replace the report-only contract above. **Teammate shutdown handler (Path A team only):** because these investigators run inside a real team and the teardown gate below waits for a tool-call response, also include this in each task body: "When you receive a message containing `"type":"shutdown_request"` (or `shutdown_request` in the text), finish any in-progress tool call, then **call the `SendMessage` tool** with `{"type": "shutdown_response", "approved": true, "request_id": "", "final_status": "complete|idle|in_progress"}`, then STOP. Plain text acknowledgement does NOT satisfy the handshake — you MUST call `SendMessage`." - Spawn 3 vbw-debugger teammates, one task each. **Set `subagent_type: "vbw:vbw-debugger"` and `model: "${DEBUGGER_MODEL}"` on each Task spawn. If `DEBUGGER_MAX_TURNS` is non-empty, also pass `maxTurns: ${DEBUGGER_MAX_TURNS}`. If `DEBUGGER_MAX_TURNS` is empty, do NOT include maxTurns (omitting it = unlimited).** True-team spawn shape: pass the exact TeamCreate `team_name` and a unique per-teammate `name` for each hypothesis investigator (for example `debug-hypothesis-1`, `debug-hypothesis-2`, `debug-hypothesis-3`). Do not pass `isolation`, `cwd`, `working_dir`, `workingDirectory`, or `workdir`. - **Investigation phase:** Wait until ALL spawned hypothesis investigators have returned `debugger_report`. Then synthesize: strongest evidence + highest confidence wins. Multiple confirmed = contributing factors. After synthesis, choose one authoritative `RESOLUTION_OBSERVATION` value for the command from the teammate reports: `already_fixed` only when fresh current evidence proves the current branch already contains the fix and no new change is needed, `needs_change` when code changes were required or would still be required, `inconclusive` when the evidence is not yet strong enough. For selected accepted-exception/backlog or UAT-deviation items, accepted-process-exception/backlog metadata alone is not enough for `already_fixed`; require fresh current evidence that the underlying issue no longer reproduces or the branch contains a real fix. If any teammate finds the selected item still reproducible or actionable, choose `needs_change`. If remediation is blocked or unsafe without more input, choose `inconclusive` and leave the session unresolved. - Choose one authoritative analysis-scoped `RESOLUTION_OBSERVATION` summary for user-facing presentation. Collect pre-existing issues from all debugger responses. De-duplicate by test name and file (keep first error message when the same test+file pair has different messages) — if multiple debuggers report the same pre-existing failure, include it only once. diff --git a/commands/map.md b/commands/map.md index e9afcd70a..be1696abb 100644 --- a/commands/map.md +++ b/commands/map.md @@ -190,7 +190,7 @@ After calling `Skill(...)`, if the loaded skill's instructions reference additio Use targeted `message` not `broadcast`. Wait for all findings. Display ✓ per scout. -**Teammate shutdown handler (duo/quad only — append to each Scout task body):** Because these Scouts run inside a real team and the teardown gate above waits for a tool-call response, include this in each Scout's task prompt: "When you receive a message containing `\"type\":\"shutdown_request\"` (or `shutdown_request` in the text), finish any in-progress tool call, then **call the `SendMessage` tool** with `{\"type\": \"shutdown_response\", \"approved\": true, \"request_id\": \"\", \"final_status\": \"complete|idle|in_progress\"}`, then STOP. Plain text acknowledgement does NOT satisfy the handshake — you MUST call `SendMessage`." Solo mode spawns no team, so omit it there. +**Teammate shutdown handler (duo/quad only — append to each Scout task body):** Because these Scouts run inside a real team and the teardown gate above waits for a tool-call response, include this in each Scout's task prompt: "When you receive a message containing `"type":"shutdown_request"` (or `shutdown_request` in the text), finish any in-progress tool call, then **call the `SendMessage` tool** with `{"type": "shutdown_response", "approved": true, "request_id": "", "final_status": "complete|idle|in_progress"}`, then STOP. Plain text acknowledgement does NOT satisfy the handshake — you MUST call `SendMessage`." Solo mode spawns no team, so omit it there. ### Step 3.5: Verify mapping documents written by Scouts From f2702cf2b2c109d8f840c18643098859e3ec9491 Mon Sep 17 00:00:00 2001 From: Shane McCarron Date: Fri, 29 May 2026 14:16:45 -0500 Subject: [PATCH 4/6] fix(645): address QA round 3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit QA round 3 (Claude Opus 4.8) verdict: CLEAN — no critical, major, or minor findings. Empty evidence commit per CONTRIBUTING.md (clean round still records an evidence commit in this format). Confirmed by the reviewer: - All three handler copies (execute-protocol.md, map.md, debug.md) specify the same fields (type, approved, request_id, final_status, complete|idle|in_progress) and the "call SendMessage / plain text not sufficient" mandate — no drift. - Round-2 quote-unescaping is a 1-line change per file; the JSON parses; no markdown/contract regression. - Team-spawn coverage complete; no non-team path received the handler. - compaction gate fail-closed; SCRIPT_DIR single-defined before use; snapshot stdout leak fixed. - bats sanity: live/non-team gate tests form a real positive+negative pair; schema-field test derives fields dynamically (not vacuous). - Serial runs: shutdown-protocol 71/71, verify-commands-contract 521/521, verify-bash-scripts-contract 669/669, verify-lsp-first-policy 49/49, verify-plugin-root-resolution 19/19, verify-claude-bootstrap 58/58, agent-shutdown-integration 26/26, two-phase-completion 20/20, crash-recovery 20/20, event-type-validation 5/5; shellcheck/bash -n clean. Co-Authored-By: Claude Opus 4.8 From 80507618dd1b33bb8bcfb20881597b51db8c2bbc Mon Sep 17 00:00:00 2001 From: Shane McCarron Date: Tue, 16 Jun 2026 07:07:04 -0500 Subject: [PATCH 5/6] ci: re-trigger checks after stale failure (#645) The branch is current with main and the full suite passes locally (3583 BATS, 51/51 contract, lint). The prior CI failure predated a fix now present in both the branch and main. Co-Authored-By: Claude Opus 4.8 From a48c0d8fb29cb68c0fbd9bbea91a026e002335c3 Mon Sep 17 00:00:00 2001 From: Shane McCarron Date: Thu, 18 Jun 2026 21:42:30 -0500 Subject: [PATCH 6/6] fix(debug): gate /vbw:debug Path A on team-tooling availability (#645) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses PR #646 review (VBW-PR-001, VBW-PR-002, VBW-PR-003). VBW-PR-001: Path A (Competing Hypotheses) is a real agent team — it calls TeamCreate, spawns teammates with team_name, and its teardown HARD GATE waits for SendMessage shutdown_response tool calls. The routing decision selected Path A purely on prefer_teams/effort/ambiguity, so in an environment without agent teams / SendMessage (the default — CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS is 0/unset) it would emit a TeamCreate/SendMessage-dependent body and then hang at teardown waiting for responses that can never arrive. Add a hard team-tooling precondition evaluated before the prefer_teams tree: require the Agent Teams flag plus TeamCreate/SendMessage in the live tool set, else force Path B (single debugger) with a "⚠ Agent Teams not enabled" notice. Surfaced the Agent Teams flag in the command Context (mirrors map.md) and tightened the Path A header. VBW-PR-002: added three contract tests in tests/shutdown-protocol.bats asserting debug.md surfaces the flag, gates Path A on the precondition (requiring SendMessage/TeamCreate and forcing Path B when unavailable), and that the Path A header carries the precondition. VBW-PR-003: README.md "Shutdown coordination" said all 6 agents carry explicit SendMessage shutdown handlers — stale after this PR moved handlers to spawn-time (team-mode-only) delivery. Reworded to match. Tests: bash testing/run-all.sh green (3586 BATS, 0 failed); the 3 new gate tests pass. (A flaky parallel-shard failure in the untouched phase-detect.bats appeared once and cleared on re-run — passes 3/3 in isolation.) Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01QFFmV2TntcmvJRjvcv3gAD --- README.md | 2 +- commands/debug.md | 14 ++++++++++++-- tests/shutdown-protocol.bats | 37 ++++++++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index cb6a93b87..65da26f07 100644 --- a/README.md +++ b/README.md @@ -115,7 +115,7 @@ Agent Teams are [experimental with known limitations](https://code.claude.com/do - **Task status lag.** Teammates sometimes forget to mark tasks complete. VBW's `TaskCompleted` hook treats commit matching as an advisory signal for execute-protocol tasks instead of a universal blocking gate, so manual or non-code tasks do not get stranded at `in_progress` when no commit exists or the wording diverges. The `TeammateIdle` hook runs a tiered SUMMARY.md gate — all summaries present passes immediately, conventional commit format only grants a 1-plan grace period, and 2+ missing summaries block regardless. -- **Shutdown coordination.** VBW defines `shutdown_request`/`shutdown_response` schemas in the typed communication protocol. After a true team run completes, the orchestrator sends `shutdown_request` to every teammate, waits for acknowledgment, then calls `TeamDelete`. Serialized subagent, turbo, and internal direct runs skip team shutdown because no team was created. All 6 team-participating agents (Dev, QA, Scout, Lead, Debugger, Docs) have explicit shutdown handlers with mechanical SendMessage tool-call instructions. Architect is planning-only and excluded from the shutdown protocol. If shutdown stalls or agents linger, `/vbw:doctor --cleanup` detects and cleans stale teams, orphan processes, and dangling PIDs. +- **Shutdown coordination.** VBW defines `shutdown_request`/`shutdown_response` schemas in the typed communication protocol. After a true team run completes, the orchestrator sends `shutdown_request` to every teammate, waits for acknowledgment, then calls `TeamDelete`. Serialized subagent, turbo, and internal direct runs skip team shutdown because no team was created. Because `SendMessage` exists only inside a live agent team, the teammate-side shutdown handler is delivered by the orchestrator at spawn time (team mode only) for the 6 team-participating agents (Dev, QA, Scout, Lead, Debugger, Docs) — it is deliberately NOT baked into the static agent prompts, so a non-team run never carries an un-followable SendMessage mandate. Architect is planning-only and excluded from the shutdown protocol. If shutdown stalls or agents linger, `/vbw:doctor --cleanup` detects and cleans stale teams, orphan processes, and dangling PIDs. - **File conflicts.** Plans decompose work into tasks with explicit file ownership. Dev teammates operate on disjoint file sets by design, enforced at runtime by the `file-guard.sh` hook that blocks writes to files not declared in the active plan. diff --git a/commands/debug.md b/commands/debug.md index 11c0555e1..8380fd9ba 100644 --- a/commands/debug.md +++ b/commands/debug.md @@ -23,6 +23,10 @@ Recent commits: ```text !`git log --oneline -10 2>/dev/null || echo "No git history"` ``` +Agent Teams: +```text +!`echo "${CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS:-0}"` +``` Store the plugin root path output above as `{plugin-root}` for use in script invocations below. Replace `{plugin-root}` with the literal `Plugin root` value from Context whenever a step below references a script or reference file. @@ -186,7 +190,13 @@ If resuming a session with `session_status=complete`: STOP "This debug session i bash "{plugin-root}/scripts/delegated-workflow.sh" set debug "$EFFORT_PROFILE" ``` - Decision tree: + **Team-tooling precondition (hard gate, evaluated FIRST — #645):** Path A is a real agent team: it calls `TeamCreate`, spawns teammates with `team_name`, and its teardown HARD GATE waits for `SendMessage` `shutdown_response` tool calls. Those tools exist only when agent teams are enabled. Before applying the `prefer_teams` decision tree, confirm real team semantics are available: + - The `Agent Teams` value from Context must be `1` (it mirrors `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS`, which is `0`/unset by default), **and** + - `TeamCreate` and `SendMessage` must be present in the live tool set (plain background `Agent` spawns without `team_name` are NOT a team). + + If either is missing, **force Path B** regardless of `prefer_teams`, effort, or ambiguity — never emit a `TeamCreate`/`SendMessage`-dependent Path A body into an environment that cannot complete the shutdown handshake (it would hang waiting for responses that can never arrive). Display `⚠ Agent Teams not enabled — using single-debugger mode (Path B)` and skip the `prefer_teams` tree below. + + Decision tree (applies only when the team-tooling precondition above is satisfied): - `prefer_teams='always'`: Use Path A (team) for ALL bugs, regardless of effort or ambiguity - `prefer_teams='auto'`: Use Path A (team) only if effort=high AND ambiguous, else Path B @@ -199,7 +209,7 @@ If resuming a session with `session_status=complete`: STOP "This debug session i ``` Treat `HEAD_BEFORE` as the pre-investigation baseline for Step 5. Do not use commit presence alone to infer whether this investigation created a new fix. - **Path A: Competing Hypotheses** (prefer_teams='always' OR (prefer_teams!='never' AND effort=high AND ambiguous)): + **Path A: Competing Hypotheses** (team-tooling precondition satisfied AND (prefer_teams='always' OR (prefer_teams!='never' AND effort=high AND ambiguous))): - Generate 3 hypotheses (cause, codebase area, confirming evidence) - Resolve Debugger model: ```bash diff --git a/tests/shutdown-protocol.bats b/tests/shutdown-protocol.bats index 943adc1c5..6f4e78e1d 100644 --- a/tests/shutdown-protocol.bats +++ b/tests/shutdown-protocol.bats @@ -80,6 +80,43 @@ teardown() { | grep -qi 'NOT satisfy\|NOT sufficient' } +# ============================================================================= +# Issue #645: /vbw:debug Path A is a real agent team (TeamCreate + SendMessage +# teardown gate). It must be gated on team-tooling availability and fall back to +# the non-team Path B when agent teams / SendMessage are unavailable — otherwise +# the teardown HARD GATE hangs waiting for shutdown_response calls that can never +# arrive. (Reproduction path for VBW-PR-002 on PR #646.) +# ============================================================================= + +@test "debug.md surfaces Agent Teams availability (CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS) in Context" { + grep -q 'CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS' "$PROJECT_ROOT/commands/debug.md" || { + echo "debug.md must surface the Agent Teams flag so routing can gate Path A on it (#645)" + return 1 + } +} + +@test "debug.md Path A is gated on team-tooling availability before the prefer_teams tree" { + # The team-tooling precondition must (a) exist, (b) require the Agent Teams flag + # and SendMessage/TeamCreate, and (c) force Path B when unavailable. + local section + section=$(sed -n '/Routing decision + delegation marker/,/Path B: Standard/p' "$PROJECT_ROOT/commands/debug.md") + echo "$section" | grep -qi 'team-tooling precondition' || { + echo "debug.md routing must document a team-tooling precondition gating Path A (#645)" + return 1 + } + echo "$section" | grep -qi 'SendMessage' + echo "$section" | grep -qi 'TeamCreate' + echo "$section" | grep -qi 'force Path B' +} + +@test "debug.md Path A header requires the team-tooling precondition" { + grep -q 'Path A: Competing Hypotheses.*team-tooling precondition satisfied' \ + "$PROJECT_ROOT/commands/debug.md" || { + echo "Path A header must require the team-tooling precondition (#645)" + return 1 + } +} + # ============================================================================= # Handoff schemas: prose documentation consistency # =============================================================================