Message & Event¶
Message and event are the two fundamental data structures in AgentScope.
Message — the primitive of agent-to-agent communication and persistence. Each
Msgis 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 ContentBlocks.
Tip
A single assistant Msg corresponds to one full call cycle (multiple reasoning + acting iterations until the final reply).
Structure¶
The core fields on Msg (via getters):
Method |
Type |
Description |
|---|---|---|
|
|
Unique message identifier |
|
|
Sender name (nullable) |
|
|
|
|
|
Ordered list of content blocks (immutable) |
|
|
Arbitrary key/value metadata |
|
|
Creation time ( |
|
|
Token usage (assistant messages only) |
|
|
Termination reason: |
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 |
|---|---|---|
|
Plain text content |
USER, ASSISTANT, SYSTEM |
|
Binary data (image / audio / video) via base64 or URL — unifies the legacy ImageBlock / AudioBlock / VideoBlock |
USER, ASSISTANT |
|
Legacy concrete media blocks (still supported; new code should prefer |
USER |
|
Model reasoning / chain of thought |
ASSISTANT |
|
A tool call: |
ASSISTANT |
|
A tool result with |
ASSISTANT |
|
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.
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.
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():
UserMessage msg =
UserMessage.builder()
.name("user")
.textContent("Hello")
.build();
Accessing content¶
Msg provides helpers for extracting specific block types:
Method |
Returns |
|---|---|
|
All |
|
List filtered by type |
|
The first matching block, or 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 AgentEvents (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() identifies the content block the event belongs to. Events follow a start → delta → end pattern:
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.
Event types¶
All events extend AgentEvent (io.agentscope.core.event), which exposes the common methods:
Method |
Type |
Description |
|---|---|---|
|
|
Unique event identifier |
|
|
ISO 8601 timestamp |
|
|
Event type enum |
Events are grouped below; unless noted otherwise, every event also carries getReplyId() linking it to the message being assembled.
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.
Text streaming events
TextBlockStartEvent — a new text block begins.
Method |
Type |
Description |
|---|---|---|
|
|
Reply message ID |
|
|
Unique text block ID |
TextBlockDeltaEvent — incremental text content arrives.
Method |
Type |
Description |
|---|---|---|
|
|
Reply message ID |
|
|
Unique text block ID |
|
|
Incremental text content |
TextBlockEndEvent — text block completes.
Method |
Type |
Description |
|---|---|---|
|
|
Reply message ID |
|
|
Unique text block ID |
Thinking streaming events
ThinkingBlockStartEvent / ThinkingBlockDeltaEvent / ThinkingBlockEndEvent — same shape as the text streaming events; specific to the model’s chain of thought.
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.
Tool-call streaming events
ToolCallStartEvent — agent begins a tool call.
Method |
Type |
Description |
|---|---|---|
|
|
Reply message ID |
|
|
Unique tool call ID |
|
|
The tool being called |
ToolCallDeltaEvent — incremental tool-call arguments arrive; getDelta() returns a JSON fragment.
ToolCallEndEvent — tool-call arguments complete.
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 |
|---|---|---|
|
|
Reply message ID |
|
|
The matching tool call ID |
|
|
Final state: |
Model-call events
ModelCallStartEvent — model API call starts (carries modelName).
ModelCallEndEvent — model API call completes (carries inputTokens / outputTokens).
Human-in-the-loop events
RequireUserConfirmEvent — agent pauses for user confirmation.
Method |
Type |
Description |
|---|---|---|
|
|
Reply message ID |
|
|
Tool calls awaiting confirmation |
RequireExternalExecutionEvent — agent pauses for external execution.
UserConfirmResultEvent — user provides confirmation results (input event); carries List<ConfirmResult>.
ExternalExecutionResultEvent — external system returns execution results (input event); carries List<ToolResultBlock>.
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.
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.
Example: streaming UI¶
A typical streaming UI loop (a Spring WebFlux SSE form is shown in streaming/StreamingWebExample.java):
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¶
How agents emit events and messages in the ReAct loop
How messages are stored and persisted