---
title: "AEP-0007: Message and Stream Sub-Profile"
sidebarTitle: "AEP-0007: Message & stream"
description: "Specifies the conversational surface AEP 0.1 deliberately left out: the `message` category's reserved types (`message.user.submitted`, `message.agent.replied`) as durable full-content facts, and a new `stream` category (`stream.started` / `stream.delta` / `stream.finished`) as the live incremental v"
---

{/* GENERATED by docs/render-site.js from spec/AEP-0007-message-stream-subprofile.md; do not edit. Edit the source and re-run `node docs/render-site.js` */}

<Note>

**Draft**: the AEP suite is pre-`v0.1`; each document's status lives in its header table. This page is rendered from the canonical [`spec/AEP-0007-message-stream-subprofile.md`](https://github.com/agenteventprotocol/agent-event-protocol/blob/main/spec/AEP-0007-message-stream-subprofile.md), which is the normative text.

</Note>

| Field | Value |
|---|---|
| **AEP** | 0007 |
| **Title** | Message and Stream Sub-Profile |
| **Type** | Standards Track — Core |
| **Status** | Draft |
| **Sponsor** | AEP maintainers |
| **Created** | 2026-07-10 |
| **Requires** | AEP-0001 (envelope, capture), AEP-0002 (taxonomy, registry), AEP-0003 §8.4 (backpressure) + §9 (redaction pipeline) |
| **Supersedes / Superseded-by** | n/a (fills the AEP-0002 §4.3 reservation) |

## Abstract

Specifies the conversational surface AEP 0.1 deliberately left out: the
`message` category's reserved types (`message.user.submitted`,
`message.agent.replied`) as **durable full-content facts**, and a new
`stream` category (`stream.started` / `stream.delta` / `stream.finished`) as
the **live incremental view** of text being produced: model output,
reasoning summaries, streamed tool arguments. Both are optional core types
forming one sub-profile: emitters opt in, consumers already tolerate them
(unknown-type tolerance), and nothing becomes mandatory.

Content is capture-gated `redacted`+ with structural fallbacks at `metadata`,
deltas default to severity `debug` so existing backpressure rules shed them
first, and the redaction pipeline is required to hold across delta boundaries.
An annex maps AG-UI's event vocabulary onto the sub-profile.

## Motivation

AEP 0.1 scoped conversational content out of the core on purpose
(AEP-0001 §3.2: token-stream transport is sub-profile territory; `message.*`
reserved in AEP-0002 §4.3). The costs of leaving it unspecified are now
concrete:

- A fleet console can show that a session ran, which tools it touched, and
  what needs attention, but not *what the agent said or was asked*, which is
  the first thing an operator wants when a session looks wrong. History
  stores face the same hole: a session's replay reconstructs everything
  except the conversation.
- Every source vocabulary AEP maps carries this surface natively: AG-UI is
  *mostly* message/stream events (text-message and tool-call streaming
  triads, reasoning streams), and agent transcripts (Claude Code, Codex)
  are message sequences. Adapters today must drop that content or smuggle it
  through vendor types with no common shape.
- Consumers MUST already tolerate the reserved types as unknown
  (AEP-0002 §4.3): the wire is prepared; only the shape is missing.

The fleet-observer test (AEP-0001 §3.2) shapes the split this AEP makes:
*that* a message happened, from whom, of what size. A fleet observer acts on
that (activity, audit, history), so `message.*` facts belong in the
registered core as optional types.

Individual token deltas are meaningful only to a rendering surface (they fail
the test for core-mandatory status), which is exactly why `stream.*` is
specified as an opt-in sub-profile with drop-first backpressure semantics
rather than as a consumer obligation.

## Specification

### 1. Sub-profile, not new obligations

The types below are **optional core types** (AEP-0002 §4.2 status). No
consumer obligation is added: a consumer that ignores them stays conformant;
a consumer that renders them follows the rules here. Emitters declare the
categories they emit through the existing `hello` capability hints
(AEP-0003 §4); no new negotiation surface is introduced.

### 2. The `stream` category (addition to AEP-0002 §3)

The closed category set of AEP-0002 §3 gains one row (a category addition is
exactly a Standards-Track AEP's power, per §3's own rule):

| Category | Scope | Meaning | 0.x members |
|---|---|---|---|
| `stream` | session | live incremental content production | `stream.started`, `stream.delta`, `stream.finished` |

### 3. Correlation: `subject` carries the stream/message identity

Exactly as the attention lifecycle uses `subject` for its request id
(AEP-0002 §7.1):

1. Every `stream.*` Event MUST set `subject` to the **stream id**: an
   emitter-unique identifier for one production of content.
2. Every `message.*` Event MUST set `subject` to the **message id**.
3. A `stream.finished` Event SHOULD set `cause` to the `id` of its
   `stream.started` (the §2.1 pairing convention of AEP-0002);
   `stream.delta` Events MAY omit `cause` (`subject` correlates them).
4. When a stream produces a message (§5), the `message.*` Event SHOULD set
   `cause` to the `id` of the `stream.finished` Event.

### 4. Types and payload schemas

Conventions as AEP-0002 §5 (all fields OPTIONAL unless REQ; capture levels in
brackets; machine-readable schemas land in `schemas/types/` at acceptance).

**`message.user.submitted`**: a user-authored message entered the session.
**`message.agent.replied`**: an agent-authored message completed.

Payload (both): `message_id: string REQ [metadata]` (mirrors `subject`),
`role?: string [metadata]` (defaults `user` / `assistant` per type; carries a
source's finer roles, e.g. `developer`, `system`), `text?: string [redacted]`
(the full content, through the AEP-0003 §9.4 pipeline at `redacted`),
`text_digest?: string [metadata]` (AEP-0003 §9.3 digest of the original
content), `chars?: int [metadata]`, `truncated?: boolean [metadata]`,
`usage? [metadata]` (AEP-0002 §5.2), `"gen_ai.request.model"?: string [metadata]`.

**`stream.started`**: content production began. Payload:
`kind: enum(message|reasoning|tool_args|output) REQ [metadata]`,
`role?: string [metadata]`.

**`stream.delta`**: an increment of content. Payload:
`text?: string [redacted]`, `chars?: int [metadata]`. At capture `redacted`+
`text` is REQUIRED; at lower ceilings see §6.

**`stream.finished`**: production ended. Payload:
`reason?: enum(completed|aborted|error) [metadata]` (default `completed`),
`chars?: int [metadata]` (total).

Severity defaults (normative): `stream.delta` Events SHOULD carry
`severity: debug`; `stream.started`/`stream.finished` and `message.*` default
`info` as usual. This is load-bearing: the existing backpressure rules
(AEP-0003 §8.4, AEP-0001 §8.1) then shed deltas first toward slow consumers
and never silently drop the durable facts.

### 5. The durable-fact / live-view split (normative)

1. Deltas are a **live view**: relays and endpoints MAY drop `stream.delta`
   Events under backpressure (they are `debug`) and bounded replay buffers
   MAY evict them first. No correctness property may depend on a consumer
   having seen every delta.
2. The **durable fact** is the `message.*` Event: an emitter that streams a
   message SHOULD emit the assembled `message.*` Event at completion (with
   `cause` per §3.4). A consumer that missed every delta still reconstructs
   the conversation from `message.*` alone.
3. Ordering: `stream.*` Events are ordinary session-scoped Events:
   `(epoch, seq)` orders them (AEP-0001 §7); deltas for one `subject`
   concatenate in `seq` order. Emitters MAY coalesce consecutive deltas of
   one stream into fewer, larger deltas.
4. Streams of `kind: "tool_args"` complement, never replace, the core
   `tool.*` lifecycle: the assembled arguments surface through
   `tool.requested` (AEP-0002 §5.3) exactly as without streaming.

### 6. Capture gating (normative)

`text` fields are user- or model-authored free text: `redacted` minimum,
never present at `metadata`. There is **no `[metadata*]` synthesis
exception** in this sub-profile (unlike `attention.requested.prompt`, nothing
here must remain human-readable for the protocol to function). At ceilings
below `redacted`:

1. `message.*` Events MAY still be emitted with structural payload only
   (`message_id`, `role`, `chars`, `text_digest`, `usage`): the fleet
   observer keeps activity, size, and correlation without content.
2. Emitters SHOULD suppress `stream.delta` entirely (emit only
   `started`/`finished`); a delta carrying only `chars` is permitted but
   rarely worth its cost.
3. Down-leveling hops (AEP-0001 §8.2) drop `text` per the annotations,
   mechanically.

**Redaction across delta boundaries.** Applying the AEP-0003 §9.4 pipeline to
each fragment independently does not satisfy §9: a secret split across two
deltas would evade every pattern.

Emitters MUST apply the pipeline over a window spanning delta boundaries (e.g.
by retaining a bounded tail of not-yet-emitted text until no §9.4 pattern can
match across the boundary) and MUST re-run the pipeline over the assembled
content before emitting the `message.*` fact. The conformance fixtures (below)
pin the split-secret case.

### 7. Bridges (AEP-0005 interaction)

The mappings apply unchanged (`message.*`/`stream.*` are ordinary Events).
Operational guidance is normative only at one point: the OTLP exporter's
capture enforcement (AEP-0005 §3.3) applies to `text` like any gated field.
Exporters SHOULD default to excluding `stream.delta` from export filters
(volume; the durable facts export instead). Operators opt in deliberately.

### 8. Registry changes (land at acceptance, SEP rule)

`schemas/registry/categories.json` gains `stream`; `types.json` moves
`message.user.submitted` and `message.agent.replied` from `reserved` to
`optional` and adds the three `stream.*` rows as `optional`; payload schemas
under `schemas/types/`; codegen regenerates.

## Conformance fixtures

Required before Accepted (GOVERNANCE §2); this Draft specifies them:

- **Golden**: a streamed-message sequence (`stream.started` →
  3 x `stream.delta` → `stream.finished` → `message.agent.replied`) with
  `subject`/`cause` links per §3, plus a `message.user.submitted`; validated
  by both runners.
- **Invalid**: `stream.delta` without `subject`; `stream.started` without
  `kind`; `stream.delta` carrying `text` on an Event whose `capture` is
  `metadata` (gating violation); a `message.*` Event whose `message_id`
  differs from `subject`.
- **Capture-gating**: `message.agent.replied` at ceiling `metadata` keeps
  `message_id`/`role`/`chars`/`text_digest`, drops `text`; down-level from
  `redacted` to `metadata` does the same mechanically.
- **Redaction**: a secret split across two `stream.delta` fragments: the
  emitted deltas MUST NOT reveal it (the §6 window rule); the assembled
  `message.*` text carries the `[REDACTED:secret]` replacement.
- **Mappings**: `conformance/fixtures/mappings/agui.json`: the first
  Annex rows pinned machine-usably (text-message triad → `stream.*` +
  `message.agent.replied`).

## Prototype

Required before Accepted; the intended shape: an **AG-UI inbound adapter**
in the reference stack (AG-UI SSE stream → AEP Events per the Annex) driving
the relay, with the flagship consumer rendering a live message tail and the
assembled conversation from `message.*` facts, proving both halves of the
§5 split end to end, plus the §6 split-secret redaction case exercised in an
adapter smoke.

Claude Code's `MessageDisplay` hook is a second candidate inbound source for
the same prototype (vendor-native assistant text with no AG-UI dependency; see
the [vendor-surface survey](/vendor-surfaces)).

## Rationale

- **Two surfaces, one profile.** The durable-fact/live-view split resolves
  the tension that kept this out of 0.1: deltas are high-volume and
  rendering-local (they fail the fleet-observer test), while message facts
  are exactly what fleet observers and history stores need.

  Specifying only facts would lose live tails; only deltas would make history
  reconstruction depend on lossy streams. The split gives each consumer class
  the half it can afford.
- **`severity: debug` on deltas** reuses machinery instead of inventing it:
  AEP-0003 §8.4 already lets endpoints shed `debug` first and forbids
  silently dropping `notice`+. The flood-control story for token streams
  falls out of two existing rules and one default.
- **`subject` as the stream id** mirrors the attention lifecycle
  (AEP-0002 §7.1), the established AEP pattern for multi-event
  correlation, rather than adding payload-level join keys consumers must
  learn separately.
- **A `stream` category rather than `message.delta`**: AG-UI streams
  reasoning summaries and tool arguments with the same triad shape it uses
  for messages; one generic category with a `kind` field maps all three
  without inventing parallel families. `output` covers process/CLI output
  streams the same way.
- **Flat `text`, no parts array**: multimodal content is deferred (Open
  questions): MCP-style content parts would triple the schema surface for
  a need no mapped source vocabulary forces today; AG-UI deltas are text.
- **AG-UI as the anchor vocabulary** (Annex): it is the protocol whose
  entire purpose overlaps this sub-profile, so mapping it fully is the
  strongest completeness check available; its deprecated `THINKING_*` →
  `REASONING_*` rename also previews the deprecation mechanics
  GOVERNANCE §5.3 defines.

## Backward compatibility

Additive throughout: a new category (via the AEP-0002 §3 sanctioned path),
five optional types (two of them already-reserved names, spellings
unchanged), no new consumer obligations, no envelope change. Existing
consumers see unknown types they already tolerate. Pre-`v0.1`-tag acceptance
folds into the 0.1 Draft in place (the AEP-0006 precedent; RELEASING.md §1:
no compatibility obligation exists before the first tag); post-tag it
ships in the next `0.x` per GOVERNANCE §5.4.

## Security considerations

This sub-profile is the highest-volume content-bearing surface in AEP.
Conversation text and reasoning summaries are exactly the data the capture
model exists to protect:

- **`redacted` floor, no synthesis exception** (§6): message and stream text
  never appears at `metadata`, and the structural fallback keeps observers
  functional without content. The threat the exception-free rule removes is
  content smuggled into ceilings operators believed were content-free
  (AEP-0002 §8).
- **Split-secret evasion** (§6): per-fragment redaction is structurally
  unsound for streams; the cross-boundary window rule plus the assembled-fact
  re-run make the §9.4 pipeline hold. Post-emission failures remain security
  incidents (AEP-0003 §9.4).
- **Reasoning exposure**: only *visible* reasoning (summaries a source
  chooses to surface) maps here. Opaque or encrypted reasoning carriers,
  designed precisely not to be read, are out of scope and MUST NOT be
  lifted into `text` (an adapter MAY pass them as vendor extension data,
  where the AEP-0001 §12 untrusted-input posture applies).
- **Volume as a vector**: delta floods are bounded by the `debug` default +
  backpressure shedding (§4 and §5); replay buffers evict deltas first, so a
  hostile or runaway stream cannot displace durable facts from bounded
  buffers.
- **Bridge egress** (§7): exporters default to facts-only; a deliberate
  opt-in is required before token-level content leaves AEP's capture model
  (AEP-0005 §6).

## Open questions

| # | Question | Owner | Resolve by |
|---|---|---|---|
| 1 | Multimodal content parts (images, structured data in messages): parts array vs. sibling types | maintainers | before Accepted if a mapped source forces it; else next revision |
| 2 | AG-UI `MESSAGES_SNAPSHOT` (bulk conversation state): map to a `message.*` replay, a `state.snapshot` projection, or leave unmapped | maintainers | with the prototype (the adapter must do *something* with it) |
| 3 | Delta coalescing bounds: should a maximum delta rate/size be RECOMMENDED numerically | maintainers | with prototype measurements |
| 4 | Encrypted-reasoning carry-over (opaque continuity blobs): permanently out of scope, or a dedicated opaque envelope later | maintainers | next revision |
| 5 | Should `message.*` ever join the mandatory set (a major-version question by construction, AEP-0002 §6) | maintainers | not before 1.0 |

## Annex (normative when Accepted): AG-UI mapping

*Verification status (informative): specified from the published AG-UI
protocol documentation (docs.ag-ui.com, "Events", fetched 2026-07-10,
including the reasoning family and chunk transformers current at that date);
no reference adapter exists yet. Rows marked ⊙ are provisional pending the
prototype.*

Lifecycle and state rows restate AEP-0002 Annex A where the core already
covers AG-UI; they are listed so this table is a complete adapter guide.

| AG-UI event | AEP mapping | Notes |
|---|---|---|
| `RUN_STARTED` / `RUN_FINISHED` / `RUN_ERROR` | `run.started` / `run.finished` / `run.failed` | core (AEP-0002 Annex A) |
| `STEP_STARTED` / `STEP_FINISHED` | `run.step.started` / `run.step.finished` + envelope `step` | core |
| `TEXT_MESSAGE_START` | `stream.started` `{kind:"message", role}`; `subject` = `messageId` | |
| `TEXT_MESSAGE_CONTENT` | `stream.delta` `{text: delta}` | `severity: debug` |
| `TEXT_MESSAGE_END` | `stream.finished` then the assembled fact: `role:"assistant"` → `message.agent.replied`, `role:"user"` → `message.user.submitted`; other roles → the fact-type matching the author with `role` carried in payload | §5.2 |
| `TEXT_MESSAGE_CHUNK` | expand per AG-UI's own chunk-transformer rules, then map as the triad | |
| `TOOL_CALL_START` | `stream.started` `{kind:"tool_args"}`; `subject` = `toolCallId` | |
| `TOOL_CALL_ARGS` | `stream.delta` | |
| `TOOL_CALL_END` | `stream.finished`, then core `tool.requested` with assembled args per its schema | §5.4 |
| `TOOL_CALL_RESULT` | `tool.completed` | core |
| `TOOL_CALL_CHUNK` | expand, then as the tool-call triad | |
| `STATE_SNAPSHOT` / `STATE_DELTA` | `state.snapshot` / `state.delta` | core optional |
| `MESSAGES_SNAPSHOT` | ⊙ unresolved (Open question 2) | |
| `ACTIVITY_SNAPSHOT` / `ACTIVITY_DELTA` | `progress.status` | core optional |
| `REASONING_MESSAGE_START` / `_CONTENT` / `_END` | `stream.started` `{kind:"reasoning"}` / `stream.delta` / `stream.finished`; `subject` = reasoning `messageId` | visible reasoning only |
| `REASONING_START` / `REASONING_END` | no separate Event: framing around the message triad above | ⊙ confirm with prototype |
| `REASONING_MESSAGE_CHUNK` | expand, then as the reasoning triad | |
| `REASONING_ENCRYPTED_VALUE` | not mapped (opaque continuity carrier; see Security considerations) | |
| `RAW` | not mapped (transport passthrough); an adapter MAY use `x.agui.raw` | |
| `CUSTOM` | `x.{vendor}.*` per AEP-0001 §6 | |
| `THINKING_*` (deprecated) | as their documented `REASONING_*` replacements | |

## References

- AEP-0001 §3.2/§7/§8, AEP-0002 §3 to §6, AEP-0003 §8.4/§9, AEP-0005 §3.3.
- AG-UI protocol documentation (docs.ag-ui.com, Events; fetched 2026-07-10).
- MCP sampling message shapes (role/content parity check).
- GOVERNANCE §5 (stability and deprecation mechanics).
