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 kindA's response
tool_decision_requiredSurface to human. Resolve with resolve_tool_call or (for defer) provide_tool_output.
tool_decision_resolvedOnly when resolved_by is timeout — a pending decision auto-fired. Surface the consequence.
tool_output_providedDefer follow-through completed — confirm B received the output and moved on.
turn_completedDecide whether to send the next turn or wrap up.
turn_failedSurface the failure details.
errorRunner-side error. Surface and decide whether to abort.
tool_call_result (with is_error)A tool call B issued errored. Surface the error.
session_stoppedWrap 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:

TokenMeaning
[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:

  1. B tries a matching call. The PreToolUse hook denies it with a DEFERRED: message.
  2. A tool_decision_required event fires. The monitor notifies A.
  3. The human runs the command locally.
  4. A calls provide_tool_output (or claw-drive provide-output) with the result.
  5. 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.