Skip to content

Chat → Execution Sequence

When you type into Claude Code, fourteen distinct components participate in producing your response. Hooks fire invisibly around tool calls; subagents spawn in isolated subprocesses; every LLM token funnels through one Portkey proxy on 127.0.0.1:18900; every tool call writes a row to Supabase tool_calls for cost + audit. If anything’s broken on the platform, this map is where you start. It is also the canonical answer to “what actually happens when Henry types something” — the question every team member asks and every existing doc dodged.

This page is operational. The diagram below is the source of truth; the prose below it explains every box and every arrow with real file paths, real failure modes, and the live commands you’d run to inspect each stage on a running system. Skim At a glance + The diagram + When to use this for orientation. Drop into Components + Transitions when you’re isolating a bug. Use Worked scenarios when you have a symptom but don’t know which arrow snapped.

TriggerWhy this map
Onboarding a new team memberSingle doc that explains how chat-driven execution works end-to-end. Day 4 of the onboarding lands here.
”Session feels off” — Claude has no context, asks questions you’ve already answeredSessionStart hook didn’t fire or didn’t load SESSION-AUDIT — see Component §2.
LLM call hung or timed outPortkey on 127.0.0.1:18900 is the single chokepoint — start the diagnostic at Component §7.
Memory didn’t get writtenPostToolUse hook → Memory pipeline; trace which step dropped — see Component §13 + Scenario 5.
Subagent dispatched but result wrongOpus skipped the trust-but-verify gate — see Component §6 + Scenario 2.
Bash command unexpectedly blockedpre-bash-check.sh matched a blocklist regex — see Component §4 + Scenario 3.
Cost reconciliation mismatchSome code path bypassed Portkey (CHOKEPOINT-1 violation) — every LLM call MUST write a tool_calls row. See Component §7 + the verify checklist.
Pre-change risk assessmentIf you’re touching a hook, the gateway, the proxy, the memory CLI, or the audit log, you’re touching a stage in this diagram. Read first; cite which arrow you’re modifying in the PR.
Stop HookMemory PipelinePostToolUse HookLangfuse :18840Supabase tool_callsAnthropicPortkey :18900Sonnet SubagentTool SelectorPreToolUse HookCLAUDE.mdSessionStart HookClaude Code CLIStop HookMemory PipelinePostToolUse HookLangfuse :18840Supabase tool_callsAnthropicPortkey :18900Sonnet SubagentTool SelectorPreToolUse HookCLAUDE.mdSessionStart HookClaude Code CLIalt[Execution-mode trigger fires][Direct read/edit/bash]at session endUserprompt1fire (first turn only)2inject SESSION-AUDIT + ACTIVE-PROJECT + memory hits3read governance + Tool Trigger Conditions4route per CLAUDE.md (Plan Check → Agent → Workspace → Execution)5dispatch with 8-element prompt + model:sonnet6validate Bash if any7POST /v1/messages8forward + Portkey config + cache namespace9response10log tool_calls row (non-blocking)11trace ingest (non-blocking)12response13structured result14independent verification (trust-but-verify)15validate16execute17post-tool fires18enqueue summary19Voyage-4 embed20store in vec0 + FTS521response text22stop hook23extract + append SESSION-AUDIT.md24User

Reading the diagram: numbered arrows are the temporal sequence (top to bottom). The alt / else block shows the two paths Claude can take per tool call — execution-mode dispatch to a Sonnet subagent (the heavyweight path used when CLAUDE.md detects an execution trigger) versus a direct built-in tool (Read / Edit / Bash, the lightweight path). Dashed arrows like PK -)) SUP are non-blocking fire-and-forget side-channels — they don’t gate the main flow; if Supabase is unreachable, the LLM call still returns to the user, and the audit row writes when the queue drains.

1. Claude Code CLI (the interactive harness)

Section titled “1. Claude Code CLI (the interactive harness)”

The binary Henry types into; spawns from the claude command. Distributed by Anthropic, not part of OpenClaw. It owns the session JSONL at /home/opsadmin/.claude/projects/-home-opsadmin/<session-uuid>.jsonl — every prompt, every tool call, every response is appended there in real time. The CLI also owns the tool-call cycle: even a “what time is it?” prompt routes through this whole diagram.

Inputs: prompt text (plus optional images for multimodal). Outputs: response text + tool invocations. Failure mode: if the CLI crashes mid-session, the JSONL is the only record — your context is recoverable but not your in-flight tool result.

2. SessionStart Hook (continuity bootstrap)

Section titled “2. SessionStart Hook (continuity bootstrap)”

Fires once per session, on the first turn (NOT every prompt — that’s a common confusion). The harness invokes the script chain registered in ~/.claude/settings.json under hooks.SessionStart: gsd-check-update.js checks the GSD framework version, session-catchup.py reads SESSION-AUDIT.md + ACTIVE-PROJECT.md and injects a 3-level continuity summary into Claude’s context, plan-suggest.py surfaces any pending phases, and ~/.openclaw/tools/hooks/on-session-start.sh writes a /tmp/claude-session-<PPID>.marker file (the Stop hook reads this later to know which session ended) and sources credentials from ~/.openclaw/master.env.

Why it matters: without this, Claude opens with no continuity — it’ll feel disoriented, ask questions you’ve already answered, miss active plans. Verify: the marker file exists at /tmp/claude-session-*.marker after first turn.

3. CLAUDE.md (governance read on every turn)

Section titled “3. CLAUDE.md (governance read on every turn)”

Lives at /home/opsadmin/CLAUDE.md (project-level) plus ~/.claude/CLAUDE.md (user-level). Claude re-reads it every turn — not just session start. Critical sections it draws from: Tool Trigger Conditions (~80 P0–P3 entries mapping keywords/events to skills), the Action Gate rules (reads/logs = unilateral; writes/restarts/API-calls-with-side-effects = wait for Henry), the Request Routing Protocol (Plan Check → Agent Assignment → Workspace → Execution → Post-Execution), and every MANDATORY: section.

Effect on the flow: determines which tool fires (Skill / Agent / MCP / Bash / Read / Edit) and which model tier executes (Opus stays / Sonnet dispatches / Haiku trivial reads). Failure mode: if CLAUDE.md is corrupted or unreadable, governance is silently bypassed — Claude routes by heuristic only.

Fires before every Bash | Read | Edit | Glob | Grep tool call. The script chain is in settings.json; the load-bearing one for safety is ~/.openclaw/tools/hooks/pre-bash-check.sh. It reads the proposed Bash command from stdin (JSON-shaped), pattern-matches against a blocklist (rm -rf on system dirs, DROP TABLE, credential file overwrite, curl | bash, pkill on production services, etc.), and exits 2 to block or 0 to allow.

Block: Claude sees a blocked-by-safety-check error; must rethink. Allow when shouldn’t: bug in the blocklist regex — review and tighten. The other PreToolUse scripts (gsd-context-monitor.js, plan-context.sh) inject phase-plan context but don’t gate.

5. Tool Selector (Claude’s reasoning step)

Section titled “5. Tool Selector (Claude’s reasoning step)”

Conceptual rather than a separate process. Claude inspects the user intent + CLAUDE.md governance and picks the right tool: built-in (Read / Edit / Bash / Glob / Grep) for direct shell ops, Skill tool for slash commands or keyword auto-trigger matches, Agent tool for execution-mode dispatch (spawns Sonnet/Haiku in subprocess), MCP tool for external system access (24 servers registered, 14 currently connected). The decision tree for model tier (Opus stays / Sonnet dispatches / Haiku trivial reads) is documented at Model Routing.

6. Sonnet Subagent (execution-mode dispatch path)

Section titled “6. Sonnet Subagent (execution-mode dispatch path)”

Spawned by Opus calling the Agent tool with model: "sonnet" whenever CLAUDE.md detects an execution-mode trigger — /gsd:* execution commands, execution skills (/acquisitions-outreach, /dispo-blast, etc.), TodoWrite transitions to in_progress, plan checkbox transitions, user keywords like “go”, “implement”, “ship it”, “do all of them”. Runs in an isolated subprocess with its own context window — reads the same CLAUDE.md but doesn’t pollute Opus’s working memory with intermediate file reads.

The subagent receives an 8-element prompt contract (goal, evidence, tool, tests, acceptance, rollback, rule constraints, output format — see Agent Dispatch Sequence for the full contract). It returns a structured text report. Critically, Opus then runs an independent acceptance check (grep, file existence, test count) before declaring complete — trusted-but-verified. If verification fails, Opus re-dispatches with a corrected prompt OR does the work itself.

7. Portkey Proxy on 127.0.0.1:18900 (THE chokepoint)

Section titled “7. Portkey Proxy on 127.0.0.1:18900 (THE chokepoint)”

Lives at ~/.openclaw/portkey-proxy/proxy.js. Single chokepoint for every LLM call from any agent to any provider — Anthropic, OpenAI, Moonshot, Google, OpenRouter (Gemma / Kimi / DeepSeek / Qwen), Voyage AI for embeddings. Routing logic: reads ~/.openclaw/portkey-proxy/tier-config.json → looks up the requested model name → maps to a Portkey config slug (pc-opus-f-be2d19, pc-sonnet-df3a4c, pc-haiku-5c4a73, pc-opencl-22d508 for Voyage-4, etc.) → forwards to provider with cache namespace + metadata + fallback chain.

Every call writes a tool_calls row to Supabase (CHOKEPOINT-1) and a Langfuse trace to 127.0.0.1:18840 (both fire-and-forget, 2-second timeouts). Bypassing Portkey = no cost tracking, no audit, no observability — never let any code call providers directly. Always route through :18900. If the proxy is down, ALL agents stop responding. Health check: systemctl --user status portkey-proxy. Logs: journalctl --user -u portkey-proxy --since "5 min ago" --no-pager.

End provider. Whatever Portkey forwards arrives here. Provider-side outages show up as 429 / 500 / timeout responses bubbling back through PK -->> SUB. Verify the provider is up: https://status.anthropic.com/. Common confusion: if Anthropic is fine but the call still fails, check Portkey config + credentials before blaming the provider.

Single source of truth for cost + audit. Every LLM call writes one row: agent_id, tool_name, parameters (JSONB), result_ok, latency_ms, called_at, cost_usd, tokens_in/out, cache_read/write_tokens. Required NOT-NULL: agent_id, tool_name, called_at, result_ok, latency_ms. Nullable INTENTIONALLY: cost_usd + token counts (NULL = plan-tier flat-rate Max-plan call; >0 = API-tier paid call). The CHOKEPOINT-1 governance gate enforces that NO LLM call may return without writing this row. If Postgres is unreachable, Portkey writes to a /tmp/openclaw/tool-calls-fallback.jsonl queue and drains within 1h or escalates to Discord.

Self-hosted Langfuse at port 18840. Receives a trace per call alongside the tool_calls row. Used for span-level latency, prompt diffs, eval datasets, scoring. Non-blocking — if it’s down, the user-facing call still returns. Dashboard: https://langfuse.reri.co/. Verify: docker logs langfuse-web for ingestion errors.

Fires after every tool call completes. The script chain: gsd-context-monitor.js tracks context window pressure; ~/.openclaw/tools/hooks/on-post-tool.sh parses the tool name + key inputs from stdin JSON and appends a JSONL entry to ~/.openclaw/logs/claude-code-audit.log. For memory_search / memory_save / workspace_query calls it sets a /tmp/session-memory-used-<PPID>.flag for compliance tracking. For Write/Edit on files in ~/.claude/plans/*.md it writes a re-index trigger to /tmp/plan-index-trigger-<PPID>.txt.

12. Memory Pipeline (Voyage-4 → vec0+FTS5)

Section titled “12. Memory Pipeline (Voyage-4 → vec0+FTS5)”

When PostToolUse fires for relevant tool calls, the pipeline embeds the result via Voyage-4 (through Portkey config pc-opencl-22d508) and stores in 44 SQLite databases at ~/.openclaw/memory/<agent>.sqlite. Each DB has 5 tables: chunks (raw text + metadata), chunks_fts (SQLite FTS5 keyword index), chunks_vec (vec0 vector index), pending_saves (queue), dead_letters (failed embeds). Hybrid retrieval at 70% vector + 30% BM25 keyword. Detail at Memory Pipeline.

Fires when Claude Code signals session end. ~/.openclaw/tools/hooks/on-stop.sh reads the session UUID from the marker file the SessionStart hook wrote, calls session-summary-extractor.py to pull a structured summary from the JSONL, appends it as a ### Session Summary (YYYY-MM-DD-HHMM) block to SESSION-AUDIT.md, enqueues the summary text into the memory pipeline, then runs check-complete.sh to verify phase completion status. Marker files cleaned up on success.

User → Claude Code CLI: plain text or markdown, possibly with images. Logged immediately to session JSONL. Nothing user-visible can fail here.

Claude Code CLI → SessionStart Hook: triggers on first user message OR after /clear. Payload: session UUID, working directory, hook event JSON. Fail mode: hook script exits non-zero → context inject silently skipped → Claude opens stale.

SessionStart Hook → Claude Code CLI: injects ~3-level continuity summary into Claude’s context. Fail mode: SESSION-AUDIT.md > 24h old or empty → continuity inject is empty → Claude has no context. Manual fix: edit SESSION-AUDIT to current state, restart session.

Claude Code CLI → CLAUDE.md: filesystem read. Not really an arrow per se — Claude has CLAUDE.md content in its system prompt every turn. Fail mode: if CLAUDE.md was just rewritten and contains malformed YAML or broken markdown, Claude may fail to parse some sections — manifests as governance rules not being applied.

Claude Code CLI → PreToolUse Hook: stdin gets a JSON document {tool_name, tool_input}. Fail mode: hook hangs (>5s) → harness kills it, allows tool by default. Mitigation: keep hooks fast.

Sonnet Subagent → Portkey :18900: standard POST /v1/messages body to localhost:18900. Headers include the agent identifier in the URL path. Fail mode: Portkey down → connection refused → subagent crashes → Opus must retry or take over.

Portkey → Anthropic: outbound HTTPS to api.anthropic.com. Fail mode: TLS handshake failure (cert rotation, bad clock) → 502 from proxy. Provider 5xx → fallback chain in tier-config kicks in (e.g., OpenRouter as backup).

Portkey -)) Supabase tool_calls: non-blocking insert. Fail mode: Supabase unreachable → row queued to /tmp fallback → drains in 1h or alerts. Why it matters: if this arrow consistently breaks, your cost dashboard goes silent without warning.

Portkey -)) Langfuse :18840: non-blocking trace ingest. Fail mode: Langfuse down → traces dropped silently. Less critical than tool_calls (Langfuse is for analytics; tool_calls is the audit-of-record).

Claude Code CLI → PostToolUse Hook: runs after every tool call. JSONL audit append + plan-index trigger + memory-used flag.

PostToolUse → Memory Pipeline: enqueues via claude-memory.py enqueue to the pending_saves table of the active agent’s SQLite DB. Fail mode: if the agent DB is locked by a long-running query, the enqueue blocks (rare).

Memory Pipeline → Voyage-4: outbound HTTPS via Portkey. Same fail modes as any LLM call.

Memory Pipeline → vec0 + FTS5: local SQLite write. Fail mode: disk full → write fails → row moves to dead_letters.

Stop Hook → Memory + SESSION-AUDIT.md: appends two artifacts. Fail mode: if session-summary-extractor.py crashes (e.g., JSONL truncated), session ends with no summary written — visible as “no ### Session Summary block at end of SESSION-AUDIT.md”.

Scenario 1 — “Prompt returned but nothing logged”

Section titled “Scenario 1 — “Prompt returned but nothing logged””

Symptom: Claude answered, but tail ~/.openclaw/logs/claude-code-audit.log shows nothing new and python3 ~/.openclaw/tools/claude-memory.py stats is unchanged.

Likely broken arrow: Claude Code CLI → PostToolUse Hook — the hook script crashed silently or wasn’t registered.

Diagnosis:

  1. jq '.hooks.PostToolUse' ~/.claude/settings.json — is the hook registered with the right matcher?
  2. bash ~/.openclaw/tools/hooks/on-post-tool.sh </tmp/test-stdin.json — does it exit 0 against synthetic input?
  3. journalctl --user --since "10 min ago" | grep on-post-tool — error trail?

Fix: repair the script, restart the session, verify next tool call appends to audit log.

Scenario 2 — “Sonnet subagent ran but Opus accepted a wrong result”

Section titled “Scenario 2 — “Sonnet subagent ran but Opus accepted a wrong result””

Symptom: Opus said “task complete” after a Sonnet dispatch, but the actual artifact (file edit, build output, test pass) didn’t materialize.

Likely broken arrow: Opus → Verify — Opus skipped the trust-but-verify gate.

Diagnosis: This is a governance violation per feedback_model_routing_at_exec.md. Opus MUST run an independent acceptance check after every Sonnet dispatch (grep for the changed line, file-existence check, test-count count, etc.). Look at the conversation transcript: did Opus run a verification command after the Sonnet report?

Fix: re-dispatch with a tightened acceptance criterion in the 8-element prompt; Opus must verify before next response. Long-term: enforce in CLAUDE.md governance.

Scenario 3 — “Bash blocked unexpectedly on a safe command”

Section titled “Scenario 3 — “Bash blocked unexpectedly on a safe command””

Symptom: pre-bash-check.sh blocked rm -rf /tmp/qvw5 (a safe /tmp cleanup); regression.

Likely broken arrow: PreToolUse blocklist regex too broad.

Diagnosis:

  1. Read ~/.openclaw/tools/hooks/pre-bash-check.sh blocklist patterns.
  2. Identify the matching pattern — common false positive: any command containing rm -rf regardless of path.

Fix: tighten the regex to require a system path (/, /etc/, /usr/, /var/) rather than blanket rm -rf. Add /tmp/* as an explicit allowlist.

Scenario 4 — “LLM call hangs or returns 5xx”

Section titled “Scenario 4 — “LLM call hangs or returns 5xx””

Symptom: Claude session feels unresponsive; eventual generic error.

Likely broken arrow: Portkey → Anthropic provider-side OR Portkey itself crashed.

Diagnosis:

  1. systemctl --user status portkey-proxy — running?
  2. journalctl --user -u portkey-proxy --since "5 min ago" --no-pager | tail -30 — proxy errors?
  3. https://status.anthropic.com/ — provider outage?
  4. tail -50 /tmp/openclaw/openclaw-$(date +%Y-%m-%d).log — request-level errors?

Fix: restart proxy if down (systemctl --user restart portkey-proxy); wait out provider outage; if it’s a routing issue, inspect tier-config.json for stale config slug.

Scenario 5 — “Memory search returns stale results”

Section titled “Scenario 5 — “Memory search returns stale results””

Symptom: memory_search "<known recent fact>" doesn’t surface a fact you saved 10 minutes ago.

Likely broken arrow: Memory Pipeline → Voyage-4 embed failed, OR vec0 + FTS5 index is out of date because pending_saves is backed up.

Diagnosis:

  1. python3 ~/.openclaw/tools/claude-memory.py stats — chunks, pending_saves, dead_letters counts.
  2. If pending_saves is growing: Voyage-4 embed failing — check Portkey logs for embed errors.
  3. If dead_letters has rows: inspect a row to find the failure cause (sqlite3 ~/.openclaw/memory/<agent>.sqlite "select error from dead_letters limit 5").

Fix: drain pending_saves manually if backlog is small; investigate Voyage-4 / Portkey if persistent.

SymptomRoot causeFirst action
First turn answers without continuitySessionStart didn’t firejq '.hooks.SessionStart' ~/.claude/settings.json
Bash unexpectedly blockedpre-bash-check.sh regex false positiveRead the blocklist; tighten regex
tool_calls row missingCode path bypassed Portkey (CHOKEPOINT-1 violation)Audit; route through :18900
Subagent result wrong but acceptedOpus → Verify skippedRe-dispatch with stronger acceptance criteria
Session summary missingStop hook crashed before writejournalctl --user for hook errors
Memory not writtenPostToolUse failed OR pending_saves backed upclaude-memory.py stats
Claude opens staleSESSION-AUDIT.md > 24h oldEdit SESSION-AUDIT to current state
LLM call 5xxPortkey down OR provider outagesystemctl --user status portkey-proxy
Terminal window
# Hooks correctly configured?
jq '.hooks' ~/.claude/settings.json
# Portkey proxy chokepoint healthy?
systemctl --user status portkey-proxy
journalctl --user -u portkey-proxy --since "5 min ago" --no-pager | tail -30
# How many tool_calls in the last hour? (CHOKEPOINT-1 flow rate)
psql "$SUPABASE_DB_URL" -c "select count(*), avg(latency_ms) from tool_calls where called_at > now() - interval '1 hour';"
# Memory ingest healthy?
python3 ~/.openclaw/tools/claude-memory.py stats
# What was the last hook event for this PPID?
ls -lt /tmp/claude-session-*.marker | head -3
tail -20 ~/.openclaw/logs/claude-code-audit.log
# Latest session summary written?
grep -c "### Session Summary" ~/.openclaw/workspace/SESSION-AUDIT.md

This map describes a read mostly subsystem — most arrows are observational. The mutating arrows that need care:

  • PreToolUse hook — modifying the blocklist regex can either lock yourself out (too strict) or open dangerous ops (too loose). Test against synthetic inputs before committing. Roll back via git checkout ~/.openclaw/tools/hooks/pre-bash-check.sh.
  • Portkey proxy — restarting portkey-proxy.service interrupts ALL in-flight LLM calls fleet-wide. Ops cost: ~5-10s of agent unresponsiveness. Roll back: revert config + restart.
  • Memory pipelineclaude-memory.py operations write to the agent’s SQLite DB; corrupted writes can poison search. Mitigation: SQLite checksums + periodic VACUUM. Backup: rsync ~/.openclaw/memory/*.sqlite to S3 nightly.

If you’re touching this map’s components and are unsure, follow the Action Gate — state intent, wait for Henry’s go.

Sibling maps (deeper-dive):

Files referenced (canonical paths):

  • /home/opsadmin/CLAUDE.md — governance rules
  • /home/opsadmin/.claude/settings.json — hook registration
  • /home/opsadmin/.openclaw/tools/hooks/{on-session-start,pre-bash-check,on-post-tool,on-stop}.sh
  • /home/opsadmin/.openclaw/portkey-proxy/proxy.js + tier-config.json
  • /home/opsadmin/.openclaw/tools/claude-memory.py
  • /home/opsadmin/.openclaw/openclaw.json — gateway config

Source of truth: Live probe 2026-05-06; verify against journalctl --user -u portkey-proxy for divergence.

Last verified: 2026-05-06 (Phase 4.5 quality pass).