Runtime API Flow & Preprocessing
The automatic runtimes currently implemented in the repository are the Claude Code plugin and the Codex hook adapter. This document is an operational reference explaining what preprocessing those adapters perform before calling Agent Tracer API, and how manual runtimes should follow the same surface.
Implementation basis:
- Claude plugin hooks:
packages/runtime/src/claude-code/hooks/*.ts - Codex hook adapter:
packages/runtime/src/codex/hooks/*.ts - Public API surface:
packages/server/src/{activity/session,activity/event,work/task,governance/rule}/api/*.ingest.controller.ts
Related documentation:
API Roles
| API | Core Role |
|---|---|
/ingest/v1/sessions/ensure | Runtime session upsert + task/session binding |
/ingest/v1/sessions/end | Runtime session closure |
/ingest/v1/tasks/complete, /ingest/v1/tasks/error | Direct task finalization by task id |
/ingest/v1/conversation | Store user raw prompt |
/ingest/v1/conversation (assistant.response) | Store assistant final response |
/ingest/v1/tool-activity | Record implementation action |
/ingest/v1/tool-activity | Record exploration/lookup |
/ingest/v1/tool-activity | Record shell execution |
/ingest/v1/coordination | Record delegation/skill/MCP calls |
/ingest/v1/workflow | Track todo lifecycle |
/ingest/v1/tasks/link | Link parent-child tasks |
/ingest/v1/coordination | Background task state |
/ingest/v1/events | Generic kind-tagged batch event ingest for manual/generic clients (the runtime hooks route exclusively to the six typed /ingest/v1/* endpoints via resolveIngestEndpoint()) |
/ingest/v1/lifecycle, /ingest/v1/telemetry | Typed lifecycle and token-usage ingest |
Claude Plugin Preprocessing Strategy
Input Normalization
- Every hook reads stdin via the shared
runHook()wrapper (packages/runtime/src/shared/hook-runtime/run-hook.ts). Non-JSON / non-object payloads become an empty object; the hook exits 0 without blocking Claude Code. - Per-event payload readers under
packages/runtime/src/shared/hooks/claude/payloads.tsenforce required fields (session_id, etc.) and expose typedagentId/model/permissionMode/transcriptPath/cwd. - Claude payload
hook_sourcemay still appear as"claude-hook"in legacy payloads, but the canonical server-sideruntimeSourcefor new events isclaude-plugin. - Canonical
runtimeSourcesent to server isclaude-plugin. - Strings are normalized with trim + maxLength cutoff inside the readers.
Session Prerequisite Guarantee
- Calls
/ingest/v1/sessions/ensurefirst fromSessionStart,UserPromptSubmit,PreToolUse,SubagentStart, and the status-line path. - User prompt is filtered for closure commands like
/exit, then saved to/ingest/v1/conversation.
Tool Event Classification
- Each official Claude tool has its own file under
PostToolUse/(e.g.Bash.ts,Edit.ts,Read.ts,Agent.ts,Skill.ts,TaskCreate.ts, …). Shared per-category logic lives in_file.ops.ts,_explore.ops.ts,_agent.ops.ts,_skill.ops.ts,_todo.ops.ts. - Routes each tool event to its typed endpoint via
resolveIngestEndpoint()(e.g.tool.used/terminal.command→/ingest/v1/tool-activity); the generic/ingest/v1/eventsendpoint is reserved for batch/manual clients. - MCP-format tools (
mcp__...) are handled by theMcp.tscategory handler (the only grouped file, sincemcp__*is a wildcard regex) and are converted toactivityType: "mcp_call". - Bash is semantically classified by command meaning with enriched semantic metadata.
Agent/Skillevents post to/ingest/v1/coordination(LANE.coordination), and subagent parent-child linking is done viaparentTaskId/parentSessionIdon/ingest/v1/sessions/ensure.- Parallel tool fan-outs are bounded by
PostToolBatchposting acontext.savedmarker (trigger: "tool_batch_completed"). - Tool-call auto-denials from
PermissionDeniedpost arule.loggedevent withruleOutcome: "auto_deny".
Assistant Response Boundary
Stophook reads the final assistant message and sends anassistant.responseevent to/ingest/v1/conversation.StopFailureposts anassistant.responsewithstopReason: "error:<error_type>"whenever a turn fails to complete due torate_limit,authentication_failed,billing_error,invalid_request,server_error,max_output_tokens, orunknown.- Token/context telemetry is collected by runtime-specific telemetry paths, not by the lifecycle API itself.
Stopcalls/ingest/v1/sessions/endwithcompleteTask: falseandcompletionReason: "assistant_turn_complete".SessionEndonly passescompleteTask: truefor an explicit user exit; runtime termination/resume closes the monitor session without completing the primary task.- The server will not complete a primary task while background descendants are still running; in that case it moves the primary task to
waiting.
Transport Error Surface
postJsonthrowsMonitorRequestError(status, pathname, code?, details?)when the monitor returns a non-2xx response, preserving the server envelope'serror.code/error.message/error.details. The sharedrunHook()wrapper logs the error and exits 0; Claude is never blocked.
Representative JSON Examples
UserPromptSubmit → /ingest/v1/conversation
{
"taskId": "task_01J...",
"sessionId": "sess_01J...",
"messageId": "msg_1712345678901_ab12cd",
"captureMode": "raw",
"source": "claude-plugin",
"title": "Organize documentation structure and add API flow diagram",
"body": "Organize documentation structure and add API flow diagram"
}PostToolUse(Bash) → /ingest/v1/tool-activity
{
"taskId": "task_01J...",
"sessionId": "sess_01J...",
"command": "npm test",
"title": "npm test",
"body": "npm test",
"lane": "implementation",
"metadata": {
"command": "npm test",
"subtypeKey": "run_test",
"toolFamily": "terminal",
"sourceTool": "Bash"
}
}Rule lane reclassification: Logged events are checked against active verification rules for the current open turn. Matches write
rule_enforcementsrows. When the timeline is read back, events with one or more rule enforcements are shown on the"rule"lane while the original lane is preserved in metadata. See API integration map § Verification Rules.
Stop → /ingest/v1/conversation (assistant.response)
{
"events": [
{
"kind": "assistant.response",
"lane": "user",
"taskId": "task_01J...",
"sessionId": "sess_01J...",
"messageId": "msg_1712345678999_f3e2aa",
"source": "claude-plugin",
"title": "Updated the documentation as requested.",
"body": "Updated the documentation as requested.",
"metadata": {
"stopReason": "end_turn",
"inputTokens": 1200,
"outputTokens": 430
}
}
]
}SessionEnd / Stop → /ingest/v1/sessions/end
{
"runtimeSource": "claude-plugin",
"runtimeSessionId": "claude-session-abc",
"completionReason": "assistant_turn_complete",
"completeTask": false,
"summary": "Updated the documentation as requested."
}Direct task finalization
Use these endpoints only when the caller already knows the monitor taskId and wants to finalize that task directly. Do not send runtime-session policy fields (completeTask, completionReason, backgroundCompletions) here; those are only meaningful for /ingest/v1/sessions/end.
{
"taskId": "task_01J...",
"sessionId": "sess_01J...",
"summary": "Work item completed.",
"metadata": {
"source": "manual"
}
}For task failure, call /ingest/v1/tasks/error with the same base fields plus errorMessage.
Minimum Rules Manual Runtime Must Follow
Runtimes without automatic plugins can use the same dashboard/storage by following the sequence below.
- If stable session ID available, call
/ingest/v1/sessions/ensure - For each user input, call
/ingest/v1/conversation - For each tool use, call
/ingest/v1/tool-activityor/ingest/v1/events - On response completion, send an
assistant.responseevent to/ingest/v1/conversation - At turn end, call
/ingest/v1/sessions/end; passcompleteTask: trueonly when the runtime is declaring the work item done
Optionally add /ingest/v1/workflow, /ingest/v1/coordination, /ingest/v1/lifecycle, /ingest/v1/telemetry, and /ingest/v1/tasks/link as needed.