Problem
When an orchestrated run pauses waiting for user input (permission prompts, AskUserQuestion dialogs, wrap-up confirmation), the factory visualization has no way to show this. A user watching the vis just sees stalled activity with no indication the orchestrator is blocked on them.
Proposed solution
Use Claude Code hooks to detect input-waiting states, emit new events to run-log.jsonl, and have the factory vis render the waiting state.
Architecture
Claude Code hooks run-log.jsonl Factory vis
───────────────── ───────────── ──────────
Notification hook ──> append ──> waiting_for_input event ──> orchestrator shows waiting
UserPromptSubmit ──> append ──> input_received event ──> orchestrator resumes
Key design decisions
-
Breadcrumb file (.claude/tmp/active-run-dir): The orchestrate skill writes the runDir path here after init_run. Hook scripts read it to discover which run-log.jsonl to append to. tmp/ is already gitignored. Only one orchestration runs per project directory, so cwd is a natural key.
-
Two new event types in run-core: waiting_for_input (with reason: permission_prompt | elicitation_dialog | idle_prompt) and input_received. These fold into a new waitingForInput field on CanonicalRunStatus.
-
Hook scripts (.claude/hooks/): Shell scripts triggered by Claude Code's Notification and UserPromptSubmit hooks. They read the breadcrumb, append JSONL directly (bypassing MCP since hooks run outside MCP context). Atomic single-line appends are safe on POSIX.
-
Visual treatment: Orchestrator sprite switches to concerned animation at 0.6 opacity (between idle 0.35 and active 1.0) when waiting. Clears when input is received. No new sprite assets needed.
-
Backward compatible: Old run-log.jsonl files without these events fold with waitingForInput: undefined. The mapper treats undefined as "not waiting."
Considerations
- Hook scripts must silently no-op when no orchestration is active (breadcrumb guard)
- The
UserPromptSubmit hook fires on every user prompt, but the breadcrumb + idempotent folder make this harmless
- The exact JSON field name for the notification type in hook input needs verification during implementation (could be
matcher, hook_event_name, or similar)
- JSONL concurrent writes from hooks and MCP are safe under POSIX
PIPE_BUF for small lines
Scope
run-core: 2 new event types, Zod schemas, event folder, exports
factory: mapper, differ, OrchestratorActor, CatwalkScene, types
agents: orchestrate skill breadcrumb write/cleanup
.claude/: hook scripts + settings.json configuration
- Tests for schema parsing and event folding
Acceptance criteria
Problem
When an orchestrated run pauses waiting for user input (permission prompts,
AskUserQuestiondialogs, wrap-up confirmation), the factory visualization has no way to show this. A user watching the vis just sees stalled activity with no indication the orchestrator is blocked on them.Proposed solution
Use Claude Code hooks to detect input-waiting states, emit new events to
run-log.jsonl, and have the factory vis render the waiting state.Architecture
Key design decisions
Breadcrumb file (
.claude/tmp/active-run-dir): The orchestrate skill writes therunDirpath here afterinit_run. Hook scripts read it to discover whichrun-log.jsonlto append to.tmp/is already gitignored. Only one orchestration runs per project directory, socwdis a natural key.Two new event types in
run-core:waiting_for_input(with reason:permission_prompt | elicitation_dialog | idle_prompt) andinput_received. These fold into a newwaitingForInputfield onCanonicalRunStatus.Hook scripts (
.claude/hooks/): Shell scripts triggered by Claude Code'sNotificationandUserPromptSubmithooks. They read the breadcrumb, append JSONL directly (bypassing MCP since hooks run outside MCP context). Atomic single-line appends are safe on POSIX.Visual treatment: Orchestrator sprite switches to
concernedanimation at 0.6 opacity (between idle 0.35 and active 1.0) when waiting. Clears when input is received. No new sprite assets needed.Backward compatible: Old
run-log.jsonlfiles without these events fold withwaitingForInput: undefined. The mapper treatsundefinedas "not waiting."Considerations
UserPromptSubmithook fires on every user prompt, but the breadcrumb + idempotent folder make this harmlessmatcher,hook_event_name, or similar)PIPE_BUFfor small linesScope
run-core: 2 new event types, Zod schemas, event folder, exportsfactory: mapper, differ, OrchestratorActor, CatwalkScene, typesagents: orchestrate skill breadcrumb write/cleanup.claude/: hook scripts + settings.json configurationAcceptance criteria
waiting_for_inputandinput_receivedevents parse and validate via Zod schemafoldEvents()correctly sets/clearswaitingForInputonCanonicalRunStatusorchestrator.waitingfromwaitingForInputstatusinit_run, cleaned up aftercomplete_runpnpm run checkpasses (typecheck, format, lint, tests)