Skip to content

Live token streaming

agent.stream() streams events as the agent runs: a ToolCallingEvent when the model decides to call a tool, a FinalAnswerEvent once it answers, and — while each of those is being generated — live ContentDeltaEvents carrying its output token by token, before the terminal event they build up to. Consumers that only care about complete messages can match on FinalAnswerEvent, ToolCallingEvent, or ReasoningEvent and ignore the deltas.

Consuming deltas

from ant_ai.core import ContentDeltaEvent, FinalAnswerEvent

async for event in agent.stream(state):
    if isinstance(event, ContentDeltaEvent):
        print(event.delta, end="", flush=True)
    elif isinstance(event, FinalAnswerEvent):
        print()  # the concatenated deltas equal event.content

The ContentDeltaEvent shape

Field Meaning
delta The new text fragment only — not the accumulated text so far.
stream_id Groups every delta (and the terminal event) from one LLM generation. A fresh UUID assigned by LLMStep.stream() per generation.
target_kind "content", "reasoning", or "tool_calling" — what this fragment is building toward.
is_first True for the first delta of a stream_id (or tool_call_index) group.
tool_call_index Set when target_kind == "tool_calling", since a turn can stream several tool calls at once.

stream_id ties a run of deltas back to the terminal event they build up to. A model that reasons before answering streams a reasoning-kind group first, then a content-kind group, both sharing the same stream_id as the ReasoningEvent/FinalAnswerEvent that closes each out:

---
title: One generation's deltas, correlated by stream_id
---
sequenceDiagram
    autonumber
    participant LLM
    participant Step as LLMStep
    participant Caller

    LLM-->>Step: reasoning tokens
    Step-->>Caller: ContentDeltaEvent(target_kind="reasoning", stream_id=X, is_first=true)
    Step-->>Caller: ContentDeltaEvent(target_kind="reasoning", stream_id=X)
    Step-->>Caller: ReasoningEvent(content=full reasoning, stream_id=X)

    LLM-->>Step: answer tokens
    Step-->>Caller: ContentDeltaEvent(target_kind="content", stream_id=X, is_first=true)
    Step-->>Caller: ContentDeltaEvent(target_kind="content", stream_id=X)
    Step-->>Caller: FinalAnswerEvent(content=full answer, stream_id=X)
Hold "Alt" / "Option" to enable pan & zoom

When it isn't live

Warning

Two things force the whole-event path for a given turn:

Gate Why
A hook overrides after_model / wrap_model_call It may need the complete response to decide — tokens already sent can't be retracted.
Structured output's coercion repair call May silently rewrite the raw text into valid JSON.

A hook whose after_model/wrap_model_call override never changes the outcome (e.g. read-only logging) can opt back in by declaring stream_safe = True:

from ant_ai.hooks import AgentHook, PostModelPass


class LoggingHook(AgentHook):
    stream_safe = True  # never returns anything but PostModelPass

    async def after_model(self, result, ctx):
        logger.info(result.output.raw)
        return PostModelPass(result=result)

Over A2A

An Agent's own stream() always emits deltas internally, regardless of how it's served. Whether an A2A server forwards them to the caller is a separate, per-agent setting: stream_artifacts on colony.agent() (default True).

colony.agent(
    "assistant",
    agent=agent,
    workflow=workflow,
    card=card,
    stream_artifacts=True,  # default; set False to stop forwarding deltas as A2A artifact events
)

Artifact IDs

Deltas are translated into A2A's own TaskArtifactUpdateEvent chunks, part of the A2A spec. Reasoning, content, and each tool call stream under their own artifact ID:

Delta kind Artifact ID
target_kind="content" {stream_id}:content
target_kind="reasoning" {stream_id}:reasoning
target_kind="tool_calling" {stream_id}:tool:{tool_call_index}

The terminal whole-message status update is always sent too, so a peer that doesn't read artifacts sees no difference.

Hive-internal agents

An agent called mainly by other agents, via A2AAgentTool in a Colony, gains nothing from stream_artifacts=True: the caller consumes the result as one string once the call completes and discards every delta. Set it False for agents that are purely hive-internal collaborators.