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).ToolProgressEvent — an intermediate update published by a running tool through ToolEmitter. Progress is for display only and is never sent to the LLM; it must not be treated as part of the tool result.ToolResultTextDeltaEvent — incremental text output from the tool’s return value; getDelta() returns a text fragment. This is what the LLM receives as the tool result.ToolResultDataDeltaEvent — incremental binary or structured output from the tool’s return value; 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. A suspended call produces no ToolResult* events: the tool has not executed, and the real result is supplied by the caller and surfaced through ExternalExecutionResultEvent.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.
For live output within the current request, adapt AgentEvent to SSE. streamEvents() starts execution and has no durable cursor; do not execute the request again to catch up after a disconnect. Harness also saves logs for direct calls by default. See Session operations, events and recovery for reading history and introducing AgentSession when you need background tasks. Hosted frontends use Agent API snapshot plus SSE.

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

Relationship to durable SessionEvent

AgentEvent is a typed live event; SessionEvent is an immutable durable fact, not a one-to-one copy. Standard execution exposes agentId/sessionId/turnId/runId through event.getExecution(). runId matches native executionRunId. Forwarded children keep their own identity. Live event IDs, native seq and public SSE cursors remain independent; receiving a live event is not a commit acknowledgment. A logical turn may span multiple executions. AgentEndEvent only ends the current invocation. See Session operations, events and recovery.