Skip to content

/gsd headless

gsd headless runs any /gsd command without the interactive TUI. It spawns a child process in RPC mode, automatically responds to interactive prompts, detects when the command completes, and exits with a meaningful exit code.

This is GSD’s entry point for automation — CI pipelines that run auto-mode on push, cron jobs that execute scheduled maintenance, and scripts that need machine-readable output from GSD commands. Progress is written to stderr; use --json to stream structured JSONL events to stdout instead.

Terminal window
gsd headless [flags] [subcommand] [args...]

Any /gsd subcommand works as a positional argument. If no subcommand is given, it defaults to auto.

Terminal window
# Run auto mode (default)
gsd headless
# Run a single unit
gsd headless next
# Check project status
gsd headless status
# Run diagnostics
gsd headless doctor
# Force a specific dispatch phase
gsd headless dispatch plan
# Instant JSON snapshot — no LLM, ~50ms
gsd headless query
# Create a new milestone from a context file and start auto mode
gsd headless new-milestone --context brief.md --auto
# Create a milestone from inline text
gsd headless new-milestone --context-text "Build a REST API with auth"
# Pipe context from stdin
echo "Build a CLI tool" | gsd headless new-milestone --context -
FlagSyntaxDefaultDescription
--timeout--timeout N300000 (5 min)Overall timeout in milliseconds. The process exits with code 1 if this limit is reached. new-milestone auto-extends this to 600000 (10 min) when the timeout is still the default.
--max-restarts--max-restarts N3Auto-restart on crash with exponential backoff. Set 0 to disable.
--json--jsonOffStream all events as JSONL to stdout. Each line is a self-contained JSON object.
--events--events type1,type2Filter JSONL output to specific event types. Implies --json.
--verbose--verboseOffShow detailed execution output in stderr progress lines.
--model--model IDSession defaultOverride the model for the headless session.
--context--context <file>Context file for new-milestone. Use - to read from stdin.
--context-text--context-text <text>Inline context text for new-milestone.
--auto--autoOffChain into auto-mode after milestone creation.
--answers--answers <file>JSON file with pre-supplied answers for interactive prompts. See Answer Injection below.
--supervised--supervisedOffEnable supervised mode — forward interactive prompts to the orchestrator via stdin/stdout. Implies --json. Cannot be combined with --context -.
--response-timeout--response-timeout N30000In supervised mode, milliseconds to wait for an orchestrator response before falling back to auto-response.

Returns a single JSON object with the full project snapshot — no LLM session, no RPC child, instant response (~50ms). This is the recommended way for orchestrators and scripts to inspect GSD state.

Terminal window
gsd headless query | jq '.state.phase'
# "executing"
gsd headless query | jq '.next'
# {"action":"dispatch","unitType":"execute-task","unitId":"M001/S01/T03"}
gsd headless query | jq '.cost.total'
# 4.25

Output schema:

{
"state": {
"phase": "executing",
"activeMilestone": { "id": "M001", "title": "..." },
"activeSlice": { "id": "S01", "title": "..." },
"activeTask": { "id": "T01", "title": "..." },
"nextAction": "Execute T01: Create auth module in slice S01.",
"registry": [{ "id": "M001", "title": "...", "status": "active" }],
"progress": {
"milestones": { "done": 0, "total": 2 },
"slices": { "done": 1, "total": 3 },
"tasks": { "done": 2, "total": 5 }
},
"blockers": [],
"requirements": { "active": 4, "validated": 2, "deferred": 0, "outOfScope": 0, "blocked": 0, "total": 6 }
},
"next": {
"action": "dispatch",
"unitType": "execute-task",
"unitId": "M001/S01/T01",
"reason": "Next incomplete task in active slice"
},
"cost": {
"workers": [{ "milestoneId": "M001", "pid": 12345, "state": "running", "cost": 1.50, "lastHeartbeat": 1716000000000 }],
"total": 1.50
}
}

Fields under state.progress (slices, tasks) are omitted when not applicable (e.g., before slice planning starts). state.requirements is omitted when no REQUIREMENTS.md exists.

Create a new milestone from a context file without the TUI. Combine with --auto to immediately start execution after creation.

Terminal window
# From a file
gsd headless new-milestone --context brief.md
# Inline text
gsd headless new-milestone --context-text "Build a REST API with auth"
# From stdin
echo "Build a CLI tool" | gsd headless new-milestone --context -
# Create and immediately start auto-mode
gsd headless new-milestone --context brief.md --auto

The timeout is automatically extended to 10 minutes for new-milestone commands when using the default 5-minute timeout, because milestone creation involves codebase investigation and writing multiple planning artifacts. If you pass a custom --timeout, it is used as-is.

The headless runner spawns GSD with --mode rpc, creating a structured communication channel between the parent (headless controller) and child (GSD session) processes. Events flow from the child to the parent as typed messages — prompts, progress updates, completion signals, and errors.

When the child process sends an interactive prompt, the headless controller applies built-in defaults to keep execution flowing without human intervention:

Prompt typeDefault behaviour
selectPicks the first available option
confirmAuto-confirms
inputSubmits an empty string
editorReturns the prefill text, or empty if none

Quick commands (status, doctor, export, steer, etc.) resolve on the first agent_end event rather than waiting for a terminal notification. Use --answers to override these defaults with specific answers for predictable decision trees.

The controller monitors for completion signals — the child process reporting that the dispatched command finished, errored, or hit a blocker. Auto-mode completion is detected by terminal notification messages ("Auto-mode stopped...", "Step-mode stopped..."). An idle timeout fires as a fallback: 15 seconds for most commands, 2 minutes for new-milestone.

When --max-restarts is greater than 0 (default: 3), an error exit triggers an exponential backoff restart — 5 seconds after the first crash, up to a maximum of 30 seconds. SIGINT/SIGTERM interrupts skip the restart and exit immediately.

On every exit, headless writes a summary to stderr:

[headless] Status: complete
[headless] Duration: 42.3s
[headless] Events: 187 total, 43 tool calls

When restarts occurred, a restart count is appended. When --answers was used, an injection stats line is appended:

[headless] Restarts: 2
[headless] Answers: 5 answered, 1 defaulted, 2 secrets

On failure, the last 5 events are also printed to aid diagnostics.

--answers <file> loads a JSON file with pre-supplied answers for interactive selection prompts. This lets you drive headless sessions through predictable decision trees without supervised mode.

Answer file format:

{
"questions": {
"question-id": "selected option label",
"multi-select-id": ["option A", "option B"]
},
"secrets": {
"ENV_VAR_NAME": "secret-value"
},
"defaults": {
"strategy": "first_option"
}
}
  • questions — Map of question ID to the label of the answer to select. Multi-select questions accept an array.
  • secrets — Environment variables injected into the RPC child process. The child’s checkExistingEnvKeys() detects them in process.env and marks them as already configured — no interactive prompt is shown. Secrets are never logged or included in event streams.
  • defaults.strategy — What to do when a question isn’t in the file: "first_option" (default) selects the first available option; "cancel" cancels the prompt.

The injector correlates tool_execution_start events for ask_user_questions with subsequent extension_ui_request events. It handles out-of-order events via a deferred processing queue with a 500ms timeout.

--supervised turns headless into an interactive orchestrator bridge. Rather than auto-responding to every prompt, the headless process forwards extension_ui_request events to the outer process via stdout JSONL, and waits for responses on stdin. If no response arrives within --response-timeout milliseconds, it falls back to auto-response.

Both --answers and --supervised can be active simultaneously: the answer injector tries first, supervised mode handles unmatched prompts next, and the auto-responder catches any remaining ones.

Headless supports concurrent parallel workers for multi-milestone execution. Workers coordinate through file-based IPC in .gsd/parallel/ — no sockets or ports.

Spawning workers:

Terminal window
GSD_MILESTONE_LOCK=M001 GSD_PARALLEL_WORKER=1 \
gsd headless --json auto 2>logs/M001.log &

Each worker isolates its state via GSD_MILESTONE_LOCK=<MID> — state derivation only sees its assigned milestone. Workers write heartbeat files atomically to .gsd/parallel/<milestoneId>.status.json:

{
"milestoneId": "M001",
"pid": 12345,
"state": "running",
"currentUnit": { "type": "task", "id": "T03", "startedAt": 1710000000000 },
"completedUnits": 7,
"cost": 1.23,
"lastHeartbeat": 1710000015000,
"startedAt": 1710000000000,
"worktreePath": ".gsd/worktrees/M001"
}

Worker states: running, paused, stopped, error

Monitoring workers:

Terminal window
for f in .gsd/parallel/*.status.json; do
jq -r '[.milestoneId, .state, (.currentUnit.id // "idle"), "\(.cost)$"] | join("\t")' "$f"
done

Sending signals: write a signal file — the worker consumes and deletes it on the next dispatch cycle:

Terminal window
echo '{"signal":"pause","sentAt":'$(date +%s000)',"from":"coordinator"}' \
> .gsd/parallel/M001.signal.json

Signal commands: pause, resume, stop, rebase

Liveness: a session is stale when its PID is dead (kill -0 $pid fails) or its lastHeartbeat is older than 30 seconds. Use gsd headless query for an instant aggregate cost snapshot across all workers.

See /gsd parallel for the built-in parallel orchestration commands.

CodeMeaningWhen
0SuccessCommand completed successfully
1Error or timeoutCommand failed, crashed (and restarts exhausted), or --timeout was exceeded
2BlockedExecution hit a blocker requiring human input
FilePurpose
.gsd/runtime/headless-context.mdTemp file written for new-milestone — passes the context spec to the RPC child. Deleted by the child after reading.
.gsd/Bootstrapped if missing during new-milestone
.gsd/PROJECT.mdWritten by new-milestone — full project vision
.gsd/REQUIREMENTS.mdWritten by new-milestone — capability contract
.gsd/DECISIONS.mdSeeded by new-milestone — initial decisions log
.gsd/milestones/<MID>/<MID>-CONTEXT.mdWritten by new-milestone — milestone scope and assumptions
.gsd/milestones/<MID>/<MID>-ROADMAP.mdWritten by new-milestone — slice breakdown with checkboxes
.gsd/milestones/<MID>/slices/Created by new-milestone for subsequent slice planning
.gsd/parallel/<MID>.status.jsonWritten by parallel workers — heartbeat and state
FilePurpose
.gsd/Checked for existence before spawning RPC child (except new-milestone)
--answers <file>Answer injection JSON file, if provided
--context <file>Context spec for new-milestone, or stdin if -
FilePurpose
.gsd/parallel/<MID>.signal.jsonWritten by external coordinators to send signals to workers

Run auto-mode in CI:

Terminal window
# In a GitHub Actions workflow
gsd headless --timeout 600000 auto
echo "Exit code: $?"

Auto-restart on crash (up to 5 times):

Terminal window
gsd headless --max-restarts 5 auto

Get machine-readable status:

Terminal window
gsd headless --json status 2>/dev/null | jq '.type'

Stream only specific event types:

Terminal window
gsd headless --events tool_execution_start,agent_end auto

Available event types: agent_start, agent_end, tool_execution_start, tool_execution_end, tool_execution_update, extension_ui_request, message_start, message_end, message_update, turn_start, turn_end

Run a specific dispatch phase:

Terminal window
gsd headless dispatch execute

Use a different model:

Terminal window
gsd headless --model claude-sonnet-4-20250514 auto

Pre-supply answers for a non-interactive setup wizard:

Terminal window
gsd headless --answers answers.json new-milestone --context brief.md --auto

Poll-and-react loop (no LLM cost between checks):

Terminal window
PHASE=$(gsd headless query | jq -r '.state.phase')
case "$PHASE" in
complete) echo "Done" ;;
blocked) echo "Needs intervention" ;;
*) gsd headless next ;;
esac