Skip to main content
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 ContentBlocks.
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):

Content blocks

Message content is composed of typed blocks, each representing one type of information. Block classes live in io.agentscope.core.message:
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.
For more optional fields (metadata, timestamp, usage, generateReason), use each subclass’s builder():

Accessing content

Msg provides helpers for extracting specific block types:

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() acts as a correlation key for events that belong to the same content-block lifecycle. Events follow a start → delta → end pattern: 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: Events are grouped below; unless noted otherwise, every event also carries getReplyId() linking it to the message being assembled.
AgentStartEvent — agent begins a new reply.AgentEndEvent — agent finishes a reply.ExceedMaxItersEvent — agent hit the max reasoning-acting iteration limit.RequestStopEvent — early-stop request raised by middleware or a tool.
TextBlockStartEvent — a new text block begins.TextBlockDeltaEvent — incremental text content arrives.TextBlockEndEvent — text block completes.
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.
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.
ToolCallStartEvent — agent begins a tool call.ToolCallDeltaEvent — incremental tool-call arguments arrive; getDelta() returns a JSON fragment.ToolCallEndEvent — tool-call arguments complete.
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.
ModelCallStartEvent — model API call starts (carries modelName).ModelCallEndEvent — model API call completes (carries inputTokens / outputTokens).
RequireUserConfirmEvent — agent pauses for user confirmation.RequireExternalExecutionEvent — agent pauses for external execution.UserConfirmResultEvent — emitted when a later call() resumes a paused permission HITL request. It carries one or more ConfirmResults, and its replyId matches the earlier RequireUserConfirmEvent.ExternalExecutionResultEvent — emitted when a later call() resumes a paused external-execution request. It carries one or more ToolResultBlocks, and its replyId matches the earlier RequireExternalExecutionEvent.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).
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.

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.
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):

Further reading

Agent

How agents emit events and messages in the ReAct loop

Context

How messages are stored and persisted