Agentic AI Atlasby a5c.ai
OverviewWikiGraphFor AgentsEdgesSearchWorkspace
/
GitHubDocsDiscord
iiRecord
Agentic AI Atlas · Claude Code Harness Integration
page:docs-assimilation-harness-claude-code-integrationa5c.ai
Search record views/
Record · tabs

Available views

II.Record viewspp. 1 - 1
overviewarticlejsongraph
II.
Page JSON

page:docs-assimilation-harness-claude-code-integration

Structured · live

Claude Code Harness Integration json

Inspect the normalized record payload exactly as the atlas UI reads it.

File · wiki/docs/assimilation/harness/claude-code-integration.mdCluster · wiki
Record JSON
{
  "id": "page:docs-assimilation-harness-claude-code-integration",
  "_kind": "Page",
  "_file": "wiki/docs/assimilation/harness/claude-code-integration.md",
  "_cluster": "wiki",
  "attributes": {
    "nodeKind": "Page",
    "sourcePath": "docs/assimilation/harness/claude-code-integration.md",
    "sourceKind": "repo-docs",
    "title": "Claude Code Harness Integration",
    "displayName": "Claude Code Harness Integration",
    "slug": "docs/assimilation/harness/claude-code-integration",
    "articlePath": "wiki/docs/assimilation/harness/claude-code-integration.md",
    "article": "\n# Claude Code Harness Integration\r\n\r\nTechnical reference for the babysitter plugin's integration with Claude Code. Covers the full lifecycle from plugin registration through session management, the stop-hook orchestration loop, effect execution, and completion proof validation.\r\n\r\n---\r\n\r\n## Table of Contents\r\n\r\n1. [Plugin Manifest Registration](#1-plugin-manifest-registration)\r\n2. [SessionStart Hook](#2-sessionstart-hook)\r\n3. [Session State File Format](#3-session-state-file-format)\r\n4. [Run Creation and Session Binding](#4-run-creation-and-session-binding)\r\n5. [The Stop Hook -- Core Orchestration Loop Control](#5-the-stop-hook----core-orchestration-loop-control)\r\n6. [The Iteration Loop](#6-the-iteration-loop)\r\n7. [Native Orchestration Hooks](#7-native-orchestration-hooks)\r\n8. [Breakpoint Handling](#8-breakpoint-handling)\r\n9. [Session Check-Iteration (Runaway Loop Detection)](#9-session-check-iteration-runaway-loop-detection)\r\n10. [Completion Proof and Clean Exit](#10-completion-proof-and-clean-exit)\r\n\r\n---\r\n\r\n## Architecture Overview\r\n\r\n```\r\n+-------------------------------------------------------------------+\r\n|                        Claude Code Host                           |\r\n|                                                                   |\r\n|  +---------------------+    +-------------------------------+     |\r\n|  | Plugin Registration  |    | Hook System                   |     |\r\n|  | (plugin.json)        |    |  SessionStart -> session-start |     |\r\n|  | - hooks              |    |  Stop         -> stop          |     |\r\n|  | - skills             |    +-------------------------------+     |\r\n|  | - commands            |                                         |\r\n|  +---------------------+                                         |\r\n+-------------------------------------------------------------------+\r\n         |                              |\r\n         v                              v\r\n+-------------------+    +------------------------------------+\r\n| babysitter CLI    |    | Session State Files                |\r\n| (SDK npm package) |    | {pluginRoot}/skills/babysit/state/ |\r\n|                   |    |   {sessionId}.md                   |\r\n| hook:run          |    +------------------------------------+\r\n| run:create        |                   |\r\n| run:iterate       |                   v\r\n| task:list         |    +------------------------------------+\r\n| task:post         |    | Run Directory                      |\r\n| session:*         |    | .a5c/runs/{runId}/                 |\r\n+-------------------+    |   run.json, journal/, tasks/,      |\r\n                         |   state/, blobs/                   |\r\n                         +------------------------------------+\r\n```\r\n\r\n### End-to-End Data Flow\r\n\r\n```\r\nClaude Code starts session\r\n        |\r\n        v\r\n[SessionStart Hook] --> bash shell script\r\n        |                    |\r\n        v                    v\r\n  stdin: {session_id}   babysitter hook:run --hook-type session-start\r\n        |                    |\r\n        v                    v\r\n  Write AGENT_SESSION_ID   Create baseline state file\r\n  to CLAUDE_ENV_FILE            {stateDir}/{sessionId}.md\r\n        |\r\n        v\r\n  [User invokes /babysit skill]\r\n        |\r\n        v\r\n  [Skill creates process, calls run:create]\r\n        |\r\n        v\r\n  run:create --harness claude-code --session-id ... --plugin-root ...\r\n        |\r\n        v\r\n  Session state file updated with runId binding\r\n        |\r\n        v\r\n  [Skill calls run:iterate, executes effects, posts results, STOPS]\r\n        |\r\n        v\r\n  Claude Code intercepts stop --> [Stop Hook]\r\n        |\r\n        v\r\n  babysitter hook:run --hook-type stop\r\n        |\r\n        v\r\n  Decision: block (continue) or approve (exit)\r\n        |\r\n        +--[block]--> reason + systemMessage injected back to Claude\r\n        |                  |\r\n        |                  v\r\n        |             Claude resumes with iteration context\r\n        |             (calls run:iterate, executes effects, STOPS)\r\n        |                  |\r\n        |                  +---> [Stop Hook] again (loop)\r\n        |\r\n        +--[approve]--> Session ends, state file cleaned up\r\n```\r\n\r\n---\r\n\r\n## 1. Plugin Manifest Registration\r\n\r\n**Generated bundle manifest:** `artifacts/generated-plugins/claude-code/plugin.json`\r\n\r\nThe plugin manifest declares two hooks, three skills, and metadata:\r\n\r\n```json\r\n{\r\n  \"name\": \"babysitter\",\r\n  \"version\": \"4.0.139\",\r\n  \"sdkVersion\": \"0.0.170-staging.336c9a98\",\r\n  \"hooks\": {\r\n    \"SessionStart\": \"hooks/babysitter-session-start-hook.sh\",\r\n    \"Stop\": \"hooks/babysitter-stop-hook.sh\"\r\n  },\r\n  \"skills\": [\r\n    { \"name\": \"babysitter\", \"file\": \"skills/babysit/SKILL.md\" }\r\n  ]\r\n}\r\n```\r\n\r\nAdditionally, `artifacts/generated-plugins/claude-code/hooks/hooks.json` provides the Claude Code hook registration file for the generated bundle:\r\n\r\n```json\r\n{\r\n  \"hooks\": {\r\n    \"SessionStart\": [{ \"hooks\": [{ \"type\": \"command\", \"command\": \"bash ${CLAUDE_PLUGIN_ROOT}/hooks/babysitter-session-start-hook.sh\" }] }],\r\n    \"Stop\": [{ \"hooks\": [{ \"type\": \"command\", \"command\": \"bash ${CLAUDE_PLUGIN_ROOT}/hooks/babysitter-stop-hook.sh\" }] }]\r\n  }\r\n}\r\n```\r\n\r\n### Key Registration Points\r\n\r\n| Component | Purpose |\r\n|-----------|---------|\r\n| `SessionStart` hook | Installs SDK, creates baseline session state file |\r\n| `Stop` hook | Intercepts exit signals, controls orchestration loop continuation |\r\n| `babysitter` skill | Primary orchestration skill (SKILL.md) |\r\n| `babysitter-score` skill | Quality scoring skill |\r\n| `sdkVersion` | Pinned SDK version for CLI installation |\r\n\r\n### Environment Variables Provided by Claude Code\r\n\r\n| Variable | Description |\r\n|----------|-------------|\r\n| `CLAUDE_PLUGIN_ROOT` | Absolute path to the installed plugin directory |\r\n| `AGENT_SESSION_ID` | Cross-harness session identifier (written to `CLAUDE_ENV_FILE` by session-start hook) |\r\n| `CLAUDE_ENV_FILE` | Path to env file for persisting exports across hook invocations |\r\n\r\n---\r\n\r\n## 2. SessionStart Hook\r\n\r\n**Generated shell entry:** `artifacts/generated-plugins/claude-code/hooks/babysitter-proxied-session-start.sh`\r\n**TypeScript handler:** `packages/sdk/src/harness/claudeCode.ts` -> `handleSessionStartHookImpl()`\r\n\r\n### Execution Flow\r\n\r\n```\r\nClaude Code starts session\r\n        |\r\n        v\r\nbabysitter-session-start-hook.sh\r\n        |\r\n        +-- 1. Resolve PLUGIN_ROOT from CLAUDE_PLUGIN_ROOT or dirname\r\n        |\r\n        +-- 2. Check if `babysitter` CLI is on PATH\r\n        |       |\r\n        |       +-- [not found] Check MARKER_FILE (.babysitter-install-attempted)\r\n        |       |       |\r\n        |       |       +-- [no marker] Read sdkVersion from versions.json\r\n        |       |       |       |\r\n        |       |       |       +-- Try: npm i -g @a5c-ai/babysitter-sdk@{version}\r\n        |       |       |       |\r\n        |       |       |       +-- Fallback: npm i -g ... --prefix $HOME/.local\r\n        |       |       |       |\r\n        |       |       |       +-- Write marker file\r\n        |       |       |\r\n        |       |       +-- [marker exists] Skip install\r\n        |       |\r\n        |       +-- [still not found] Create npx fallback function\r\n        |\r\n        +-- 3. Capture stdin to temp file (clean EOF for Node.js)\r\n        |\r\n        +-- 4. Invoke: babysitter hook:run --hook-type session-start \\\r\n        |         --harness claude-code --plugin-root $PLUGIN_ROOT --json < $INPUT_FILE\r\n        |\r\n        +-- 5. Output result JSON to stdout\r\n        |\r\n        v\r\n    exit $EXIT_CODE\r\n```\r\n\r\n### TypeScript Handler (`handleSessionStartHookImpl`)\r\n\r\nThe handler performs three operations:\r\n\r\n1. **Parse stdin** -- Reads JSON input containing `{ session_id: string }`.\r\n\r\n2. **Append to CLAUDE_ENV_FILE** -- If the `CLAUDE_ENV_FILE` environment variable is set, appends `export AGENT_SESSION_ID=\"{sessionId}\"` to make the session ID available to subsequent hook invocations.\r\n\r\n3. **Create baseline state file** -- Writes a session state file at `{pluginRoot}/skills/babysit/state/{sessionId}.md` with initial values:\r\n\r\n```yaml\r\n---\r\nactive: true\r\niteration: 1\r\nmax_iterations: 65000\r\nrun_id: \"\"\r\nstarted_at: \"2026-03-02T10:00:00Z\"\r\nlast_iteration_at: \"2026-03-02T10:00:00Z\"\r\niteration_times:\r\n---\r\n```\r\n\r\nThe baseline state file is created unconditionally (if it does not already exist) so the stop hook can find it later, even before a run is created. The `run_id` field is empty at this stage -- it gets populated during run creation (Section 4).\r\n\r\n### SDK Installation Strategy\r\n\r\nThe shell script uses a four-tier fallback for CLI availability:\r\n\r\n| Priority | Method | Condition |\r\n|----------|--------|-----------|\r\n| 1 | Global `babysitter` binary | Already on PATH |\r\n| 2 | `npm i -g` (global install) | Marker file absent, permissions OK |\r\n| 3 | `npm i -g --prefix $HOME/.local` | Global install fails (permissions) |\r\n| 4 | `npx -y @a5c-ai/babysitter-sdk@{version}` | All installs failed |\r\n\r\nThe marker file (`{PLUGIN_ROOT}/.babysitter-install-attempted`) prevents repeated install attempts.\r\n\r\n---\r\n\r\n## 3. Session State File Format\r\n\r\n**Module:** `packages/sdk/src/session/`\r\n**Path convention:** `{pluginRoot}/skills/babysit/state/{sessionId}.md`\r\n\r\nSession state files use Markdown with YAML frontmatter. The frontmatter stores machine-readable state, and the body stores the user's original prompt.\r\n\r\n### File Structure\r\n\r\n```markdown\r\n---\r\nactive: true\r\niteration: 3\r\nmax_iterations: 65000\r\nrun_id: \"my-run-abc123\"\r\nstarted_at: \"2026-03-02T10:00:00Z\"\r\nlast_iteration_at: \"2026-03-02T10:05:30Z\"\r\niteration_times: 45,62,58\r\n---\r\n\r\nBuild a REST API with authentication and rate limiting for the user service.\r\n```\r\n\r\n### YAML Frontmatter Fields\r\n\r\n| Field | Type | Description |\r\n|-------|------|-------------|\r\n| `active` | boolean | Whether the session loop is active |\r\n| `iteration` | number | Current iteration number (1-based) |\r\n| `max_iterations` | number | Maximum allowed iterations (0 = unlimited, default: 65000) |\r\n| `run_id` | string | Associated run ID (empty string before `run:create`) |\r\n| `started_at` | string | ISO 8601 timestamp of session start |\r\n| `last_iteration_at` | string | ISO 8601 timestamp of last iteration |\r\n| `iteration_times` | string | Comma-separated list of last 3 iteration durations in seconds. Represented as a plain string in YAML (e.g., `iteration_times: 45,62,58`), not a YAML list. Parsed into `number[]` by the TypeScript layer. |\r\n\r\n### TypeScript Types\r\n\r\n```typescript\r\ninterface SessionState {\r\n  active: boolean;\r\n  iteration: number;        // 1-based\r\n  maxIterations: number;    // 0 = unlimited\r\n  runId: string;            // \"\" if unbound\r\n  startedAt: string;        // ISO 8601\r\n  lastIterationAt: string;  // ISO 8601\r\n  iterationTimes: number[]; // last 3 durations (seconds)\r\n}\r\n\r\ninterface SessionFile {\r\n  state: SessionState;\r\n  prompt: string;       // Markdown body after frontmatter\r\n  filePath: string;\r\n}\r\n```\r\n\r\n### Atomic Write Protocol\r\n\r\nSession files are written atomically via `writeSessionFile()`:\r\n\r\n1. Create temp file: `{filePath}.tmp.{pid}`\r\n2. Write content to temp file\r\n3. Atomic rename: `rename(temp, target)`\r\n4. On error: clean up temp file\r\n\r\n### Parsing\r\n\r\nThe YAML frontmatter parser (`parseYamlFrontmatter`) is a lightweight implementation that:\r\n- Splits content on `---` delimiters\r\n- Parses `key: value` pairs (strips surrounding quotes)\r\n- Returns the body (everything after the second `---`) as the prompt\r\n\r\n---\r\n\r\n## 4. Run Creation and Session Binding\r\n\r\n**Harness method:** `bindSessionImpl()` in `packages/sdk/src/harness/claudeCode.ts`\r\n\r\nWhen the babysitter skill creates a run via `run:create --harness claude-code`, the SDK binds the Claude Code session to the new run.\r\n\r\n### CLI Invocation\r\n\r\n```bash\r\nbabysitter run:create \\\r\n  --process-id my-process \\\r\n  --entry ./process.js#process \\\r\n  --runs-dir .a5c/runs \\\r\n  --inputs inputs.json \\\r\n  --run-id my-run-abc123 \\\r\n  --process-revision v2.1 \\\r\n  --request req-456 \\\r\n  --prompt \"Build the API\" \\\r\n  --harness claude-code \\\r\n  --session-id \"${AGENT_SESSION_ID}\" \\\r\n  --plugin-root \"${CLAUDE_PLUGIN_ROOT}\" \\\r\n  --json \\\r\n  --dry-run\r\n```\r\n\r\n**All `run:create` flags:**\r\n\r\n| Flag | Required | Description |\r\n|------|----------|-------------|\r\n| `--process-id <id>` | Yes | Process identifier |\r\n| `--entry <path#export>` | Yes | Entrypoint file path and export name |\r\n| `--runs-dir <dir>` | No | Root directory for run storage (default: `.a5c/runs`) |\r\n| `--inputs <file>` | No | JSON file with process inputs |\r\n| `--run-id <id>` | No | Override auto-generated run ID |\r\n| `--process-revision <rev>` | No | Process revision tag |\r\n| `--request <id>` | No | Associated request identifier |\r\n| `--prompt <text>` | No | User prompt text |\r\n| `--harness <name>` | No | Harness adapter name (e.g., `claude-code`) |\r\n| `--session-id <id>` | No | Session ID for harness binding |\r\n| `--plugin-root <dir>` | No | Plugin root directory for state resolution |\r\n| `--json` | No | Output as JSON |\r\n| `--dry-run` | No | Preview without creating run |\r\n\r\n### Binding Flow\r\n\r\n```\r\nrun:create command\r\n        |\r\n        v\r\n  Create run directory (.a5c/runs/{runId}/)\r\n  Write run.json, inputs.json, initial journal events\r\n        |\r\n        v\r\n  Detect harness = \"claude-code\"\r\n        |\r\n        v\r\n  bindSessionImpl()\r\n        |\r\n        +-- Resolve stateDir = {pluginRoot}/skills/babysit/state\r\n        |\r\n        +-- Compute filePath = {stateDir}/{sessionId}.md\r\n        |\r\n        +-- Check if state file exists\r\n        |       |\r\n        |       +-- [exists, different runId] -> ERROR: re-entrant run\r\n        |       |\r\n        |       +-- [exists, same/empty runId] -> Update state with runId\r\n        |       |\r\n        |       +-- [not exists] -> Create new state file with runId\r\n        |\r\n        v\r\n  Return SessionBindResult { harness, sessionId, stateFile }\r\n```\r\n\r\n### Re-entrant Run Prevention\r\n\r\nIf a session state file already exists with a different `runId`, the binding fails with:\r\n\r\n```\r\nSession already associated with run: {existingRunId}\r\n```\r\n\r\nThis prevents concurrent runs on the same session. To start a new run, the previous run must complete (state file cleaned up) or the state file must be manually removed.\r\n\r\n### State File After Binding\r\n\r\nAfter successful binding, the state file is updated:\r\n\r\n```yaml\r\n---\r\nactive: true\r\niteration: 1\r\nmax_iterations: 65000\r\nrun_id: \"my-run-abc123\"\r\nstarted_at: \"2026-03-02T10:00:00Z\"\r\nlast_iteration_at: \"2026-03-02T10:00:00Z\"\r\niteration_times:\r\n---\r\n\r\nBuild a REST API with authentication and rate limiting for the user service.\r\n```\r\n\r\n---\r\n\r\n## 5. The Stop Hook -- Core Orchestration Loop Control\r\n\r\n**Generated shell entry:** `artifacts/generated-plugins/claude-code/hooks/babysitter-proxied-stop.sh`\r\n**TypeScript handler:** `handleStopHookImpl()` in `packages/sdk/src/harness/claudeCode.ts`\r\n\r\nThe stop hook is the central mechanism that converts Claude Code's single-turn execution model into a multi-iteration orchestration loop. Every time Claude attempts to end its response, the stop hook intercepts and decides whether to allow the exit or block it with new context.\r\n\r\n### Stop Hook Decision Flow\r\n\r\nThe decision flow is organized into four logical phases:\r\n\r\n#### Phase 1: Input Parsing\r\n\r\n```\r\nClaude Code agent finishes response -> triggers Stop hook\r\n        |\r\n        v\r\nbabysitter-stop-hook.sh\r\n        |\r\n        +-- Resolve babysitter CLI (PATH / $HOME/.local/bin / npx)\r\n        +-- Capture stdin to temp file\r\n        +-- Invoke: babysitter hook:run --hook-type stop \\\r\n        |     --harness claude-code --plugin-root $PLUGIN_ROOT --json\r\n        |\r\n        v\r\nhandleStopHookImpl()\r\n        |\r\n        +-- 1. Read stdin JSON: { session_id, transcript_path, last_assistant_message }\r\n```\r\n\r\n#### Phase 2: Guard Checks\r\n\r\n```\r\n        +-- 2. No session_id? --> APPROVE (allow exit)\r\n        |\r\n        +-- 3. Resolve stateDir, find session file\r\n        |       |\r\n        |       +-- Primary: {pluginRoot}/skills/babysit/state/{sessionId}.md\r\n        |       +-- Fallback: .a5c/state/{sessionId}.md\r\n        |       +-- [not found] --> APPROVE (no active loop)\r\n        |\r\n        +-- 4. Read session state\r\n        |\r\n        +-- 5. Check max iterations\r\n        |       +-- [iteration >= maxIterations] --> APPROVE + mark inactive\r\n        |\r\n        +-- 6. No runId bound? --> APPROVE + mark inactive\r\n```\r\n\r\n#### Phase 3: Run State Evaluation\r\n\r\n```\r\n        +-- 8. Load run state from journal\r\n        |       |\r\n        |       +-- Read run.json metadata\r\n        |       +-- Load journal events\r\n        |       +-- Build effect index\r\n        |       +-- Determine: completed / failed / waiting / created\r\n        |       +-- Count pending effects by kind\r\n        |       +-- [run state unknown] --> ERROR/APPROVE without deleting state\r\n        |\r\n        +-- 9. Parse transcript for <promise> tag\r\n        |       |\r\n        |       +-- Read transcript_path (JSONL file)\r\n        |       +-- Extract last assistant text message\r\n        |       +-- Search for <promise>VALUE</promise>\r\n        |       +-- Fallback: use last_assistant_message from hook input\r\n        |\r\n        +-- 10. Check completion proof\r\n        |       |\r\n        |       +-- [run completed AND promise matches proof] --> APPROVE + mark inactive\r\n```\r\n\r\n#### Phase 4: Output (Block Decision)\r\n\r\n```\r\n        +-- 11. BLOCK: Continue loop\r\n                |\r\n                +-- Increment iteration\r\n                +-- Update session state file\r\n                +-- Build reason (injected to Claude as context)\r\n                +-- Build systemMessage (shown to user)\r\n                +-- Discover relevant skills/agents\r\n                +-- Output: { decision: \"block\", reason, systemMessage }\r\n```\r\n\r\n### State Transition Diagram\r\n\r\n```\r\n                    +----------+\r\n                    |  SESSION  |\r\n                    |  STARTED  |\r\n                    +----+-----+\r\n                         |\r\n                    SessionStart hook\r\n                    creates baseline state\r\n                         |\r\n                         v\r\n                    +----------+\r\n                    |  UNBOUND  |  (state file exists, runId = \"\")\r\n                    +----+-----+\r\n                         |\r\n                    run:create --harness claude-code\r\n                    binds session to run\r\n                         |\r\n                         v\r\n                    +----------+\r\n              +---->|  ACTIVE   |  (state file has runId, iteration N)\r\n              |     +----+-----+\r\n              |          |\r\n              |     Claude stops -> Stop hook fires\r\n              |          |\r\n              |          v\r\n              |     +---------+\r\n              |     | EVALUATE |\r\n              |     +----+----+\r\n              |          |\r\n              |     +----+----+----+----+----+----+\r\n              |     |    |    |    |    |    |    |\r\n              |     v    v    v    v    v    v    v\r\n              |   max  fast  no   run  proof no   otherwise\r\n              |   iter loop  run  unk  match run\r\n              |     |    |    |    |    |    |    |\r\n              |     v    v    v    v    v    v    |\r\n              |   +---------------------------+  |\r\n              |   |        APPROVE            |  |\r\n              |   | (allow exit, cleanup      |  |\r\n              |   |  state file)              |  |\r\n              |   +---------------------------+  |\r\n              |                                  |\r\n              |                                  v\r\n              |                            +---------+\r\n              |                            |  BLOCK  |\r\n              |                            | (inject |\r\n              |                            | context)|\r\n              |                            +----+----+\r\n              |                                 |\r\n              |     Claude resumes with reason  |\r\n              |     (calls run:iterate, etc.)   |\r\n              |                                 |\r\n              +---------------------------------+\r\n```\r\n\r\n### Hook Input Format\r\n\r\nThe stop hook receives JSON on stdin:\r\n\r\n```json\r\n{\r\n  \"session_id\": \"sess-abc123\",\r\n  \"transcript_path\": \"/tmp/claude-transcript-abc123.jsonl\",\r\n  \"last_assistant_message\": \"I've completed the task...\"\r\n}\r\n```\r\n\r\n### Hook Output Format\r\n\r\n**Block (continue loop):**\r\n\r\n```json\r\n{\r\n  \"decision\": \"block\",\r\n  \"reason\": \"Babysitter iteration 3 | Continue orchestration (run:iterate).\\n\\nBuild a REST API...\",\r\n  \"systemMessage\": \"\\uD83D\\uDD04 Babysitter iteration 3/65000 [waiting]\"\r\n}\r\n```\r\n\r\n**Approve (allow exit):**\r\n\r\n```json\r\n{}\r\n```\r\n\r\nAn empty object or `{ \"decision\": \"approve\" }` signals approval. The stop hook outputs `{}` for all approve cases.\r\n\r\n### Block Reason Construction\r\n\r\nThe `reason` field (injected as context to Claude) is constructed from:\r\n\r\n1. **Iteration context** -- varies by run state:\r\n   - Completed: `\"Run completed! To finish: call 'run:status --json', extract 'completionProof', output in <promise>SECRET</promise> tags.\"`\r\n   - Waiting: `\"Waiting on: {pendingKinds}. Check if pending effects are resolved, then call run:iterate.\"`\r\n   - Failed: `\"Run failed. Fix the run, journal or process and proceed.\"`\r\n   - Default: `\"Continue orchestration (run:iterate).\"`\r\n\r\n2. **Discovered skills/agents** -- appended if found (up to 10 items)\r\n\r\n3. **Original prompt** -- the full prompt from the session state file body\r\n\r\nFormat: `\"{iterationContext}\\n\\n{prompt}\"`\r\n\r\n### Journal Event Recording\r\n\r\nEach stop hook invocation appends a `STOP_HOOK_INVOKED` event to the run journal:\r\n\r\n```json\r\n{\r\n  \"sessionId\": \"sess-abc123\",\r\n  \"iteration\": 2,\r\n  \"decision\": \"block\",\r\n  \"reason\": \"continue_loop\",\r\n  \"runState\": \"waiting\",\r\n  \"pendingKinds\": \"node, breakpoint\",\r\n  \"hasPromise\": false,\r\n  \"timestamp\": \"2026-03-02T10:05:30.000Z\"\r\n}\r\n```\r\n\r\n### Approve Conditions (Exit Allowed)\r\n\r\n| Condition | Reason String |\r\n|-----------|---------------|\r\n| No `session_id` in hook input | (no event recorded) |\r\n| No session state file found | (no event recorded) |\r\n| `iteration >= maxIterations` | `max_iterations_reached` |\r\n| No `runId` bound to session | (cleanup, no event) |\r\n| Run state unknown/unreadable | `run_state_unknown` |\r\n| Promise tag matches completion proof | `completion_proof_matched` |\r\n\r\n---\r\n\r\n## 6. The Iteration Loop\r\n\r\nThe iteration loop is not a programmatic loop within any single process. It is an emergent loop created by the interaction between the babysitter skill (running inside Claude Code) and the stop hook.\r\n\r\n### Single Iteration Sequence\r\n\r\n```\r\n[Claude resumes with stop-hook context]\r\n        |\r\n        v\r\n  1. babysitter run:iterate .a5c/runs/{runId} --json\r\n        |\r\n        +-- orchestrateIteration() replays journal, runs process function\r\n        |   |\r\n        |   +-- Process calls ctx.task() / ctx.breakpoint() / etc.\r\n        |   +-- Replay engine checks effect index\r\n        |   +-- Resolved effects: return cached result\r\n        |   +-- Unresolved: throw EffectRequestedError\r\n        |   +-- New effects: append EFFECT_REQUESTED to journal\r\n        |\r\n        +-- Output: { status, action, count, completionProof?, effects[] }\r\n        |\r\n        v\r\n  2. babysitter task:list .a5c/runs/{runId} --pending --json\r\n        |\r\n        +-- Lists all pending (unresolved) effects\r\n        +-- Output: { tasks: [{ effectId, kind, status, label }] }\r\n        |\r\n        v\r\n  3. For each pending effect:\r\n        |\r\n        +-- [kind=node]       Execute Node.js task\r\n        +-- [kind=agent]      Delegate to agent via Task tool\r\n        +-- [kind=skill]      Invoke Claude Code skill\r\n        +-- [kind=breakpoint] Ask user (interactive) or auto-resolve (non-interactive)\r\n        +-- [kind=sleep]      Wait until time condition met\r\n        |\r\n        v\r\n  4. babysitter task:post .a5c/runs/{runId} {effectId} \\\r\n       --status ok --value {valueFile} --json\r\n        |\r\n        +-- Writes result.json to tasks/{effectId}/\r\n        +-- Appends EFFECT_RESOLVED event to journal\r\n        +-- Updates state cache\r\n        |\r\n        v\r\n  5. Claude STOPS (ends response)\r\n        |\r\n        v\r\n  [Stop Hook fires] --> evaluates --> BLOCK with next iteration context\r\n        |\r\n        v\r\n  [Claude resumes] --> back to step 1\r\n```\r\n\r\n### run:iterate Output Schema\r\n\r\n```json\r\n{\r\n  \"iteration\": 3,\r\n  \"status\": \"executed\",\r\n  \"action\": \"executed-tasks\",\r\n  \"reason\": \"auto-runnable-tasks\",\r\n  \"count\": 2,\r\n  \"metadata\": { \"runId\": \"my-run\", \"processId\": \"my-process\" }\r\n}\r\n```\r\n\r\n| Status | Meaning | Next Action |\r\n|--------|---------|-------------|\r\n| `executed` | Tasks were requested | Execute pending effects, post results, stop |\r\n| `waiting` | Breakpoint or sleep pending | Handle breakpoint/sleep, post result, stop |\r\n| `completed` | Run finished | Extract `completionProof`, output in `<promise>` tag |\r\n| `failed` | Run errored | Inspect and fix, re-iterate |\r\n| `none` | No pending effects | Stop (hook may continue or allow exit) |\r\n\r\n### task:post Protocol\r\n\r\nResults must be posted through the CLI, not by writing `result.json` directly:\r\n\r\n```bash\r\n# Write value to separate file\r\necho '{\"score\": 85, \"details\": {...}}' > tasks/{effectId}/output.json\r\n\r\n# Post through CLI (creates result.json + journal event + cache update)\r\nbabysitter task:post .a5c/runs/{runId} {effectId} \\\r\n  --status ok \\\r\n  --value tasks/{effectId}/output.json \\\r\n  --json\r\n```\r\n\r\nThe `task:post` command:\r\n1. Reads the value from the specified file\r\n2. Writes `tasks/{effectId}/result.json` with schema version and metadata\r\n3. Appends an `EFFECT_RESOLVED` event to `journal/`\r\n4. Updates `state/state.json` cache\r\n\r\n---\r\n\r\n## 7. Native Orchestration Hooks\r\n\r\n**SDK hook discovery:** `packages/sdk/src/hooks/dispatcher.ts`\r\n\r\nThe hook dispatcher executes native babysitter lifecycle hooks (distinct from Claude Code's `SessionStart`/`Stop` hooks). These hooks are triggered by the SDK runtime during `run:iterate`.\r\n\r\n### Hook Types Triggered During Iteration\r\n\r\n| Hook | When | Triggered By |\r\n|------|------|-------------|\r\n| `on-iteration-start` | Before orchestrateIteration() | `run:iterate` command |\r\n| `on-iteration-end` | After orchestrateIteration() | `run:iterate` command |\r\n| `on-run-start` | Run created | `run:create` command |\r\n| `on-run-complete` | Run finished successfully | orchestrateIteration() |\r\n| `on-run-fail` | Run failed | orchestrateIteration() |\r\n| `on-task-start` | Task execution begins | Effect executor |\r\n| `on-task-complete` | Task execution ends | Effect executor |\r\n| `on-breakpoint` | Breakpoint reached | orchestrateIteration() |\r\n| `on-step-dispatch` | Effect dispatched | Replay engine |\r\n| `on-score` | Quality score posted | Score handler |\r\n| `pre-commit` | Before git commit | Git integration |\r\n| `pre-branch` | Before branch creation | Git integration |\r\n| `post-planning` | Planning phase complete | Planning handler |\r\n\r\n### Hook Discovery Priority\r\n\r\nThe dispatcher searches for hook scripts in three directories, executing in order:\r\n\r\n```\r\n1. Per-repo:  {REPO_ROOT}/.a5c/hooks/{hookType}/*.sh    (highest priority)\r\n2. Per-user:  ~/.config/babysitter/hooks/{hookType}/*.sh  (medium priority)\r\n3. Plugin:    {PLUGIN_ROOT}/hooks/{hookType}/*.sh         (lowest priority)\r\n```\r\n\r\nWithin each directory, scripts are sorted alphabetically and executed sequentially. Each script receives the hook payload on stdin. Individual hook failures do not fail the dispatcher -- it continues executing remaining hooks.\r\n\r\n### Breakpoint Hook Dispatcher\r\n\r\n**Unified source hooks:** `plugins/babysitter-unified/hooks/`\r\n\r\nA specialized dispatcher for breakpoint events. Same three-tier discovery as the generic dispatcher but specific to the `on-breakpoint` hook type. Receives breakpoint payload on stdin via `BREAKPOINT_PAYLOAD` environment variable.\r\n\r\n---\r\n\r\n## 8. Breakpoint Handling\r\n\r\nBreakpoints are human-approval gates within a process. When the process function calls `ctx.breakpoint()`, the replay engine throws an `EffectRequestedError` with kind `breakpoint`.\r\n\r\n### Interactive Mode (Default)\r\n\r\nWhen Claude Code has access to the `AskUserQuestion` tool:\r\n\r\n```\r\nrun:iterate detects breakpoint effect\r\n        |\r\n        v\r\n  task:list shows: { kind: \"breakpoint\", status: \"requested\" }\r\n        |\r\n        v\r\n  Skill reads breakpoint question from task.json\r\n        |\r\n        v\r\n  AskUserQuestion tool presented to user\r\n  (MUST include explicit \"Approve\" / \"Reject\" options)\r\n        |\r\n        v\r\n  User selects option\r\n        |\r\n        +-- [empty/dismissed/ambiguous] --> Re-ask. NEVER assume approval.\r\n        |\r\n        +-- [explicit approve/reject] --> Post result via task:post\r\n        |\r\n        v\r\n  babysitter task:post {runId} {effectId} --status ok --value {response.json}\r\n        |\r\n        v\r\n  Next iteration replays breakpoint with cached result\r\n```\r\n\r\n**Validation rules for interactive breakpoints:**\r\n\r\n1. AskUserQuestion MUST include explicit approve/reject options\r\n2. Empty, dismissed, or ambiguous responses are treated as NOT approved\r\n3. Never fabricate or infer approval text\r\n4. Only pass the user's actual response verbatim\r\n5. Never offer \"chat about this\" options -- only explicit choices or free-text\r\n\r\n### Non-Interactive Mode (Running with `-p` flag)\r\n\r\nWhen `AskUserQuestion` is unavailable:\r\n\r\n```\r\nrun:iterate detects breakpoint effect\r\n        |\r\n        v\r\n  Skill reads breakpoint context from task.json\r\n        |\r\n        v\r\n  Auto-resolve: select best option based on context and user intent\r\n        |\r\n        v\r\n  babysitter task:post {runId} {effectId} --status ok --value {decision.json}\r\n```\r\n\r\n---\r\n\r\n## 9. Session Check-Iteration\r\n\r\n**CLI command:** `babysitter session:check-iteration`\r\n**Handler:** `handleSessionCheckIteration()` in `packages/sdk/src/cli/commands/session.ts`\r\n\r\nThe stop hook and `session:check-iteration` enforce only the max-iterations limit. Iteration-speed stopping is disabled; iteration timing is retained only as diagnostic data.\r\n\r\n### Max Iterations Guard\r\n\r\n``` \r\nIF iteration >= maxIterations (default 65000):\r\n    APPROVE exit, mark session inactive\r\n``` \r\n\r\n### Timing Diagnostics\r\n\r\nThe `iterationTimes` array stores recent iteration durations in seconds for diagnostics, but it does not stop or approve the loop by itself.\r\n\r\n### session:check-iteration Output\r\n\r\n```json\r\n{\r\n  \"found\": true,\r\n  \"shouldContinue\": true,\r\n  \"nextIteration\": 4,\r\n  \"updatedIterationTimes\": [45, 62, 58],\r\n  \"iteration\": 3,\r\n  \"maxIterations\": 65000,\r\n  \"runId\": \"my-run-abc123\",\r\n  \"prompt\": \"Build the API...\"\r\n}\r\n```\r\n\r\nWhen `shouldContinue` is `false`, includes `reason` and `stopMessage`:\r\n\r\n```json\r\n{\r\n  \"found\": true,\r\n  \"shouldContinue\": false,\r\n  \"averageTime\": 8.3,\r\n  \"threshold\": 15,\r\n  \"stopMessage\": \"Average iteration time too fast (8.3s <= 15s)\"\r\n}\r\n```\r\n\r\n---\r\n\r\n## 10. Completion Proof and Clean Exit\r\n\r\nThe completion proof is a cryptographic mechanism that prevents premature exit from the orchestration loop. Only when the run has genuinely completed does the proof become available.\r\n\r\n### Proof Generation\r\n\r\n**File:** `packages/sdk/src/cli/completionProof.ts`\r\n\r\n```typescript\r\nconst COMPLETION_PROOF_SALT = \"babysitter-completion-secret-v1\";\r\n\r\nfunction deriveCompletionProof(runId: string): string {\r\n  return sha256(`${runId}:${COMPLETION_PROOF_SALT}`);\r\n}\r\n\r\nfunction resolveCompletionProof(metadata: RunMetadata): string {\r\n  return metadata.completionProof ?? deriveCompletionProof(metadata.runId);\r\n}\r\n```\r\n\r\nThe proof is a SHA-256 hash of `{runId}:{salt}`. It is stored in `run.json` metadata or derived on demand.\r\n\r\n### Proof Verification in Stop Hook\r\n\r\n```\r\nStop hook fires\r\n        |\r\n        v\r\n  Load journal -> check for RUN_COMPLETED event\r\n        |\r\n        +-- [not completed] -> no proof available\r\n        |\r\n        +-- [completed] -> resolveCompletionProof(metadata)\r\n        |\r\n        v\r\n  Parse transcript for <promise>VALUE</promise> tag\r\n        |\r\n        +-- extractPromiseTag(lastAssistantText)\r\n        |   Returns content between first <promise>...</promise> tags\r\n        |   Trims whitespace, collapses internal whitespace\r\n        |\r\n        v\r\n  Compare: promiseValue === completionProof\r\n        |\r\n        +-- [match] -> APPROVE exit, mark state inactive\r\n        |\r\n        +-- [no match] -> BLOCK with hint:\r\n              \"Run completed! Extract completionProof from run:status --json,\r\n               output in <promise>SECRET</promise> tags.\"\r\n```\r\n\r\n### Promise Tag Format\r\n\r\n```\r\n<promise>a1b2c3d4e5f6...</promise>\r\n```\r\n\r\nThe agent must output the exact completion proof value inside `<promise>` tags. The extraction function:\r\n\r\n```typescript\r\nfunction extractPromiseTag(text: string): string | null {\r\n  const match = text.match(/<promise>([\\s\\S]*?)<\\/promise>/);\r\n  if (!match) return null;\r\n  return match[1].trim().replace(/\\s+/g, ' ');\r\n}\r\n```\r\n\r\n### Complete Exit Sequence\r\n\r\n```\r\nrun:iterate returns { status: \"completed\", completionProof: \"abc123...\" }\r\n        |\r\n        v\r\n  Claude outputs: <promise>abc123...</promise>\r\n        |\r\n        v\r\n  Claude STOPS\r\n        |\r\n        v\r\n  Stop hook fires\r\n        |\r\n        v\r\n  Load journal: RUN_COMPLETED event found\r\n  Derive proof: sha256(\"{runId}:babysitter-completion-secret-v1\")\r\n  Parse transcript: extract <promise>abc123...</promise>\r\n        |\r\n        v\r\n  promiseValue === completionProof -> MATCH\r\n        |\r\n        v\r\n  Append STOP_HOOK_INVOKED event (reason: \"completion_proof_matched\")\r\n        |\r\n        v\r\n  Delete session state file (cleanup)\r\n        |\r\n        v\r\n  Output: {}  (APPROVE)\r\n        |\r\n        v\r\n  Claude Code session ends normally\r\n```\r\n\r\n### Session Completion Marking\r\n\r\nOn approve decisions that end hook blocking, the stop hook retains the session state file and marks it inactive instead of deleting it. This preserves recovery context while ensuring later `hook:run` calls return without blocking even if the run remains associated in the retained state file.\r\n\r\nThis ensures that:\r\n- Completion and guard exits are auditable after the fact\r\n- The next hook invocation does not block on inactive retained state\r\n- Recovery tooling can still inspect the previous session/run association\r\n\r\n---\r\n\r\n## Harness Adapter Architecture\r\n\r\n**Files:**\r\n- `packages/sdk/src/harness/types.ts` -- Interface definition\r\n- `packages/sdk/src/harness/claudeCode.ts` -- Claude Code implementation\r\n- `packages/sdk/src/harness/nullAdapter.ts` -- No-op fallback\r\n- `packages/sdk/src/harness/registry.ts` -- Auto-detection and lookup\r\n\r\nThe harness adapter pattern abstracts host-specific behaviors so the SDK core remains harness-agnostic. The `HarnessAdapter` interface defines:\r\n\r\n```typescript\r\ninterface HarnessAdapter {\r\n  readonly name: string;\r\n  isActive(): boolean;\r\n  resolveSessionId(parsed: { sessionId?: string }): string | undefined;\r\n  resolveStateDir(args: { stateDir?: string; pluginRoot?: string }): string | undefined;\r\n  resolvePluginRoot(args: { pluginRoot?: string }): string | undefined;\r\n  bindSession(opts: SessionBindOptions): Promise<SessionBindResult>;\r\n  handleStopHook(args: HookHandlerArgs): Promise<number>;\r\n  handleSessionStartHook(args: HookHandlerArgs): Promise<number>;\r\n  findHookDispatcherPath(startCwd: string): string | null;\r\n}\r\n```\r\n\r\n### Adapter Detection\r\n\r\nThe registry probes adapters in priority order. The Claude Code adapter reports active when either `AGENT_SESSION_ID` or `CLAUDE_ENV_FILE` is set:\r\n\r\n```typescript\r\nisActive(): boolean {\r\n  return !!(process.env.AGENT_SESSION_ID || process.env.CLAUDE_ENV_FILE);\r\n}\r\n```\r\n\r\nIf no adapter matches, the null adapter is used, which approves all stop hooks (no orchestration loop) and returns safe defaults.\r\n\r\n### hookRun Command Dispatch\r\n\r\n**File:** `packages/sdk/src/cli/commands/hookRun.ts`\r\n\r\nThe `hook:run` command routes to the appropriate adapter method:\r\n\r\n```\r\nbabysitter hook:run --hook-type stop --harness claude-code\r\n        |\r\n        v\r\n  getAdapterByName(\"claude-code\") -> ClaudeCodeAdapter\r\n        |\r\n        v\r\n  switch (hookType):\r\n    case \"stop\":          adapter.handleStopHook(args)\r\n    case \"session-start\": adapter.handleSessionStartHook(args)\r\n```\r\n\r\n---\r\n\r\n## File Reference\r\n\r\n| File | Role |\r\n|------|------|\r\n| `plugins/babysitter-unified/plugin.json` | Unified source manifest used to generate harness-specific bundles |\r\n| `artifacts/generated-plugins/claude-code/plugin.json` | Generated Claude Code plugin manifest |\r\n| `artifacts/generated-plugins/claude-code/hooks/hooks.json` | Claude Code hook registration file |\r\n| `artifacts/generated-plugins/claude-code/hooks/babysitter-proxied-session-start.sh` | Generated shell entry for SessionStart |\r\n| `artifacts/generated-plugins/claude-code/hooks/babysitter-proxied-stop.sh` | Generated shell entry for Stop |\r\n| `packages/sdk/src/hooks/dispatcher.ts` | SDK hook discovery for native babysitter lifecycle hooks |\r\n| `plugins/babysitter-unified/hooks/` | Unified source hook implementations copied into generated bundles |\r\n| `plugins/babysitter-unified/skills/babysit/SKILL.md` | Primary orchestration skill definition |\r\n| `packages/sdk/src/harness/types.ts` | HarnessAdapter interface definition |\r\n| `packages/sdk/src/harness/claudeCode.ts` | Claude Code adapter (stop hook, session-start, binding) |\r\n| `packages/sdk/src/harness/nullAdapter.ts` | No-op fallback adapter |\r\n| `packages/sdk/src/harness/registry.ts` | Adapter auto-detection and lookup registry |\r\n| `packages/sdk/src/harness/index.ts` | Harness module public exports |\r\n| `packages/sdk/src/session/types.ts` | SessionState, SessionFile, error types |\r\n| `packages/sdk/src/session/parse.ts` | YAML frontmatter parsing, state file reading |\r\n| `packages/sdk/src/session/write.ts` | Atomic state file writes, timing utilities |\r\n| `packages/sdk/src/session/index.ts` | Session module public exports |\r\n| `packages/sdk/src/cli/commands/hookRun.ts` | hook:run CLI command dispatcher |\r\n| `packages/sdk/src/cli/commands/session.ts` | session:* CLI commands including check-iteration |\r\n| `packages/sdk/src/cli/commands/runIterate.ts` | run:iterate CLI command |\r\n| `packages/sdk/src/cli/completionProof.ts` | Completion proof derivation (SHA-256) |\n",
    "documents": []
  },
  "outgoingEdges": [],
  "incomingEdges": []
}

Shortcuts

Back to overview
Open graph tab