Driving patterns
How one Claude Code session drives another.
The A/B session model
Session A is the driver: a Claude Code session running in the project you're building. It hosts claw-drive as an MCP server.
Session B is a driven session: a fresh claude -p --input-format=stream-json --output-format=stream-json subprocess that Session A spawns in another working directory. B acts like a human user would — it even runs hooks.
Each call to start_session spawns its own driven session, so a single Session A can hold a fan-out of Session B, C, D, … running in parallel — each with its own working directory, policy, scenario brief, and event log. list_sessions (MCP) and claw-drive sessions (CLI) enumerate them all; claw-drive pending shows awaiting-approval calls across every running session in one view.
Every tool call a driven session makes is intercepted by a PreToolUse hook that talks to claw-drive's runner. The runner checks policy and auto-approves, auto-rejects, defers to the human, or escalates to A.
Monitor + watch_command
start_session returns a ready-made payload for Claude Code's Monitor tool:
// A's driver code
const { session_id, watch_command } = await claw_drive.start_session({
cwd: "/path/to/project",
policy: <policy>,
scenario_brief: "<the brief>",
});
await Monitor(watch_command);
// Monitor spawns `claw-drive watch <id>` and streams notifications to A.
A reacts to each notification by its kind:
Notification kind | A's response |
|---|---|
tool_decision_required | Surface to human. Resolve with resolve_tool_call or (for defer) provide_tool_output. |
tool_decision_resolved | Only when resolved_by is timeout — a pending decision auto-fired. Surface the consequence. |
tool_output_provided | Defer follow-through completed — confirm B received the output and moved on. |
turn_completed | Decide whether to send the next turn or wrap up. |
turn_failed | Surface the failure details. |
error | Runner-side error. Surface and decide whether to abort. |
tool_call_result (with is_error) | A tool call B issued errored. Surface the error. |
session_stopped | Wrap up. |
watch filters events.jsonl to only the eight kinds above — Session B's assistant prose, intermediate tool-call lifecycle events, and successful results stay out of A's stream.
Sentinel tokens (the default)
Session B is auto-taught a tiny two-token contract at session start via --append-system-prompt:
| Token | Meaning |
|---|---|
[NEEDS-INPUT] | The human is needed — a question, a decision, a missing fact, an unrecoverable failure. End the turn with this token on its own line. |
[DONE] | The task in the brief is complete. End the turn with this token on its own line. |
The contract for B is exactly that: emit [NEEDS-INPUT] when the human is needed; [DONE] when the task is complete; nothing otherwise. Quiet progress, retries-in-progress, intermediate milestones — no token. Drivers shouldn't have to reason about a vocabulary of marker types; the only signal that matters is "does this turn need human attention or not."
watch reads the trailing token from each turn_completed's last assistant_text and surfaces the event only when it matches one of the two. No token, or anything else → turn_completed is dropped (Session A doesn't get pinged). To bypass the sentinel filter entirely (raw stream including every turn_completed), pass --no-token-filter. To bypass the wrapper injection on the runner side, pass wrapper: false to start_session; pair with --no-token-filter on watch since there's no sentinel to anchor on.
Idle events
If no surfaced event arrives for a configurable stretch, watch emits a synthetic idle event so the driver can break out of an indefinite wait. Default 600 seconds; tune via --idle-after SECONDS on the watch invocation (set to 0 to disable). Useful when B is genuinely working — a long build, a slow tool — but the driver still wants a heartbeat to decide whether to interrupt.
Narrowing further: --only and --decision-only
Even tighter than the sentinel filter, --only KIND[,KIND]... restricts the kind universe to a subset. Mutually exclusive with --decision-only (which is shorthand for the historically-human-attention kinds). These compose with the sentinel filter — they restrict which kinds watch considers; the sentinel filter restricts which turn_completeds surface among them.
# Specify exactly which kinds to keep — sentinel filter still applies on top.
claw-drive watch <id> --only tool_decision_required,turn_failed,session_stopped
The /claw-drive-start plugin skill defaults to the sentinel-aware behavior. Pass --verbose to bypass both the wrapper injection and the sentinel filter — full raw event stream.
Defer round-trip
Some commands can't run inside B — sudo, interactive logins, anything that needs auth, a TTY, or a secret. For these, use auto_defer:
"auto_defer": [
{ "tool": "Bash", "bash_command_matches": "^sudo\\s", "name": "sudo → human" }
]
Flow:
- B tries a matching call. The PreToolUse hook denies it with a
DEFERRED:message. - A
tool_decision_requiredevent fires. The monitor notifies A. - The human runs the command locally.
- A calls
provide_tool_output(orclaw-drive provide-output) with the result. - The runner formats the output as a user turn to B. B continues.
claw-drive provide-output <call_id> --stdout "<output>" --exit 0
CLAW-GATE: pausing mid-task for human input
When B needs a human decision before attempting a step, use the CLAW-GATE convention baked into the default policy. See Policies → CLAW-GATE for the mechanism. Example brief:
Implement feature X. Before committing, run echo 'CLAW-GATE: ready to commit feature X — should I include the changelog entry?' and wait for my response.
Same hook → defer → provide_tool_output round-trip as sudo. No new primitive.
Interrupting a turn
If B is off-track, send SIGINT via interrupt_turn. B aborts the current turn cleanly; the session stays alive.
claw-drive interrupt <session> <turn_id>
You can then send_turn a correction and continue.
Restart resilience
Events persist to ~/.claw-drive/sessions/<id>/events.jsonl. If Session A crashes or restarts, the driven session continues to run. Reconnect by listing sessions and starting a new Monitor:
claw-drive sessions
# <id> running cwd=/path/to/project policy=…
claw-drive watch <id> --replay
# Replays from seq 0 so A can rebuild state, then follows live events.
The policy, scenario brief, and full event history survive. The only state that doesn't is Monitor's own streaming cursor — pass --replay the first time you re-subscribe.