Documentation index for AI agents: see /llms.txt. Markdown versions of every page are available at <path>.md or via Accept: text/markdown.
Guides

Streaming Events

runtime.query(prompt, thread_id=...) and runtime.run_specialist(specialist, prompt, thread_id=...) both return an async iterator over wire-shape event dicts. Each event is a...

runtime.query(prompt, thread_id=...) and runtime.run_specialist(specialist, prompt, thread_id=...) both return an async iterator over wire-shape event dicts. Each event is a plain Python dict with a type field discriminator and event-specific payload fields.

The events follow the AI SDK Data Stream Protocol. The exact shapes are defined in the Rust agent-fw-core crate and serialised in camelCase.

Consumer loop

A typical consumer dispatches on event["type"]:

async for event in runtime.query("Draft a tiny pricing scenario.", thread_id="thread-1"):
    kind = event["type"]
    if kind == "text":
        print(event["text"], end="")
    elif kind == "tool-invocation":
        if event["state"] == "call":
            print(f"\n[call {event['toolName']}({event['args']})]")
        else:
            print(f"\n[result {event['toolName']} -> {event['result']}]")
    elif kind == "approval-required":
        await runtime.respond_to_approval(event["data"]["id"], "approve")
    elif kind == "finish":
        print(f"\n[done; usage={event['usage']}]")

Event kinds

The kinds below are the ones a harness consumer is likely to see. Field names are camelCase on the wire. Optional fields are absent when not applicable.

text

Incremental text token.

{"type": "text", "text": "Hello"}

reasoning

Chain-of-thought reasoning, emitted only by models that surface it.

{"type": "reasoning", "text": "..."}

step-start

Boundary marker preceding text / reasoning / tool calls for one logical step. Useful for re-grouping output by step.

{"type": "step-start"}

tool-invocation

Emitted twice per tool call: once with state == "call" and once with state == "result". For approval-gated tools the approval-required and approval-decision events sit between the two.

# state=call
{
    "type": "tool-invocation",
    "toolInvocationId": "scripted-tool-1",
    "toolName": "echo",
    "args": {"value": "hello"},
    "state": "call",
}

# state=result
{
    "type": "tool-invocation",
    "toolInvocationId": "scripted-tool-1",
    "toolName": "echo",
    "args": {"value": "hello"},
    "state": "result",
    "result": {"echo": "hello"},
}

tool-progress

Progress milestone for a long-running tool. Includes a monotonic phaseIndex, the totalPhases, an optional milestone payload, and the correlating toolName (plus optional toolCallId).

{
    "type": "tool-progress",
    "toolName": "buildPlan",
    "label": "Resolving products",
    "phaseIndex": 1,
    "totalPhases": 4,
    "milestone": {"matched": 142},
}

tool-agent

Sub-agent invocation event, emitted in call / result pairs analogous to tool-invocation. Carries agentName and the prompt / result payload.

{
    "type": "tool-agent",
    "agentName": "planner",
    "state": "call",
    # ...
}

data-tool-agent

Sub-agent completion with usage metrics for that agent's slice of the call. The payload carries agentName, the model it ran on, and a usage object.

{
    "type": "data-tool-agent",
    "data": {"agentName": "planner", "model": "claude-haiku-4-5", "usage": {...}},
}

approval-required

The runtime is waiting for a host decision. See Approvals for the full payload shape and how to respond.

{
    "type": "approval-required",
    "data": {
        "id": "apr-1234",
        "kind": "plan",
        "target": "demo-plan-1",
        "payload": {...},
        "resourceId": "acme",
        "threadId": "thread-1",
    }
}

approval-decision

Emitted immediately after runtime.respond_to_approval(...) is processed.

{
    "type": "approval-decision",
    "data": {
        "id": "apr-1234",
        "outcome": {"outcome": "approve"},
        "feedback": "approved by smoke test"
    }
}

outcome matches the Rust ApprovalOutcome shape: {"outcome": "approve"}, {"outcome": "reject"}, or {"outcome": "revise", "partial": {...}}.

plan-status-change

Plan lifecycle transition. from and to are canonical status strings ("draft", "approved", "executing", "executed", "failed") or the "pending_approval" display alias.

{
    "type": "plan-status-change",
    "data": {
        "planId": "demo-plan-1",
        "from": "draft",
        "to": "pending_approval",
    }
}

data-file-registered

A file produced by a tool becomes available for download.

{"type": "data-file-registered", "data": {...}}

data-cost-summary and data-latency-summary

Aggregated cost and latency metrics for the stream. Both arrive after finish.

{"type": "data-cost-summary", "data": {...}}
{"type": "data-latency-summary", "data": {...}}

finish

Terminal event for the stream. Carries cumulative usage and a finishReason. At most one finish per stream. totalTokens is computed as promptTokens + completionTokens; the cache fields are reported separately.

{
    "type": "finish",
    "finishReason": "stop",
    "usage": {
        "promptTokens": 12,
        "completionTokens": 8,
        "cacheReadInputTokens": 0,
        "cacheCreationInputTokens": 0,
        "totalTokens": 20,
    },
}

error

Non-recoverable error. No further events follow.

{"type": "error", "error": {"message": "...", "code": "..."}}

custom

Domain-specific event from a tool or product layer. On the wire, type is the literal string "custom"; the domain identifier travels in the separate event_type field next to the arbitrary JSON data payload.

{"type": "custom", "event_type": "acme-forecast-refresh", "data": {"runId": "fr-42"}}

Note that data-flow-ui is not a custom event. The protocol defines it as its own typed event carrying a pre-computed UI payload, serialized as {"type": "data-flow-ui", "data": {"dsl": "..."}}. It only appears in a stream when a product layer emits it; the harness runtime does not produce it on its own.

Ordering guarantees

The runtime guarantees the following partial order within a stream:

  • step-start precedes any text / reasoning for that step.
  • For a tool call: tool-invocation (call) precedes tool-progress* precedes tool-invocation (result).
  • For sub-agents: tool-agent (call) precedes tool-agent (result).
  • For an approval-gated tool: tool-invocation (call) precedes approval-required precedes approval-decision precedes tool-invocation (result).
  • finish is terminal; only data-cost-summary and data-latency-summary may follow.
  • After error, no further events are emitted.

Common errors

SymptomExplanation
Text never appears before a tool callTool-first steps are valid. Dispatch by event["type"], not by assuming text arrives first.
Stream stops at approval-requiredSend runtime.respond_to_approval(...); the runtime is intentionally paused.
Usage or cost metadata is missing in the middle of a streamRead until finish; aggregate summaries arrive at the end.
UI treats unknown events as fatalPreserve or ignore unknown event types so product-specific custom events can pass through.

See also