Skip to main content
Streaming responses are emitted as artifact-update events under the artifact id aion:stream-delta. Two things identify a delta, and a client needs both. The artifact id says which channel the update belongs to — live reply text rather than a file the agent produced. The schema marker on the artifact update’s own metadata says what shape the payload has. They answer different questions and live in different places: the id on the artifact, the marker on the event.

Core Fields

Typical Sequence

  1. First chunk with append: false.
  2. Intermediate chunks with append: true.
  3. A durable message (TaskStatusUpdateEvent(working, message=...) carrying the full text) or the terminal task status. Either one ends the sequence; clients finalize the open section on it. Every chunk carries content.

Sections

append decides which section a delta belongs to, and neither value ever replaces text on its own.
  • true — continue the section currently open for this task and artifact id.
  • false, or absent — open a new section, including a new section under the same artifact id. The section already displayed is kept and finalized, and a visual break is inserted before the new one. This is what lets several response sections appear in order within one task.
An artifact has to be opened before it can be appended to. append: true for an artifact id that was never sent is an agent protocol violation: the A2A task manager rejects it and the task fails with InvalidAgentResponseError. Both built-in adapters open with append: false, and artifacts emitted directly from graph code — emit_artifact, for instance — default to append=False too. append: false on its own always opens a new section. It never replaces one, and a new streaming sequence inside the same task is exactly that case: it starts a fresh section beside the previous one. An artifact update replaces the active section only when it carries append: false and an explicit full-value reconstruction signal the client recognizes. Aion Chat recognizes three, any one of which is enough:
  • lastChunk: true;
  • artifact.metadata.status of finalized;
  • artifact.metadata.status_reason of complete_message or complete_task.
Without one of those, the update opens a section like any other. An interruption and resume, or the wish to supersede partial output with corrected content, are reasons a producer might send a reconstruction — they do not change what append means, and they replace nothing by themselves. Replacing the active response section with the durable agent message that follows a stream is a separate path, not an artifact reconstruction: see Client Guidance.

Ending a sequence

Finalize on the durable message (TaskStatusUpdateEvent(working, message=...) carrying the full text) or on the terminal task status. Every chunk carries content, so an empty delta never arrives to mark the end. Do not treat lastChunk as the completion signal. It is not one, and what it does say differs by framework:
  • Google ADK streams a run of partial responses and then a separate, non-partial response carrying the aggregated content of the whole turn. At the moment a partial arrives there is nothing that could say it is the last one — that information comes with the next event — so every ADK delta carries lastChunk: false.
  • LangGraph sets lastChunk: true on a content-bearing chunk whose chunk_position is last. It is therefore present sometimes and absent others: langchain-core ends a stream with an empty final chunk, and an empty chunk produces no artifact update at all, so the flag simply never arrives on that path.
A client that waits for lastChunk will hang on one framework and on most sequences of the other. A client that finalizes on the durable message or the terminal status is correct on both.

Example Chunk

Both framework adapters emit this exact shape, so a client written against one reads the other.

Reconstruction Rule

A finalized artifact may later be reconstructed within the same task after an interruption or resume flow. Clients should treat the latest finalized artifact as source of truth.

Client Guidance

Render live reply text by special-casing artifact.artifactId === "aion:stream-delta". When a durable agent message arrives after streaming, a client may replace the open aion:stream-delta response section for that task with it. If no response section is open, render the durable message as its own section. Treat artifacts with other ids as independent payloads, such as files, structured data, reactions, ephemeral messages, cards, or extension-specific content. Do not merge them into the streamed reply text unless the client explicitly supports that artifact type. Both framework adapters emit the shape above; each one’s event reference states it in its own terms, under the framework you are writing for.