Skip to content

Agent Runtime Pack

The Agent Runtime Pack is the pack-scoped runtime for governed agent execution. Its manifest-registered tool surface is agent-runtime:execute-turn plus six remote-callable remote-control tools (resume_workflow, pause_turn, abort_turn, elevate_autonomy, lower_autonomy, approve_inflight), alongside library helpers for long-running agent-session orchestration.

| Surface | Purpose | | ------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | | agent-runtime:execute-turn | Executes one governed model turn and returns native ordered content, exact tool calls, and terminal state | | runGovernedAgentLoop() | Host helper for a multi-turn loop that keeps real tool calls in the kernel enforcement path | | startGovernedAgentSession() / resumeGovernedAgentSession() | Persist and resume linear agent-session state | | startGovernedAgentWorkflow() / resumeGovernedAgentWorkflow() | Persist and resume branching, joins, and scheduled wakeups inside the same agent-session task model | | createHostContextMessages() / createApprovalResolutionMessage() | Build host-supplied messages without depending on another pack’s storage internals |

The pack exports provider adapters, capability augmentation, and a policy factory, but those are internal pack plumbing rather than a separate public tool surface.

The pack owns one task type:

task_types:
  - agent-session

Scheduled wakeups, resumed sessions, and workflow branches all remain agent-session executions. The pack does not introduce a second execution class for routines or workflows.

| Layer | Owns | | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | Kernel | Host-authenticated caller binding, persisted authority projection, policy/scope enforcement, physical confinement, approvals, and immutable evidence | | Agent Runtime Pack | Turn normalization, provider adapter behavior, agent-session state, branch/join readiness, and scheduled wakeups | | Host | Opaque caller authentication, strict selector binding, external context, exact tool allowlisting, loop entry/exit, and approval timing |

The pack receives a sealed AuthenticatedExecutionRuntime, not a raw ToolHost or caller-constructed execution context. Every operation uses an ExecutionSelector containing exactly task_id, run_id, and session_id; the kernel resolves the principal, workspace, lane, scopes, pinned pack configuration, and execution-policy profile from persisted trusted state.

Every agent-runtime:execute-turn response uses the same normalized output shape:

{
  "status": "tool_request",
  "assistant_message": "I can schedule that for tomorrow morning.",
  "requested_tools": [
    {
      "call_id": "call_schedule_01",
      "name": "calendar:create-event",
      "input": {
        "title": "Weekly review"
      }
    }
  ],
  "content_blocks": [
    {
      "type": "text",
      "text": "I can schedule that for tomorrow morning."
    },
    {
      "type": "tool_call",
      "call_id": "call_schedule_01",
      "name": "calendar:create-event",
      "input": {
        "title": "Weekly review"
      }
    }
  ],
  "terminal_state": "tool_request",
  "provider": {
    "kind": "messages_compatible",
    "model": "ops-assistant"
  },
  "finish_reason": "tool_use"
}

content_blocks is the canonical ordered conversation representation. It keeps text, reasoning, opaque provider state, tool calls, and tool results distinct instead of flattening them into one string. Empty content_blocks is a valid empty reply. requested_tools contains every native provider call in provider order. Only native OpenAI-compatible tool_calls or Messages-compatible tool_use blocks create executable requests.

Assistant text is always ordinary untrusted content, including valid JSON text. The adapter never parses it as status, policy authority, or requested-tool metadata. Native tool-only responses need no synthetic text envelope and remain valid with empty assistant_message. The deprecated intent_catalog input is accepted for compatibility but ignored; model-authored labels can only appear as optional untrusted_model_label telemetry and never select an execution policy profile, allowed tools, approvals, or protected metadata. Messages-compatible requests include the native API’s x-api-key, pinned anthropic-version, and positive max_tokens fields.

The kernel still governs every real tool call. The orchestration helper requires each requested tool to exist in the exact host-provided catalog before it executes any request in the batch. It appends the assistant call turn before its results, executes multiple calls deterministically, and sends each result back with the exact provider-issued call_id. A missing ID is invalid output, never an invitation to create a replacement ID.

The complete call list is validated before execution: IDs must be unique, and ordered call blocks must match every request’s ID, name, and input. Batch budget checks are atomic. Rejected batches, thrown calls, and unexecuted remainder calls still receive paired exact-ID results so provider history remains valid. Multiple approval requests are persisted together and a partial approval continuation cannot erase unresolved requests. Resolutions require a nonempty request_id, a boolean approved, and a nonempty approved_by; malformed and duplicate resolutions fail before persisted state changes.

This matches the provider pairing rules: Chat Completions history uses the exact tool_calls[].id as tool_call_id; Responses history preserves each exact call_id and emits one function_call_output item per call; Messages-compatible history uses the exact tool_use.id as tool_use_id. See the official OpenAI function-calling guide and Anthropic tool-use guide.

Every tool-related content block (tool_call, tool_result, and the opaque fallback for unrecognized provider items) declares an execution_owner:

| Owner | Meaning | | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | client | An arbitrary governed tool the host executes via the authenticated execution runtime, subject to trusted authority and approval | | kernel | A reserved agent-runtime:* remote-control tool (see Remote Controls) — a kernel-implemented lifecycle operation, never a user-registered tool | | provider | The provider itself executes and owns the call (Anthropic server_tool_use, an OpenAI Responses hosted tool item, or any unrecognized future block kind) |

requested_tools only ever contains client- and kernel-owned calls — the exact set that crossed the provider boundary into the governed execution path. provider-owned calls are never executed or emulated locally; they round-trip byte-for-byte (see below) so the provider can continue its own state.

Blocks also preserve, when the provider supplies them, our internal caller_id/container_id/ source_order fields. These are normalized names — the actual wire shape differs per provider and is read from exactly where each provider puts it, never fabricated:

  • caller_id — the parent/caller item id for a nested or programmatic tool-call graph.
    • Anthropic nests it on the tool_use block itself: "caller": {"type": "direct"} for a direct model call, or "caller": {"type": "code_execution_20260120", "tool_id": "srvtoolu_..."} when the call was issued from inside a running code_execution block — tool_id becomes caller_id. See Programmatic tool calling.
    • OpenAI nests it on the item: "caller": {"type": "program", "caller_id": "call_prog_123"}. That caller_id matches the calling program item’s call_id, not its id — so the caller-graph node identity for every OpenAI item is its call_id when present, falling back to id only for items (mostly hosted tool calls) that have no call_id. See Programmatic tool calling.
    • A caller_id must resolve to an item already seen earlier in the same turn; an unresolved, self-referencing, or out-of-order caller edge fails the turn closed with a stable PROVIDER_CALLER_GRAPH_* error rather than silently dropping or fabricating a correlation.
  • container_id — an opaque provider container/resume token (for example a code-execution container), preserved unchanged across turns.
    • Anthropic: the container is top-level on the Message response — {"content": [...], "container": {"id": "container_xyz", "expires_at": "..."}, "stop_reason": ...} — not per-block. It applies to the whole turn, so every provider-owned block in that turn is tagged with it. On the next request it is required as a top-level string field — {"container": "container_xyz", ...} — whenever a programmatic tool call is still pending; the pack resends the most recently observed container id automatically on every continuation.
    • OpenAI’s confirmed container-reuse mechanism is a tool-config field (tools: [{"type": "code_interpreter", "container": "cntr_..."}]), not a per-item response field. The pack still preserves a per-item container_id/container.id when a hosted item happens to carry one (a defensive, non-fabricating pass-through), but this is not itself a confirmed OpenAI wire contract the way the Anthropic top-level container is.
  • source_order — the 0-based ordinal fixing the item’s position in the provider’s original sequence, independent of any later filtering.

A reserved kernel tool must always be called directly — a kernel-named call arriving with a non-direct caller (nested inside a code_execution/program run) is contradictory ownership and fails closed with PROVIDER_AMBIGUOUS_TOOL_OWNERSHIP before any execution is attempted.

The preserved caller graph is bounded: depth and fan-out both have configurable safety limits (max_caller_graph_depth, max_caller_graph_fan_out, defaulting to 8 and 32). Exceeding either limit — or a graph shape that cannot be resolved unambiguously — fails the turn closed with one of PROVIDER_AMBIGUOUS_TOOL_OWNERSHIP, PROVIDER_CALLER_GRAPH_CYCLE, PROVIDER_CALLER_GRAPH_DEPTH_EXCEEDED, PROVIDER_CALLER_GRAPH_FAN_OUT_EXCEEDED, or PROVIDER_CALLER_GRAPH_UNKNOWN_CALLER before any execution is attempted.

A provider-owned tool_call/tool_result block carries the exact original provider block under metadata.provider_raw_block. When the pack resends history, a provider-owned block is emitted verbatim from that raw block instead of being rewritten into the generic client tool_use/ tool_result shape — the same idiom already used for Anthropic thinking/redacted_thinking blocks. This is what lets an Anthropic server_tool_use call and its web_search_tool_result (or any ${tool_name}_tool_result) survive a full round trip without the kernel ever executing or rewriting provider-owned state.

Anthropic can pause a long-running, provider-owned turn with stop_reason: "pause_turn" and expects the exact content resent unmodified to continue. The pack maps this to terminal_state: "paused" — never a phantom complete — and the governed loop persists the turn as suspended (not completed), appending the exact assistant content so a later resume call (resumeGovernedAgentSession / resumeGovernedAgentWorkflow) can continue it.

  • The pack never emulates a provider-hosted tool locally, and never exposes a generic proxy that lets the kernel invoke provider-owned tools on the provider’s behalf.
  • Depth/fan-out bounds and caller-edge resolution are a safety net, not a full graph query API — the pack does not expose ancestor/descendant traversal helpers.
  • Provider “container” staleness/expiry is not tracked; container_id is preserved faithfully as opaque provider state, but the pack does not validate whether a given container is still live on the provider’s side.

The provider adapter uses bounded incremental parsing for response bytes, stream bytes, events, snapshots, blocks, calls, and tool arguments. Delta snapshots do not copy the growing transcript. After a terminal marker, the adapter drains until natural EOF or one absolute configured deadline, rejects every non-empty post-terminal chunk observed before that deadline, and then cancels a reader that did not close. Cancellation is best-effort and never blocks the bounded return path. Bytes sent only after the deadline are not observable. Cancellation remains active after response headers.

The provider adapter emits one normalized adapter outcome per invocation: completed, cancelled, provider_error, malformed_stream, or client_disconnect. Partial stream chunks can produce progress snapshots, but cannot produce a second terminal event or a phantom tool call. Chat Completions streams are malformed without a finish reason before [DONE]. Responses streams must end in exactly one response.completed, response.incomplete, response.failed, response.cancelled, or error event, with provider failure details preserved as bounded diagnostics. Messages streams are incomplete unless every started content block is stopped and message_delta supplies a stop reason before message_stop. Tool turns require a successful provider terminal reason. terminal_state maps Chat Completions stop and Messages end_turn/stop_sequence to complete, length/max_tokens to incomplete, content_filter/refusal to refused, Messages pause_turn to paused (see Pause and continuation), and unrecognized future reasons to unknown. Only tool_request is runnable; paused is resumable, never a phantom completion.

Responses text and function-argument deltas must reach their matching native done substages before the output item completes. Duplicate or late deltas/done events are rejected. Incomplete calls stay opaque and non-runnable, while item, call, argument-byte, and JSON-structure limits still apply.

Messages-compatible thinking and redacted_thinking blocks are returned complete and unmodified when history is sent back for tool use. Chat Completions reasoning fragments retain their index and assembled signature. Responses requests use store: false; reasoning items preserve ordered provider identity, optional provider status, and encrypted content so complete stateless history can replay provider-owned reasoning without interpreting it as application authority. Function tools use strict: false because the runtime accepts governed schemas beyond OpenAI’s strict structured-output subset. See OpenAI’s reasoning continuation guide and Responses streaming migration guide.

  • The manifest-registered tool surface is intentionally small: one turn tool plus six remote-control tools, alongside pack library helpers (not separate manifest tools).
  • OpenAI protocol selection is explicit: openai_compatible is Chat Completions-style and openai_responses is Responses typed-item/SSE. The adapter never infers protocol from a URL.
  • The Messages-compatible and Responses adapters preserve client-, kernel-, and provider-owned tool calls and their caller graph (see Execution Ownership & Caller Graphs) but never execute or emulate provider-owned work locally, and do not track provider container staleness/expiry.
  • Tool access is controlled by the persisted execution-policy profile, kernel scope/policy projection, and an exact host catalog; the model never gets direct access to undeclared tools.
  • Workflow state is committed through the RunAggregate-backed workflow authority (a fenced record_effect transition plus a content-addressed payload sink), while the durable audit trail remains in kernel evidence files. The legacy .agent-runtime/workflow/ directory is read-only, used only for one-way migration into the RunAggregate authority.
  • New workflow writes use schema v3. The bounded v1/v2 read path migrates persisted envelope fields to ordered native blocks without interpreting assistant text as control data.