> ## Documentation Index
> Fetch the complete documentation index at: https://java.agentscope.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Message & Event

> The core data abstractions for agent communication and streaming

Message and event are the two fundamental data structures in AgentScope.

* **Message** — the primitive of agent-to-agent communication and persistence. Each `Msg` is a complete conversation turn, stored in the context and passed between agents.
* **Event** — the primitive of frontend interaction and streaming. Events carry incremental progress updates (text tokens, tool-call fragments, permission requests, …) and drive real-time UIs and human-in-the-loop flows.

The event sequence emitted by a single `call` always condenses into exactly one assistant `Msg`, ensuring the full message state can be reconstructed from the event stream alone.

## Message

`Msg` (`io.agentscope.core.message`) represents one turn of conversation — a user input, an agent reply, or a system instruction — with content modelled as an ordered list of typed `ContentBlock`s.

<Tip>
  A single assistant `Msg` corresponds to one full `call` cycle (multiple reasoning + acting iterations until the final reply).
</Tip>

### Structure

The core fields on `Msg` (via getters):

| Method | Type | Description |
| - | - | - |
| `getId()` | `String` | Unique message identifier |
| `getName()` | `String` | Sender name (nullable) |
| `getRole()` | `MsgRole` | `USER` / `ASSISTANT` / `SYSTEM` / `TOOL` |
| `getContent()` | `List<ContentBlock>` | Ordered list of content blocks (immutable) |
| `getMetadata()` | `Map<String, Object>` | Arbitrary key/value metadata |
| `getTimestamp()` | `String` | Creation time (`yyyy-MM-dd HH:mm:ss.SSS`) |
| `getUsage()` | `ChatUsage` | Token usage (assistant messages only) |
| `getGenerateReason()` | `GenerateReason` | Termination reason: `MODEL_STOP` / `TOOL_SUSPENDED` / `REASONING_STOP_REQUESTED` / `ACTING_STOP_REQUESTED` / `ALL_TOOLS_DENIED` / `INTERRUPTED` / `MAX_ITERATIONS` |

### Content blocks

Message content is composed of typed blocks, each representing one type of information. Block classes live in `io.agentscope.core.message`:

| Block | Description | Allowed in |
| - | - | - |
| `TextBlock` | Plain text content | USER, ASSISTANT, SYSTEM |
| `DataBlock` | Binary data (image / audio / video) via base64 or URL — unifies the legacy ImageBlock / AudioBlock / VideoBlock | USER, ASSISTANT |
| `ImageBlock` / `AudioBlock` / `VideoBlock` | Legacy concrete media blocks (still supported; new code should prefer `DataBlock`) | USER |
| `ThinkingBlock` | Model reasoning / chain of thought | ASSISTANT |
| `ToolUseBlock` | A tool call: `id` / `name` / `input` / `state` (`ToolCallState`) | ASSISTANT |
| `ToolResultBlock` | A tool result with `state` (`ToolResultState`) | ASSISTANT |
| `HintBlock` | Instructions injected into the loop as user context | ASSISTANT |

<Note>
  Role constraints are enforced at construction: `USER` only allows text/data/image/audio/video blocks; `SYSTEM` only allows `TextBlock`; `ASSISTANT` allows all block types.
</Note>

### Creating a message

The role-pinned subclasses (`io.agentscope.core.message.UserMessage` / `AssistantMessage` / `SystemMessage` / `ToolResultMessage`) provide convenient constructors. When `content` is a plain string, it is wrapped in a `TextBlock` automatically.

```java theme={null}
import io.agentscope.core.message.AssistantMessage;
import io.agentscope.core.message.Base64Source;
import io.agentscope.core.message.DataBlock;
import io.agentscope.core.message.SystemMessage;
import io.agentscope.core.message.TextBlock;
import io.agentscope.core.message.UserMessage;

// User message — text only
UserMessage userText = new UserMessage("user", "What's in this image?");

// Multi-modal user message
UserMessage userMulti =
        new UserMessage(
                "user",
                TextBlock.builder().text("Describe this image:").build(),
                DataBlock.builder()
                        .source(Base64Source.builder()
                                .data("...")
                                .mediaType("image/png")
                                .build())
                        .build());

// System message — text only
SystemMessage systemMsg = new SystemMessage("system", "You are a helpful assistant.");

// Assistant message — all block types allowed
AssistantMessage assistantMsg = new AssistantMessage("agent", "Here's the result...");
```

For more optional fields (`metadata`, `timestamp`, `usage`, `generateReason`), use each subclass's `builder()`:

```java theme={null}
UserMessage msg =
        UserMessage.builder()
                .name("user")
                .textContent("Hello")
                .build();
```

### Accessing content

`Msg` provides helpers for extracting specific block types:

| Method | Returns |
| - | - |
| `getTextContent()` | All `TextBlock`s concatenated by `\n`; empty string when there are none |
| `getContentBlocks(Class<T>)` | List filtered by type |
| `getFirstContentBlock(Class<T>)` | The first matching block, or null |
| `hasContentBlocks(Class<T>)` | `true` if a block of the given type exists |

```java theme={null}
import io.agentscope.core.message.ToolUseBlock;
import io.agentscope.core.message.ToolResultBlock;

// All text content
String text = msg.getTextContent();

// All tool calls
List<ToolUseBlock> toolCalls = msg.getContentBlocks(ToolUseBlock.class);

// Whether there are tool results
if (msg.hasContentBlocks(ToolResultBlock.class)) {
    // ...
}
```

## Event

Events are the streaming counterpart of messages. While the agent runs, it emits a sequence of `AgentEvent`s (`io.agentscope.core.event`) representing incremental progress — text tokens arriving, tool calls being assembled, results streaming back. Each event is lightweight and self-contained.

### Event lifecycle

Every event carries `getReplyId()`, tying it to the message being assembled. Within a reply, `getBlockId()` or `getToolCallId()` acts as a correlation key for events that belong to the same content-block lifecycle. Events follow a **start → delta → end** pattern:

```mermaid theme={null}
sequenceDiagram
    participant Client
    participant Agent

    Agent->>Client: AgentStartEvent

    rect rgba(100, 150, 255, 0.1)
        Note over Client,Agent: Reasoning phase
        Agent->>Client: ModelCallStartEvent
        rect rgba(200, 200, 100, 0.1)
            Note over Client,Agent: TextBlock (blockId)
            Agent->>Client: TextBlockStartEvent
            Agent->>Client: TextBlockDeltaEvent (×N)
            Agent->>Client: TextBlockEndEvent
        end
        rect rgba(200, 200, 100, 0.1)
            Note over Client,Agent: DataBlock (blockId)
            Agent->>Client: DataBlockStartEvent
            Agent->>Client: DataBlockDeltaEvent (×N)
            Agent->>Client: DataBlockEndEvent
        end
        rect rgba(200, 200, 100, 0.1)
            Note over Client,Agent: ToolUseBlock (toolCallId)
            Agent->>Client: ToolCallStartEvent
            Agent->>Client: ToolCallDeltaEvent (×N)
            Agent->>Client: ToolCallEndEvent
        end
        Agent->>Client: ModelCallEndEvent
    end

    rect rgba(100, 255, 150, 0.1)
        Note over Client,Agent: Acting phase
        rect rgba(200, 200, 100, 0.1)
            Note over Client,Agent: ToolResultBlock (toolCallId)
            Agent->>Client: ToolResultStartEvent
            Agent->>Client: ToolResultTextDeltaEvent (×N)
            Agent->>Client: ToolResultDataDeltaEvent (×N)
            Agent->>Client: ToolResultEndEvent
        end
    end

    Agent->>Client: AgentEndEvent
```

All events in one reply share the same `replyId`. Within a reply, `blockId` ties text/thinking/data block events together; `toolCallId` ties tool calls and tool results. A `blockId` is scoped to its `replyId` and does not have to be a globally unique generated ID. When a block type can have at most one lifecycle within a reply, an implementation may use a stable type key, such as a fixed key for the text block.

### Event types

All events extend `AgentEvent` (`io.agentscope.core.event`), which exposes the common methods:

| Method | Type | Description |
| - | - | - |
| `getId()` | `String` | Unique event identifier |
| `getCreatedAt()` | `String` | ISO 8601 timestamp |
| `getType()` | `AgentEventType` | Event type enum |
| `getSource()` | `String` | Source path identifying the originating agent. `null` for top-level agent events; a slash-separated path (e.g. `"main/researcher"`) for events forwarded from a subagent |
| `getMetadata()` | `Map<String, Object>` | Optional key/value bag. Remote subagent forwards also set `taskId` (`AgentEvent.METADATA_TASK_ID`) to the harness / Agent Protocol task id and `parentSessionId` (`AgentEvent.METADATA_PARENT_SESSION_ID`) to the parent session when events are task-backed |

Events are grouped below; unless noted otherwise, every event also carries `getReplyId()` linking it to the message being assembled.

<Accordion title="Lifecycle events">
  **AgentStartEvent** — agent begins a new reply.

  | Method | Type | Description |
  | - | - | - |
  | `getReplyId()` | `String` | Reply message ID |
  | `getSessionId()` | `String` | Session ID |
  | `getName()` | `String` | Agent name |
  | `getRole()` | `String` | Agent role (default `"assistant"`) |

  **AgentEndEvent** — agent finishes a reply.

  | Method | Type | Description |
  | - | - | - |
  | `getReplyId()` | `String` | Reply message ID |

  **ExceedMaxItersEvent** — agent hit the max reasoning-acting iteration limit.

  | Method | Type | Description |
  | - | - | - |
  | `getReplyId()` | `String` | Reply message ID |

  **RequestStopEvent** — early-stop request raised by middleware or a tool.
</Accordion>

<Accordion title="Text streaming events">
  **TextBlockStartEvent** — a new text block begins.

  | Method | Type | Description |
  | - | - | - |
  | `getReplyId()` | `String` | Reply message ID |
  | `getBlockId()` | `String` | Text-block correlation key within the current reply |

  **TextBlockDeltaEvent** — incremental text content arrives.

  | Method | Type | Description |
  | - | - | - |
  | `getReplyId()` | `String` | Reply message ID |
  | `getBlockId()` | `String` | Text-block correlation key within the current reply |
  | `getDelta()` | `String` | Incremental text content |

  **TextBlockEndEvent** — text block completes.

  | Method | Type | Description |
  | - | - | - |
  | `getReplyId()` | `String` | Reply message ID |
  | `getBlockId()` | `String` | Text-block correlation key within the current reply |
</Accordion>

<Accordion title="Thinking streaming events">
  **ThinkingBlockStartEvent / ThinkingBlockDeltaEvent / ThinkingBlockEndEvent** — same shape as the text streaming events; specific to the model's chain of thought. Its `blockId` has the same reply-scoped correlation-key semantics.
</Accordion>

<Accordion title="Data streaming events">
  **DataBlockStartEvent / DataBlockDeltaEvent / DataBlockEndEvent** — same shape as the text streaming events, carrying images / audio / video binary data:

  * `DataBlockStartEvent`: `getMediaType()` returns the MIME type (e.g. `"image/png"`).
  * `DataBlockDeltaEvent`: `getData()` returns incremental base64-encoded data.
</Accordion>

<Accordion title="Tool-call streaming events">
  **ToolCallStartEvent** — agent begins a tool call.

  | Method | Type | Description |
  | - | - | - |
  | `getReplyId()` | `String` | Reply message ID |
  | `getToolCallId()` | `String` | Unique tool call ID |
  | `getToolCallName()` | `String` | The tool being called |

  **ToolCallDeltaEvent** — incremental tool-call arguments arrive; `getDelta()` returns a JSON fragment.

  **ToolCallEndEvent** — tool-call arguments complete.
</Accordion>

<Accordion title="Tool-result streaming events">
  **ToolResultStartEvent** — tool starts executing (carries `toolCallId`, `toolCallName`).

  **ToolResultTextDeltaEvent** — incremental text output from the tool; `getDelta()` returns a text fragment.

  **ToolResultDataDeltaEvent** — incremental binary output from the tool; similar to `DataBlockDeltaEvent` with `mediaType` / `data` / `url`.

  **ToolResultEndEvent** — tool completes.

  | Method | Type | Description |
  | - | - | - |
  | `getReplyId()` | `String` | Reply message ID |
  | `getToolCallId()` | `String` | The matching tool call ID |
  | `getState()` | `ToolResultState` | Final state: `SUCCESS`, `ERROR`, `INTERRUPTED`, `DENIED`, `RUNNING` |
</Accordion>

<Accordion title="Model-call events">
  **ModelCallStartEvent** — model API call starts (carries `modelName`).

  **ModelCallEndEvent** — model API call completes (carries `inputTokens` / `outputTokens`).
</Accordion>

<Accordion title="Human-in-the-loop events">
  **RequireUserConfirmEvent** — agent pauses for user confirmation.

  | Method | Type | Description |
  | - | - | - |
  | `getReplyId()` | `String` | Reply message ID |
  | `getToolCalls()` | `List<ToolUseBlock>` | Tool calls awaiting confirmation |

  **RequireExternalExecutionEvent** — agent pauses for external execution.

  | Method | Type | Description |
  | - | - | - |
  | `getReplyId()` | `String` | Reply message ID |
  | `getToolCalls()` | `List<ToolUseBlock>` | Tool calls awaiting external execution |

  **UserConfirmResultEvent** — emitted when a later `call()` resumes a paused permission HITL request.
  It carries one or more `ConfirmResult`s, and its `replyId` matches the earlier `RequireUserConfirmEvent`.

  | Method | Type | Description |
  | - | - | - |
  | `getReplyId()` | `String` | Reply ID of the correlated `RequireUserConfirmEvent` |
  | `getConfirmResults()` | `List<ConfirmResult>` | Confirmation results accepted for this resume |

  **ExternalExecutionResultEvent** — emitted when a later `call()` resumes a paused external-execution request.
  It carries one or more `ToolResultBlock`s, and its `replyId` matches the earlier `RequireExternalExecutionEvent`.

  | Method | Type | Description |
  | - | - | - |
  | `getReplyId()` | `String` | Reply ID of the correlated `RequireExternalExecutionEvent` |
  | `getToolResults()` | `List<ToolResultBlock>` | External execution results accepted for this resume |

  **AllToolsDeniedEvent** — the user denied all tool calls from the most recent reasoning step via HITL confirmation. This event is emitted through the `onActing` middleware chain, allowing middlewares to emit a `RequestStopEvent` to stop the agent. If no middleware handles it, the agent continues to the next reasoning iteration (backward compatible).

  | Method | Type | Description |
  | - | - | - |
  | `getDeniedToolCalls()` | `List<ToolUseBlock>` | The denied tool calls |
</Accordion>

<Accordion title="Subagent events">
  **SubagentExposedEvent** — a subagent spawned via `agent_spawn(expose_to_user=true)` has been exposed as a user-addressable entry point. SSE / streaming consumers can use this to render a new conversation entry in the UI.

  | Method | Type | Description |
  | - | - | - |
  | `getSubagentId()` | `String` | Unique identifier of the subagent |
  | `getAgentId()` | `String` | Agent type ID of the subagent |
  | `getSessionId()` | `String` | Session ID of the subagent |
  | `getLabel()` | `String` | User-visible label (optional) |
</Accordion>

## Reconstructing messages from events

Events and messages are not separate worlds — they are two views of the same data. The event stream from `streamEvents` can be aggregated by `replyId` / `blockId` / `toolCallId` to reconstruct a complete `AssistantMessage`, ensuring the final message state is fully recoverable from events alone.

See `agentscope-core`'s `agent/StreamingHook.java` and `agentscope-examples/documentation/.../streaming/AgentEventStreamExample.java` for the standard pattern of grouping by block ID and accumulating content with Reactor operators.

```java theme={null}
import io.agentscope.core.event.AgentEvent;
import io.agentscope.core.event.AgentStartEvent;
import io.agentscope.core.event.AgentEndEvent;
import io.agentscope.core.event.TextBlockDeltaEvent;
import io.agentscope.core.event.ToolCallStartEvent;
import io.agentscope.core.event.ToolResultEndEvent;

StringBuilder accumulated = new StringBuilder();

agent.streamEvents(userMsg)
        .doOnNext(event -> {
            if (event instanceof AgentStartEvent start) {
                System.out.println("[start replyId=" + start.getReplyId() + "]");
            } else if (event instanceof TextBlockDeltaEvent delta) {
                accumulated.append(delta.getDelta());
            } else if (event instanceof ToolCallStartEvent tc) {
                System.out.println("[tool] " + tc.getToolCallName());
            } else if (event instanceof ToolResultEndEvent end) {
                System.out.println("[tool result state=" + end.getState() + "]");
            } else if (event instanceof AgentEndEvent end) {
                System.out.println("\n[end] full text:\n" + accumulated);
            }
        })
        .blockLast();
```

<Tip>
  This decoupling makes deployments flexible: the backend pushes the event stream over SSE, and the frontend reconstructs the message client-side. Even if the connection drops, replaying events from any checkpoint restores the message state precisely.
</Tip>

### Example: streaming UI

A typical streaming UI loop (a Spring WebFlux SSE form is shown in `streaming/StreamingWebExample.java`):

```java theme={null}
import io.agentscope.core.event.AgentEndEvent;
import io.agentscope.core.event.AgentStartEvent;
import io.agentscope.core.event.TextBlockDeltaEvent;
import io.agentscope.core.event.ToolCallStartEvent;
import io.agentscope.core.event.ToolResultEndEvent;
import io.agentscope.core.message.UserMessage;

agent.streamEvents(new UserMessage("user", "Help me fix this bug"))
        .doOnNext(event -> {
            if (event instanceof AgentStartEvent start) {
                System.out.println("[start replyId=" + start.getReplyId() + "]");
            } else if (event instanceof TextBlockDeltaEvent delta) {
                System.out.print(delta.getDelta());
            } else if (event instanceof ToolCallStartEvent tc) {
                System.out.println("\n[calling " + tc.getToolCallName() + "...]");
            } else if (event instanceof ToolResultEndEvent end) {
                System.out.println("[tool finished: " + end.getState() + "]");
            } else if (event instanceof AgentEndEvent end) {
                System.out.println("\n[done]");
            }
        })
        .blockLast();
```

## Further reading

<CardGroup cols={2}>
  <Card title="Agent" href="/v2/en/docs/building-blocks/agent">
    How agents emit events and messages in the ReAct loop
  </Card>

  <Card title="Context" href="/v2/en/docs/building-blocks/context">
    How messages are stored and persisted
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.