The core data abstractions for agent communication and streaming
Message and Event are the two fundamental data structures in AgentScope.
Message — the unit of inter-agent communication and persistence. Each Msg represents a complete conversation turn that is stored in context and exchanged between agents.
Event — the unit 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 workflows.
A sequence of events produced during a single reply call accumulates into exactly one assistant Msg. This guarantees that the complete message state is always recoverable from its event stream.
A Msg represents a single turn in a conversation — a user input, an assistant response, or a system instruction, carrying structured content as a list of typed blocks.
One assistant message corresponds to one complete reply cycle of the agent (repeated reasoning and acting until a final response is produced).
Message content is composed of typed blocks. Each block represents a distinct piece of information:
Block
Description
Allowed In
TextBlock
Plain text content
user, assistant, system
DataBlock
Binary data (images, audio) via base64 or URL
user, assistant
ThinkingBlock
Model reasoning (chain-of-thought)
assistant
ToolCallBlock
A tool invocation with name, input, and state
assistant
ToolResultBlock
The output of a tool execution
assistant
HintBlock
Out-of-band hint injected into the conversation (e.g. a scheduled-task trigger, a team message, a background-tool result). The hint field is str for plain text or list[TextBlock | DataBlock] for multimodal payloads; source carries a small JSON tag the frontend uses to label the hint’s origin.
assistant
Role constraints are enforced at construction: user messages can only contain TextBlock and DataBlock; system messages can only contain TextBlock; assistant messages can contain all block types.
AgentScope provides three shortcut factory functions for quickly creating messages with the correct role, without manually specifying role or wrapping content into blocks:
Factory
Role
Allowed Content
UserMsg(name, content)
user
str or list[TextBlock | DataBlock]
AssistantMsg(name, content)
assistant
str or list[ContentBlock]
SystemMsg(name, content)
system
str or list[TextBlock]
When content is a plain string, it is automatically wrapped into a TextBlock.
from agentscope.message import UserMsg, AssistantMsg, SystemMsg# User message — text and optional imagesuser_msg = UserMsg(name="user", content="What's in this image?")# User message with multimodal contentfrom agentscope.message import TextBlock, DataBlock, Base64Sourceuser_msg = UserMsg( name="user", content=[ TextBlock(text="Describe this image:"), DataBlock(source=Base64Source(data="...", media_type="image/png")), ],)# System message — text onlysystem_msg = SystemMsg(name="system", content="You are a helpful assistant.")# Assistant message — all block types allowedassistant_msg = AssistantMsg(name="agent", content="Here is the result...")
Msg provides helper methods to extract specific block types:
Method
Returns
get_text_content(separator="\n")
Concatenated text from all TextBlocks, or None
get_content_blocks(block_type)
Filtered list of blocks by type
has_content_blocks(block_type)
True if blocks of the given type exist
# Get all text contenttext = msg.get_text_content()# Get all tool callstool_calls = msg.get_content_blocks("tool_call")# Check if message has tool resultsif msg.has_content_blocks("tool_result"): ...
Events are the streaming counterpart of messages. While the agent executes, it yields a sequence of AgentEvent objects that represent incremental progress — text tokens arriving, tool calls being constructed, results streaming back. Each event is lightweight and self-contained.
Every event carries a reply_id that links it to the message being constructed. Within a reply, block_id or tool_call_id identifies which content block an event belongs to. Events follow a start → delta → end pattern for each content block:All events within the same reply share the same reply_id. Within a reply, use block_id to correlate text/thinking/data block events, and tool_call_id to correlate tool call and tool result events.
ToolCallEndEvent — The tool call input is complete.
Field
Type
Description
reply_id
str
ID of the reply message
tool_call_id
str
Unique identifier of the tool call
Tool Result Streaming Events
ToolResultStartEvent — Tool execution begins.
Field
Type
Description
reply_id
str
ID of the reply message
tool_call_id
str
ID of the corresponding tool call
tool_call_name
str
Name of the tool
ToolResultTextDeltaEvent — Incremental text output from the tool.
Field
Type
Description
reply_id
str
ID of the reply message
tool_call_id
str
ID of the corresponding tool call
delta
str
Incremental text content
ToolResultDataDeltaEvent — Binary data output from the tool.
Field
Type
Description
reply_id
str
ID of the reply message
tool_call_id
str
ID of the corresponding tool call
block_id
str
Unique identifier of the data block
media_type
str
MIME type of the content
data
str | None
Base64-encoded data (mutually exclusive with url)
url
str | None
URL pointing to the content (mutually exclusive with data)
ToolResultEndEvent — Tool execution is complete.
Field
Type
Description
reply_id
str
ID of the reply message
tool_call_id
str
ID of the corresponding tool call
state
ToolResultState
Final state: SUCCESS, ERROR, INTERRUPTED, DENIED, or RUNNING
Model Call Events
ModelCallStartEvent — A model API call begins.
Field
Type
Description
reply_id
str
ID of the reply message
model_name
str
Name of the model being called
ModelCallEndEvent — A model API call completes.
Field
Type
Description
reply_id
str
ID of the reply message
input_tokens
int
Number of input tokens consumed
output_tokens
int
Number of output tokens generated
Human-in-the-Loop Events
RequireUserConfirmEvent — Agent pauses for user confirmation.
Field
Type
Description
reply_id
str
ID of the reply message
tool_calls
list[ToolCallBlock]
Tool calls pending user confirmation
RequireExternalExecutionEvent — Agent pauses for external execution.
Field
Type
Description
reply_id
str
ID of the reply message
tool_calls
list[ToolCallBlock]
Tool calls to be executed externally
UserConfirmResultEvent — User provides confirmation results (input event).
Field
Type
Description
reply_id
str
ID of the reply message
confirm_results
list[ConfirmResult]
Confirmation results for each pending tool call
ExternalExecutionResultEvent — External system provides execution results (input event).
Field
Type
Description
reply_id
str
ID of the reply message
execution_results
list[ToolResultBlock]
Results returned by the external executor
One-shot Events
Unlike text / thinking / data / tool blocks, these events do not follow the start → delta → end pattern. The full payload arrives in a single event because it is known up-front rather than streamed.HintBlockEvent — A HintBlock is injected into the agent’s context (e.g. a scheduled-task trigger, a team message, a result returned by an offloaded background tool).
Field
Type
Description
reply_id
str
ID of the reply message
block_id
str
Unique identifier of the hint block
hint
str | list[TextBlock | DataBlock]
The hint payload — plain text or a list of multimodal blocks
source
str | None
Optional sender / origin tag (typically a small JSON object describing how the frontend should label this hint)
CustomEvent — Generic extensible event used by service-layer middleware to notify subscribers about state changes (task progress, team membership, permission updates, …) without polluting the core agent event enum.
Field
Type
Description
reply_id
str
ID of the reply message
name
str
The signal name (e.g. "tasks_context", "team_updated")
value
dict
Arbitrary JSON-serialisable payload for this signal
Events and messages are not independent — they are two views of the same data. Every event produced by reply_stream can be applied to a Msg via append_event(), reconstructing the complete message incrementally. This guarantees that the final message state is fully recoverable from the event stream alone.
from agentscope.message import Msg, AssistantMsgmsg = None# Accumulate events into the messageasync for event in agent.reply_stream(user_msg): if isinstance(event, ReplyStartEvent): # Create a new message when the reply starts msg = AssistantMsg(name=event.name, content=[], id=event.reply_id) else: # For all other events, append to the message to reconstruct its state msg.append_event(event)
The append_event method handles all event types:
Event Type
Effect on Msg
ReplyEndEvent
Sets finished_at timestamp
TextBlockStartEvent
Appends a new empty TextBlock
TextBlockDeltaEvent
Concatenates delta to the block’s text
DataBlockStartEvent
Appends a new empty DataBlock
DataBlockDeltaEvent
Concatenates data to the block’s base64 content
ThinkingBlockStartEvent
Appends a new empty ThinkingBlock
ThinkingBlockDeltaEvent
Concatenates delta to the block’s thinking text
ToolCallStartEvent
Appends a new ToolCallBlock with empty input
ToolCallDeltaEvent
Concatenates delta to the tool call’s input
ToolResultStartEvent
Appends a new ToolResultBlock with empty output
ToolResultTextDeltaEvent
Appends text to the tool result’s output
ToolResultDataDeltaEvent
Appends a binary data block to the tool result’s output
ToolResultEndEvent
Sets the tool result’s final state
HintBlockEvent
Appends a HintBlock to content (carrying the event’s hint and source) so the hint is persisted and replayable
RequireUserConfirmEvent
Updates tool call states to ASKING
ExternalExecutionResultEvent
Appends ToolResultBlocks to content
This design enables flexible deployment: a backend can stream events over WebSocket to a frontend, which reconstructs the message client-side. If the connection drops, replaying the event sequence from any checkpoint restores the exact message state.
A TypeScript version of the message and event primitives is available on npm, so frontends can reconstruct messages from the event stream with the same appendEvent API:
pnpm install @agentscope-ai/agentscope
import { AssistantMsg, ReplyStartEvent } from "@agentscope-ai/agentscope/message";let msg: AssistantMsg | null = null;for await (const event of stream) { if (event.type === "REPLY_START") { msg = new AssistantMsg({ name: event.name, content: [], id: event.reply_id }); } else { msg?.appendEvent(event); }}