diff --git a/.changeset/566-spawn-liveness-banner.md b/.changeset/566-spawn-liveness-banner.md
new file mode 100644
index 000000000..233452bc2
--- /dev/null
+++ b/.changeset/566-spawn-liveness-banner.md
@@ -0,0 +1,5 @@
+---
+type: Changed
+pr: 566
+---
+**Spawn announcements now include a liveness note** — every `◆ Spawning …` line and subagent dispatch instruction across all 26+ GSD workflows now carries the canonical phrase `runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze`. Silent subagents are visually identical to frozen sessions; this inline note sets the expectation so users wait instead of interrupting. Documented in `references/ui-brand.md § Spawning Indicators`, enforced by `tests/spawn-liveness-banner.test.cjs`, and explained in `docs/USER-GUIDE.md § Troubleshooting`.
diff --git a/docs/USER-GUIDE.md b/docs/USER-GUIDE.md
index 03dd832f1..de22e0fad 100644
--- a/docs/USER-GUIDE.md
+++ b/docs/USER-GUIDE.md
@@ -1126,6 +1126,14 @@ Always read a file before editing it. Never call Edit or Write on a file you hav
You ran `/gsd-new-project` but `.planning/PROJECT.md` already exists. This is a safety check. If you want to start over, delete the `.planning/` directory first.
+### A Command Looks Frozen After "Spawning..."
+
+If you see `◆ Spawning researcher...` (or any "Spawning…" line) and then nothing — no output, no spinner — for 1–5 minutes, **this is normal**. GSD subagents run in a separate context window; their work is invisible to the parent session while in progress. The liveness note on the spawn line confirms this: "(runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)".
+
+**Do not interrupt the session.** Killing it discards the in-progress subagent work and forces you to restart that step. Wait for the result to appear. Research and planning agents routinely take 1–5 minutes; verification agents can take longer on large phases.
+
+**If it truly seems stuck** (>10 minutes with no result): check whether Claude Code's agent task is still active in the sidebar. If the task shows as completed but no output appeared, the result may have been lost in a context switch — run the command again.
+
### Context Degradation During Long Sessions
Clear your context window between major commands: `/clear` in Claude Code. GSD is designed around fresh contexts -- every subagent gets a clean 200K window. If quality is dropping in the main session, clear and use `/gsd-resume-work` or `/gsd-progress` to restore state.
diff --git a/get-shit-done/references/ui-brand.md b/get-shit-done/references/ui-brand.md
index 47e6f741c..9a9676b78 100644
--- a/get-shit-done/references/ui-brand.md
+++ b/get-shit-done/references/ui-brand.md
@@ -83,10 +83,12 @@ Plans: 3/5 complete
## Spawning Indicators
-```
-◆ Spawning researcher...
+**Liveness convention:** Every spawn announcement must carry the canonical phrase `runs in a subagent` inline so users know that silence during a subagent run is expected. Without this, a healthy 1–5 minute agent looks identical to a frozen session. Single spawns use the singular form; parallel spawns use the plural form.
-◆ Spawning 4 researchers in parallel...
+```
+◆ Spawning researcher... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
+
+◆ Spawning 4 researchers in parallel... (each runs in a subagent — no output until they return, ~1–5 min; expected, not a freeze)
→ Stack research
→ Features research
→ Architecture research
diff --git a/get-shit-done/workflows/audit-fix.md b/get-shit-done/workflows/audit-fix.md
index e6474bbe1..884d75ae9 100644
--- a/get-shit-done/workflows/audit-fix.md
+++ b/get-shit-done/workflows/audit-fix.md
@@ -94,7 +94,7 @@ final output — do not proceed to fixing.
For each **auto-fixable** finding (up to `--max`, ordered by severity desc):
-**a. Spawn executor agent:**
+**a. Spawn executor agent** (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)**:**
```
Agent(
prompt="Fix finding {ID}: {description}. Files: {file_refs}. Make the minimal change to resolve this specific finding. Do not refactor surrounding code.",
diff --git a/get-shit-done/workflows/audit-milestone.md b/get-shit-done/workflows/audit-milestone.md
index 709fe2278..579349c4c 100644
--- a/get-shit-done/workflows/audit-milestone.md
+++ b/get-shit-done/workflows/audit-milestone.md
@@ -67,6 +67,8 @@ With phase context collected:
Extract `MILESTONE_REQ_IDS` from REQUIREMENTS.md traceability table — all REQ-IDs assigned to phases in this milestone.
+Print: "Spawning integration checker (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)"
+
```
Agent(
prompt="Check cross-phase integration and E2E flows.
diff --git a/get-shit-done/workflows/autonomous.md b/get-shit-done/workflows/autonomous.md
index cae91ebfd..70e9f346d 100644
--- a/get-shit-done/workflows/autonomous.md
+++ b/get-shit-done/workflows/autonomous.md
@@ -324,6 +324,8 @@ UI_SPEC_FILE=$(ls "${PHASE_DIR}"/*-UI-SPEC.md 2>/dev/null | head -1)
**If `INTERACTIVE` is set:** Dispatch plan as a background agent to keep the main context lean. While plan runs, the workflow can immediately start discussing the next phase (see step 4).
+Print: `◆ Spawning background planner for phase ${PHASE_NUM}... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)`
+
```
Agent(
description="Plan phase ${PHASE_NUM}: ${PHASE_NAME}",
diff --git a/get-shit-done/workflows/code-review-fix.md b/get-shit-done/workflows/code-review-fix.md
index eedb60e11..3fa6e211f 100644
--- a/get-shit-done/workflows/code-review-fix.md
+++ b/get-shit-done/workflows/code-review-fix.md
@@ -179,7 +179,7 @@ If REVIEW.md contains a `files_reviewed_list` frontmatter field, use that as the
-Spawn the gsd-code-fixer agent with config:
+Spawn the gsd-code-fixer agent with config (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze):
```bash
# Build config for agent
@@ -271,7 +271,7 @@ if [ "$AUTO_MODE" = "true" ]; then
done
fi
- # Spawn gsd-code-reviewer agent to re-review
+ # Spawn gsd-code-reviewer agent to re-review (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
# (This overwrites REVIEW_PATH with latest review state)
Agent(subagent_type="gsd-code-reviewer", prompt="
@@ -304,7 +304,7 @@ Do NOT commit the output — the orchestrator handles that.
break
fi
- # Still has issues — spawn fixer again
+ # Still has issues — spawn fixer again (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
echo "Issues remain. Applying fixes for iteration ${ITERATION}..."
Agent(subagent_type="gsd-code-fixer", prompt="
diff --git a/get-shit-done/workflows/code-review.md b/get-shit-done/workflows/code-review.md
index 974395668..9d41b6426 100644
--- a/get-shit-done/workflows/code-review.md
+++ b/get-shit-done/workflows/code-review.md
@@ -441,6 +441,8 @@ fi
Spawn the gsd-code-reviewer agent:
+Print: `◆ Spawning code reviewer... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)`
+
```
Agent(subagent_type="gsd-code-reviewer", prompt="
diff --git a/get-shit-done/workflows/debug.md b/get-shit-done/workflows/debug.md
index dec8d8495..bae8e6568 100644
--- a/get-shit-done/workflows/debug.md
+++ b/get-shit-done/workflows/debug.md
@@ -101,7 +101,7 @@ Eliminated: {count}
Surface to user. Then delegate directly to the session manager (skip Steps 2 and 3 — pass `symptoms_prefilled: true` and set the slug from SLUG variable). The existing file IS the context.
-Print before spawning:
+Print before spawning (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze):
```
[debug] Session: .planning/debug/{SLUG}.md
[debug] Status: {status}
@@ -190,6 +190,11 @@ Create `.planning/debug/{slug}.md` with initial state using the Write tool (neve
After initial context setup, spawn the session manager to handle the full checkpoint/continuation loop. The session manager handles specialist_hint dispatch internally: when gsd-debugger returns ROOT CAUSE FOUND it extracts the specialist_hint field and invokes the matching skill (e.g. typescript-expert, swift-concurrency) before offering fix options.
+Print before spawning (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze):
+```
+[debug] Delegating loop to session manager...
+```
+
```
Agent(
prompt="""
diff --git a/get-shit-done/workflows/diagnose-issues.md b/get-shit-done/workflows/diagnose-issues.md
index 003f84d2b..06a19ca05 100644
--- a/get-shit-done/workflows/diagnose-issues.md
+++ b/get-shit-done/workflows/diagnose-issues.md
@@ -96,6 +96,8 @@ EXPECTED_BASE=$(git rev-parse HEAD)
For each gap, fill the debug-subagent-prompt template and spawn:
+Print: `◆ Spawning diagnostics agent... (each runs in a subagent — no output until they return, ~1–5 min; expected, not a freeze)`
+
```
Agent(
prompt=filled_debug_subagent_prompt + "\n\n\nFIRST ACTION: assert this is a disposable worktree branch before any repair. Run:\n```bash\nHEAD_REF=$(git symbolic-ref --quiet HEAD || echo \"DETACHED\")\nACTUAL_BRANCH=$(git rev-parse --abbrev-ref HEAD)\nif [ \"$HEAD_REF\" = \"DETACHED\" ] || echo \"$ACTUAL_BRANCH\" | grep -Eq '^(main|master|develop|trunk|release/.*)$'; then\n echo \"FATAL: diagnose worktree HEAD on '$ACTUAL_BRANCH'; refusing reset --hard on a protected branch.\" >&2\n exit 1\nfi\nif ! echo \"$ACTUAL_BRANCH\" | grep -Eq '^worktree-agent-[A-Za-z0-9._/-]+$'; then\n echo \"FATAL: diagnose worktree HEAD '$ACTUAL_BRANCH' is not in the worktree-agent-* namespace; refusing reset --hard.\" >&2\n exit 1\nfi\nACTUAL_BASE=$(git merge-base HEAD {EXPECTED_BASE})\nif [ \"$ACTUAL_BASE\" != \"{EXPECTED_BASE}\" ]; then\n git reset --hard {EXPECTED_BASE}\n [ \"$(git rev-parse HEAD)\" != \"{EXPECTED_BASE}\" ] && { echo \"ERROR: Could not correct worktree base\"; exit 1; }\nfi\n```\nFixes EnterWorktree creating branches from main on all platforms while preventing protected-branch data loss.\n\n\n\n- {phase_dir}/{phase_num}-UAT.md\n- .planning/STATE.md\n\n${AGENT_SKILLS_DEBUGGER}",
diff --git a/get-shit-done/workflows/discuss-phase-assumptions.md b/get-shit-done/workflows/discuss-phase-assumptions.md
index b674b7883..2ccf8ed24 100644
--- a/get-shit-done/workflows/discuss-phase-assumptions.md
+++ b/get-shit-done/workflows/discuss-phase-assumptions.md
@@ -252,7 +252,7 @@ Map to calibration tier:
If no USER-PROFILE.md: calibration_tier = "standard"
-**Spawn Explore subagent:**
+**Spawn Explore subagent** (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)**:**
```
Agent(subagent_type="gsd-assumptions-analyzer", prompt="""
@@ -310,7 +310,7 @@ Parse the subagent's response. Extract:
**Skip if:** `needs_research` from deep_codebase_analysis is empty.
-If research topics were flagged, spawn a general-purpose research agent:
+If research topics were flagged, spawn a general-purpose research agent (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze):
```
Agent(subagent_type="general-purpose", prompt="""
diff --git a/get-shit-done/workflows/discuss-phase/modes/advisor.md b/get-shit-done/workflows/discuss-phase/modes/advisor.md
index b6be0d09b..f6a75c883 100644
--- a/get-shit-done/workflows/discuss-phase/modes/advisor.md
+++ b/get-shit-done/workflows/discuss-phase/modes/advisor.md
@@ -86,7 +86,7 @@ This reframing applies to:
After the user selects gray areas in `present_gray_areas`, spawn parallel
research agents.
-1. Display brief status: `Researching {N} areas...`
+1. Display brief status: `Researching {N} areas...` (each runs in a subagent — no output until they return, ~1–5 min; expected, not a freeze)
2. For EACH user-selected gray area, spawn a `Agent()` in parallel:
diff --git a/get-shit-done/workflows/docs-update.md b/get-shit-done/workflows/docs-update.md
index caf274440..0498bc4f6 100644
--- a/get-shit-done/workflows/docs-update.md
+++ b/get-shit-done/workflows/docs-update.md
@@ -374,7 +374,7 @@ After all decisions recorded, continue to detect_runtime_capabilities.
**Read the work manifest first:** `Read .planning/tmp/docs-work-manifest.json` — use `canonical_queue` items with `wave: 1` for this step.
-Spawn 3 parallel gsd-doc-writer agents for Wave 1 docs: README, ARCHITECTURE, CONFIGURATION.
+Spawn 3 parallel gsd-doc-writer agents for Wave 1 docs: README, ARCHITECTURE, CONFIGURATION (each runs in a subagent — no output until they return, ~1–5 min; expected, not a freeze).
These are foundational docs with no cross-references needed, making them ideal for parallel generation.
@@ -831,7 +831,8 @@ Extract `canonical_queue` (items with `status: "completed"`) and `review_queue`
For each doc in `canonical_queue` that was successfully written to disk:
-1. Spawn the `gsd-doc-verifier` agent (or invoke sequentially if Task tool is unavailable) with a `` block:
+1. Print: `◆ Spawning doc verifier for {doc_path}... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)`
+ Spawn the `gsd-doc-verifier` agent (or invoke sequentially if Task tool is unavailable) with a `` block:
```xml
doc_path: {relative path to the doc file, e.g. README.md}
@@ -849,7 +850,8 @@ This is NOT optional. Every doc in `review_queue` MUST be verified.
For each doc in `review_queue` from the manifest:
-1. Spawn the `gsd-doc-verifier` agent with the same `` block as above.
+1. Print: `◆ Spawning doc verifier for {doc_path}... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)`
+ Spawn the `gsd-doc-verifier` agent with the same `` block as above.
2. Read the result JSON from `.planning/tmp/verify-{doc_filename}.json`.
3. Update the manifest: set `status: "verified"` for each review_queue doc processed.
diff --git a/get-shit-done/workflows/eval-review.md b/get-shit-done/workflows/eval-review.md
index 19ad488f8..c39705225 100644
--- a/get-shit-done/workflows/eval-review.md
+++ b/get-shit-done/workflows/eval-review.md
@@ -73,7 +73,7 @@ Build file list for auditor:
## 3. Spawn gsd-eval-auditor
```
-◆ Spawning eval auditor...
+◆ Spawning eval auditor... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
```
Build prompt:
diff --git a/get-shit-done/workflows/execute-phase.md b/get-shit-done/workflows/execute-phase.md
index 213d8a574..67810c036 100644
--- a/get-shit-done/workflows/execute-phase.md
+++ b/get-shit-done/workflows/execute-phase.md
@@ -491,7 +491,7 @@ increases monotonically across waves. `{status}` is `complete` (success),
**{Plan ID}: {Plan Name}**
{2-3 sentences: what this builds, technical approach, why it matters}
- Spawning {count} agent(s)...
+ Spawning {count} agent(s)... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
---
```
diff --git a/get-shit-done/workflows/execute-phase/steps/codebase-drift-gate.md b/get-shit-done/workflows/execute-phase/steps/codebase-drift-gate.md
index 648ea796d..1aa4957a3 100644
--- a/get-shit-done/workflows/execute-phase/steps/codebase-drift-gate.md
+++ b/get-shit-done/workflows/execute-phase/steps/codebase-drift-gate.md
@@ -49,7 +49,7 @@ _GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-pars
AGENT_SKILLS_MAPPER=$(gsd_run query agent-skills gsd-codebase-mapper)
```
-Then spawn `gsd-codebase-mapper` agents with the `--paths` hint:
+Then spawn `gsd-codebase-mapper` agents with the `--paths` hint (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze):
```text
Agent(
diff --git a/get-shit-done/workflows/execute-plan.md b/get-shit-done/workflows/execute-plan.md
index 39a487ced..704d37a72 100644
--- a/get-shit-done/workflows/execute-plan.md
+++ b/get-shit-done/workflows/execute-plan.md
@@ -92,7 +92,7 @@ Otherwise: Apply checkpoint-based routing below.
| Verify-only | B (segmented) | Segments between checkpoints. After none/human-verify → SUBAGENT. After decision/human-action → MAIN |
| Decision | C (main) | Execute entirely in main context |
-**Pattern A:** init_agent_tracking → capture `EXPECTED_BASE=$(git rev-parse HEAD)` → spawn Agent(subagent_type="gsd-executor", model=executor_model) with prompt: execute plan at [path], autonomous, all tasks + SUMMARY + commit, follow deviation/auth rules, report: plan name, tasks, SUMMARY path, commit hash → track agent_id → wait → update tracking → report. **Include `isolation="worktree"` only if `workflow.use_worktrees` is not `false`** (read via `config-get workflow.use_worktrees`). **When using `isolation="worktree"`, include a `` block in the prompt** instructing the executor to: (1) FIRST assert `git symbolic-ref HEAD` resolves to a per-agent branch (NOT a protected ref like `main`/`master`/`develop`/`trunk`/`release/*`) and HALT with a blocker if not — never self-recover via `git update-ref refs/heads/` (#2924); (2) only after that assertion passes, run `git merge-base HEAD {EXPECTED_BASE}` and, if the result differs from `{EXPECTED_BASE}`, hard-reset the branch with `git reset --hard {EXPECTED_BASE}` before starting work, then verify with `[ "$(git rev-parse HEAD)" != "{EXPECTED_BASE}" ] && exit 1`. The HEAD assertion (Step 1) MUST run before any reset/checkout. This corrects a known issue where `EnterWorktree` creates branches from `main` instead of the feature branch HEAD (affects all platforms — #2015) and prevents the destructive HEAD-on-master self-recovery path (#2924).
+**Pattern A:** init_agent_tracking → capture `EXPECTED_BASE=$(git rev-parse HEAD)` → print `Spawning executor agent (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)` → spawn Agent(subagent_type="gsd-executor", model=executor_model) with prompt: execute plan at [path], autonomous, all tasks + SUMMARY + commit, follow deviation/auth rules, report: plan name, tasks, SUMMARY path, commit hash → track agent_id → wait → update tracking → report. **Include `isolation="worktree"` only if `workflow.use_worktrees` is not `false`** (read via `config-get workflow.use_worktrees`). **When using `isolation="worktree"`, include a `` block in the prompt** instructing the executor to: (1) FIRST assert `git symbolic-ref HEAD` resolves to a per-agent branch (NOT a protected ref like `main`/`master`/`develop`/`trunk`/`release/*`) and HALT with a blocker if not — never self-recover via `git update-ref refs/heads/` (#2924); (2) only after that assertion passes, run `git merge-base HEAD {EXPECTED_BASE}` and, if the result differs from `{EXPECTED_BASE}`, hard-reset the branch with `git reset --hard {EXPECTED_BASE}` before starting work, then verify with `[ "$(git rev-parse HEAD)" != "{EXPECTED_BASE}" ] && exit 1`. The HEAD assertion (Step 1) MUST run before any reset/checkout. This corrects a known issue where `EnterWorktree` creates branches from `main` instead of the feature branch HEAD (affects all platforms — #2015) and prevents the destructive HEAD-on-master self-recovery path (#2924).
**Pattern B:** Execute segment-by-segment. Autonomous segments: spawn subagent for assigned tasks only (no SUMMARY/commit). Checkpoints: main context. After all segments: aggregate, create SUMMARY, commit. See segment_execution.
diff --git a/get-shit-done/workflows/explore.md b/get-shit-done/workflows/explore.md
index d93635725..a6e73cf17 100644
--- a/get-shit-done/workflows/explore.md
+++ b/get-shit-done/workflows/explore.md
@@ -59,6 +59,8 @@ This would take ~30 seconds and might surface useful context.
```
If yes, spawn a research agent:
+
+Print: `◆ Spawning explorer... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)`
```
Agent(
prompt="Quick research: {specific_question}. Return 3-5 key findings, no more than 200 words.",
diff --git a/get-shit-done/workflows/import.md b/get-shit-done/workflows/import.md
index dda817932..cc516a159 100644
--- a/get-shit-done/workflows/import.md
+++ b/get-shit-done/workflows/import.md
@@ -197,6 +197,8 @@ Write the PLAN.md file to the target directory.
Delegate validation to gsd-plan-checker:
+Print: "Delegating to gsd-plan-checker (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)"
+
```
Agent({
subagent_type: "gsd-plan-checker",
diff --git a/get-shit-done/workflows/ingest-docs.md b/get-shit-done/workflows/ingest-docs.md
index f19cc558b..49757d492 100644
--- a/get-shit-done/workflows/ingest-docs.md
+++ b/get-shit-done/workflows/ingest-docs.md
@@ -180,7 +180,7 @@ Collect the one-line confirmations from each classifier. If any classifier error
-Spawn `gsd-doc-synthesizer` once:
+Spawn `gsd-doc-synthesizer` once (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze):
```
Agent({
@@ -247,7 +247,7 @@ Proceed to routing silently, or optionally display `GSD > No conflicts. Auto-res
Audit PROJECT.md field requirements that `gsd-roadmapper` expects. For fields derivable from `.planning/intel/SYNTHESIS.md` (project scope, goals/non-goals, constraints, locked decisions), synthesize from the intel. For fields NOT derivable (project name, developer-facing success metric, target runtime), prompt via `AskUserQuestion` one at a time — minimal question set, no interrogation.
-Delegate to `gsd-roadmapper`:
+Delegate to `gsd-roadmapper` (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze):
```
Agent({
diff --git a/get-shit-done/workflows/manager.md b/get-shit-done/workflows/manager.md
index 032d69c8d..5187d3220 100644
--- a/get-shit-done/workflows/manager.md
+++ b/get-shit-done/workflows/manager.md
@@ -269,7 +269,7 @@ Important: You are running in the background. Do NOT use AskUserQuestion — mak
Display:
```
-◆ Spawning planner for Phase {N}: {phase_name}...
+◆ Spawning planner for Phase {N}: {phase_name}... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
```
Loop back to dashboard step.
@@ -303,7 +303,7 @@ Important: You are running in the background. Do NOT use AskUserQuestion — mak
Display:
```
-◆ Spawning executor for Phase {N}: {phase_name}...
+◆ Spawning executor for Phase {N}: {phase_name}... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
```
Loop back to dashboard step.
diff --git a/get-shit-done/workflows/map-codebase.md b/get-shit-done/workflows/map-codebase.md
index 22526067d..b63523bea 100644
--- a/get-shit-done/workflows/map-codebase.md
+++ b/get-shit-done/workflows/map-codebase.md
@@ -144,6 +144,8 @@ Use Agent tool with `subagent_type="gsd-codebase-mapper"`, `model="{mapper_model
**CRITICAL:** Use the dedicated `gsd-codebase-mapper` agent, NOT `Explore` or `browser_subagent`. The mapper agent writes documents directly.
+Print: "Spawning 4 parallel codebase mapper agents (each runs in a subagent — no output until they return, ~1–5 min; expected, not a freeze)"
+
**Agent 1: Tech Focus**
```text
diff --git a/get-shit-done/workflows/new-milestone.md b/get-shit-done/workflows/new-milestone.md
index ba66cfe6c..43dc58fce 100644
--- a/get-shit-done/workflows/new-milestone.md
+++ b/get-shit-done/workflows/new-milestone.md
@@ -287,7 +287,7 @@ AskUserQuestion: "Research the domain ecosystem for new features before defining
GSD ► RESEARCHING
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-◆ Spawning 4 researchers in parallel...
+◆ Spawning 4 researchers in parallel... (each runs in a subagent — no output until they return, ~1–5 min; expected, not a freeze)
→ Stack, Features, Architecture, Pitfalls
```
@@ -455,7 +455,7 @@ gsd_run query commit "docs: define milestone v[X.Y] requirements" --files .plann
GSD ► CREATING ROADMAP
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-◆ Spawning roadmapper...
+◆ Spawning roadmapper... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
```
**Starting phase number:**
diff --git a/get-shit-done/workflows/new-project.md b/get-shit-done/workflows/new-project.md
index 1676dbea2..c15226e5d 100644
--- a/get-shit-done/workflows/new-project.md
+++ b/get-shit-done/workflows/new-project.md
@@ -869,7 +869,7 @@ Check if this is greenfield or subsequent milestone:
Display spawning indicator:
```
-◆ Spawning 4 researchers in parallel...
+◆ Spawning 4 researchers in parallel... (each runs in a subagent — no output until they return, ~1–5 min; expected, not a freeze)
→ Stack research
→ Features research
→ Architecture research
@@ -1265,7 +1265,7 @@ Display stage banner:
GSD ► CREATING ROADMAP
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-◆ Spawning roadmapper...
+◆ Spawning roadmapper... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
```
**ROADMAP.md template — mode-aware emit.** When generating the initial ROADMAP.md:
diff --git a/get-shit-done/workflows/plan-phase.md b/get-shit-done/workflows/plan-phase.md
index 700efb366..ab5d33949 100644
--- a/get-shit-done/workflows/plan-phase.md
+++ b/get-shit-done/workflows/plan-phase.md
@@ -480,7 +480,7 @@ Display banner:
GSD ► RESEARCHING PHASE {X}
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-◆ Spawning researcher...
+◆ Spawning researcher... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
```
### Spawn gsd-phase-researcher
@@ -815,7 +815,7 @@ Display banner:
GSD ► PATTERN MAPPING PHASE {X}
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-◆ Spawning pattern mapper...
+◆ Spawning pattern mapper... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
```
Pattern mapper prompt:
@@ -877,7 +877,7 @@ Display banner:
GSD ► PLANNING PHASE {X}
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-◆ Spawning planner...
+◆ Spawning planner... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
```
Planner prompt:
@@ -1040,7 +1040,7 @@ fi
Display:
```text
-◆ Chunked mode: spawning outline planner...
+◆ Chunked mode: spawning outline planner... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
```
Spawn the planner in **outline-only** mode — it must write only the outline manifest, not any
@@ -1087,7 +1087,7 @@ For each plan entry extracted from `PLAN-OUTLINE.md`:
2. Display:
```text
- ◆ Chunked mode: planning {plan_id} ({k}/{N})...
+ ◆ Chunked mode: planning {plan_id} ({k}/{N})... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
```
3. Spawn the planner in **single-plan** mode — it must write exactly one PLAN.md file:
@@ -1225,7 +1225,7 @@ Display banner:
GSD ► VERIFYING PLANS
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-◆ Spawning plan checker...
+◆ Spawning plan checker... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
```
Checker prompt:
diff --git a/get-shit-done/workflows/plan-review-convergence.md b/get-shit-done/workflows/plan-review-convergence.md
index f3a925147..f0b31773a 100644
--- a/get-shit-done/workflows/plan-review-convergence.md
+++ b/get-shit-done/workflows/plan-review-convergence.md
@@ -98,7 +98,7 @@ Display startup banner:
**If `has_plans` is false:**
-Display: `◆ No plans found — spawning initial planning agent...`
+Display: `◆ No plans found — spawning initial planning agent... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)`
```text
Agent(
@@ -134,7 +134,7 @@ prev_high_count = Infinity
Increment `cycle`.
-Display: `◆ Cycle {cycle}/{MAX_CYCLES} — spawning review agent...`
+Display: `◆ Cycle {cycle}/{MAX_CYCLES} — spawning review agent... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)`
```text
Agent(
@@ -308,7 +308,7 @@ Exit workflow.
Update `prev_high_count = HIGH_COUNT`.
-Display: `◆ Spawning replan agent with review feedback...`
+Display: `◆ Spawning replan agent with review feedback... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)`
```text
Agent(
diff --git a/get-shit-done/workflows/quick.md b/get-shit-done/workflows/quick.md
index 6c127e0ad..fae302a55 100644
--- a/get-shit-done/workflows/quick.md
+++ b/get-shit-done/workflows/quick.md
@@ -392,7 +392,7 @@ Display banner:
GSD ► RESEARCHING QUICK TASK
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-◆ Investigating approaches for: ${DESCRIPTION}
+◆ Investigating approaches for: ${DESCRIPTION} (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
```
Spawn a single focused researcher (not 4 parallel researchers like full phases — quick tasks need targeted research, not broad domain surveys):
@@ -455,6 +455,8 @@ If research file not found, warn but continue: "Research agent did not produce o
**If NOT `$VALIDATE_MODE`:** Use standard `quick` mode.
+Display: `◆ Spawning planner... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)`
+
```
Agent(
prompt="
@@ -518,7 +520,7 @@ Display banner:
GSD ► CHECKING PLAN
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-◆ Spawning plan checker...
+◆ Spawning plan checker... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
```
Checker prompt:
@@ -855,7 +857,7 @@ Display banner:
GSD ► VERIFYING RESULTS
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-◆ Spawning verifier...
+◆ Spawning verifier... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
```
```
diff --git a/get-shit-done/workflows/scan.md b/get-shit-done/workflows/scan.md
index 0b8228cbc..a72f451ec 100644
--- a/get-shit-done/workflows/scan.md
+++ b/get-shit-done/workflows/scan.md
@@ -72,6 +72,8 @@ mkdir -p .planning/codebase
Spawn a single `gsd-codebase-mapper` agent with the selected focus area:
+Print: `◆ Spawning scanner... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)`
+
```
Agent(
prompt="Scan this codebase with focus: {focus}. Write results to .planning/codebase/. Produce only: {document_list}",
diff --git a/get-shit-done/workflows/secure-phase.md b/get-shit-done/workflows/secure-phase.md
index c5a9b1dfa..06a55ddac 100644
--- a/get-shit-done/workflows/secure-phase.md
+++ b/get-shit-done/workflows/secure-phase.md
@@ -93,6 +93,8 @@ Call AskUserQuestion with threat table and options:
- `register_authored_at_plan_time: true` — **Verify mitigations exist** — do not scan for new threats. The register is complete; verify each threat's mitigation is present in the implementation.
- `register_authored_at_plan_time: false` (retroactive-STRIDE mode) — **Retroactive-STRIDE: build a STRIDE register from implementation files first, then verify mitigations.** The phase was authored before formal threat modelling; the auditor must construct the register from scratch before verifying.
+Print: `◆ Spawning security auditor... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)`
+
```
Agent(
prompt="Read ~/.claude/agents/gsd-security-auditor.md for instructions.\n\n" +
diff --git a/get-shit-done/workflows/ui-phase.md b/get-shit-done/workflows/ui-phase.md
index 92e2381b8..7b3e11b1d 100644
--- a/get-shit-done/workflows/ui-phase.md
+++ b/get-shit-done/workflows/ui-phase.md
@@ -118,7 +118,7 @@ Display:
GSD ► UI DESIGN CONTRACT — PHASE {N}
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-◆ Spawning UI researcher...
+◆ Spawning UI researcher... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
```
Build prompt:
@@ -183,7 +183,7 @@ Display:
GSD ► VERIFYING UI-SPEC
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-◆ Spawning UI checker...
+◆ Spawning UI checker... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
```
Build prompt:
diff --git a/get-shit-done/workflows/ui-review.md b/get-shit-done/workflows/ui-review.md
index a6c328ad8..92fda0302 100644
--- a/get-shit-done/workflows/ui-review.md
+++ b/get-shit-done/workflows/ui-review.md
@@ -68,7 +68,7 @@ Build file list for auditor:
## 3. Spawn gsd-ui-auditor
```
-◆ Spawning UI auditor...
+◆ Spawning UI auditor... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
```
Build prompt:
diff --git a/get-shit-done/workflows/validate-phase.md b/get-shit-done/workflows/validate-phase.md
index 937d343e6..878280c54 100644
--- a/get-shit-done/workflows/validate-phase.md
+++ b/get-shit-done/workflows/validate-phase.md
@@ -93,6 +93,8 @@ Call AskUserQuestion with gap table and options:
## 5. Spawn gsd-nyquist-auditor
+Print: `◆ Spawning nyquist auditor... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)`
+
```
Agent(
prompt="Read ~/.claude/agents/gsd-nyquist-auditor.md for instructions.\n\n" +
diff --git a/get-shit-done/workflows/verify-work.md b/get-shit-done/workflows/verify-work.md
index 950c65772..65bc517a4 100644
--- a/get-shit-done/workflows/verify-work.md
+++ b/get-shit-done/workflows/verify-work.md
@@ -552,7 +552,7 @@ Display:
GSD ► PLANNING FIXES
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-◆ Spawning planner for gap closure...
+◆ Spawning planner for gap closure... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
```
Spawn gsd-planner in --gaps mode:
@@ -602,7 +602,7 @@ Display:
GSD ► VERIFYING FIX PLANS
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-◆ Spawning plan checker...
+◆ Spawning plan checker... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
```
Initialize: `iteration_count = 1`
diff --git a/tests/spawn-liveness-banner.test.cjs b/tests/spawn-liveness-banner.test.cjs
new file mode 100644
index 000000000..5c907a848
--- /dev/null
+++ b/tests/spawn-liveness-banner.test.cjs
@@ -0,0 +1,110 @@
+// allow-test-rule: source-text-is-the-product
+// Tests that every GSD workflow that spawns a subagent carries the liveness phrase
+// "runs in a subagent" on its spawn announcement lines.
+// Canonical phrase defined in get-shit-done/references/ui-brand.md § Spawning Indicators.
+// Regression test for https://github.com/open-gsd/gsd-core/issues/558.
+//
+// TEST STRATEGY: Two complementary assertions.
+//
+// 1. SPAWN-BANNER CHECK — any ◆ display line that contains the word "spawn" or "spawning"
+// (case-insensitive, anywhere on the line) must carry the liveness phrase. This is more
+// rigorous than a simple /◆\s+[Ss]pawning/ prefix match and catches all variants:
+// "◆ Spawning researcher..." (spawn word right after ◆)
+// "◆ Chunked mode: spawning planner..." (spawn word after prefix text)
+// It does NOT match ◆ status/error lines that say "planner returned" or "checker wrote"
+// (those don't contain the word "spawn"), so it has no false positives.
+//
+// 2. PRESENCE CHECK (coarser fallback) — if a file contains `subagent_type`, it must
+// contain the liveness phrase at least once. Catches workflows that dispatch subagents
+// without any ◆ spawn banner (e.g. prose-only "Spawn X" instructions).
+//
+// Together the two assertions are strictly stronger than the original file-level check:
+// a file with 10 spawns where only one ◆ Spawning banner got the note still fails #1.
+// A file with subagent_type but no banner at all still fails #2.
+
+const { test, describe } = require('node:test');
+const assert = require('node:assert/strict');
+const fs = require('fs');
+const path = require('path');
+
+const WORKFLOWS_DIR = path.join(__dirname, '..', 'get-shit-done', 'workflows');
+const LIVENESS_PHRASE = 'runs in a subagent';
+
+// Matches any ◆ line that contains "spawn" or "spawning" (case-insensitive, word anywhere).
+// This covers:
+// "◆ Spawning researcher..." → matched
+// "◆ Chunked mode: spawning X..." → matched
+// But NOT:
+// "◆ Planner wrote N plan(s)..." → not matched (no "spawn" word)
+// "◆ Research phase enabled" → not matched (no "spawn" word)
+const SPAWN_BANNER_RE = /◆[^\n]*\bspawning?\b/i;
+
+function findMdFiles(dir) {
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
+ const files = [];
+ for (const entry of entries) {
+ const full = path.join(dir, entry.name);
+ if (entry.isDirectory()) {
+ files.push(...findMdFiles(full));
+ } else if (entry.isFile() && entry.name.endsWith('.md')) {
+ files.push(full);
+ }
+ }
+ return files;
+}
+
+describe('spawn-liveness-banner', () => {
+ test('every ◆ spawn announcement line carries the liveness phrase "runs in a subagent"', () => {
+ const mdFiles = findMdFiles(WORKFLOWS_DIR);
+ const bannerViolations = [];
+
+ for (const filePath of mdFiles) {
+ const content = fs.readFileSync(filePath, 'utf-8');
+ const lines = content.split('\n');
+ const rel = path.relative(WORKFLOWS_DIR, filePath);
+
+ for (let i = 0; i < lines.length; i++) {
+ const line = lines[i];
+ if (SPAWN_BANNER_RE.test(line) && !line.includes(LIVENESS_PHRASE)) {
+ bannerViolations.push(`${rel}:${i + 1}: ${line.trim()}`);
+ }
+ }
+ }
+
+ assert.deepStrictEqual(
+ bannerViolations,
+ [],
+ `The following ◆ spawn announcement lines are missing the liveness phrase "${LIVENESS_PHRASE}":\n` +
+ bannerViolations.map(v => ` - ${v}`).join('\n') +
+ '\n\nPer get-shit-done/references/ui-brand.md § "Spawning Indicators":\n' +
+ 'every ◆ spawn announcement must carry "runs in a subagent" so users know\n' +
+ 'that silence during a subagent run is expected and do not kill a healthy agent.\n' +
+ 'See https://github.com/open-gsd/gsd-core/issues/558'
+ );
+ });
+
+ test('every workflow that dispatches a subagent contains the liveness phrase somewhere', () => {
+ const mdFiles = findMdFiles(WORKFLOWS_DIR);
+ const presenceViolations = [];
+
+ for (const filePath of mdFiles) {
+ const content = fs.readFileSync(filePath, 'utf-8');
+ const rel = path.relative(WORKFLOWS_DIR, filePath);
+
+ if (content.includes('subagent_type') && !content.includes(LIVENESS_PHRASE)) {
+ presenceViolations.push(rel);
+ }
+ }
+
+ assert.deepStrictEqual(
+ presenceViolations,
+ [],
+ `The following workflow files contain "subagent_type" but are missing the liveness phrase "${LIVENESS_PHRASE}" anywhere in the file:\n` +
+ presenceViolations.map(f => ` - ${f}`).join('\n') +
+ '\n\nPer get-shit-done/references/ui-brand.md § "Spawning Indicators":\n' +
+ 'every workflow that spawns a subagent must carry "runs in a subagent" so users know\n' +
+ 'that silence during a subagent run is expected and do not kill a healthy agent.\n' +
+ 'See https://github.com/open-gsd/gsd-core/issues/558'
+ );
+ });
+});