Skip to content

Envelopes & Events

The Envelope is the wire format for everything that flows between agents on the network. Every send produces one; every observer reads them. This page covers the envelope shape, the built-in event types, audience/visibility rules, and how to send raw envelopes when session.send(text) isn't enough.

The Envelope#

@dataclass(slots=True)
class Envelope:
    envelope_id: str        # hub-stamped UUID
    session_id: str
    sender_id: str          # agent_id
    audience: list[str] | None  # None = broadcast to all session participants
    event_type: str         # "ag2.msg.text", "ag2.session.invite", etc.
    event_data: dict        # event-specific payload
    causation_id: str | None = None  # envelope_id this one is "in reply to"
    priority: Priority = Priority.NORMAL
    created_at: str = ""    # hub-stamped ISO-Z
    sequence: int = 0       # hub-stamped per-session monotonic counter

The hub stamps envelope_id, created_at, and sequence at admission time. Everything else comes from the sender.

Built-in Event Types#

Constants exported from autogen.beta.network:

Substantive events#

Constant String Carried in event_data
EV_TEXT "ag2.msg.text" {"text": "<body>"}
EV_PACKET "ag2.packet" {"routing": {...}, "context_updates": {...}, "body": "<text>"}

These are the envelopes adapters fold into per-session state. EV_TEXT carries plain text. EV_PACKET is the workflow adapter's atomic round-end capture: it bundles the agent's routing decision (matched against ToolCalled(...) rules), accumulated context_vars mutations, and final body text into one envelope. Posted by the framework after each Agent.ask round; tool authors don't construct these directly.

Session lifecycle events#

Constant String Source
EV_SESSION_INVITE "ag2.session.invite" Hub posts to each target when a session is created.
EV_SESSION_INVITE_ACK "ag2.session.invite.ack" Each invitee posts when accepting.
EV_SESSION_INVITE_REJECT "ag2.session.invite.reject" Optional — invitee rejects (default handler doesn't, but you can override).
EV_SESSION_OPENED "ag2.session.opened" Hub posts when all acks land.
EV_SESSION_CLOSED "ag2.session.closed" Hub posts on any termination path; event_data.reason carries why.
EV_SESSION_EXPIRED "ag2.session.expired" Hub posts when TTL sweeper closes the session.
EV_EXPECTATION_VIOLATED "ag2.session.expectation_violated" Hub posts when an expectation evaluator's threshold is breached and the handler is notify (vs audit / auto_close).

Task lifecycle events#

Constant String Notes
ag2.task.started "ag2.task.started" Mirrored from TaskStarted.
ag2.task.progress "ag2.task.progress" Mirrored from TaskProgress.
ag2.task.completed "ag2.task.completed" Mirrored from TaskCompleted.
ag2.task.failed "ag2.task.failed" Mirrored from TaskFailed.
ag2.task.expired "ag2.task.expired" Mirrored from TaskExpired.

These flow only when an AgentClient is running an LLM turn — see Task Observation.

Audience and Visibility#

audience: list[str] | None controls who sees the envelope:

  • None — broadcast to all session participants.
  • [agent_id_1, agent_id_2, ...] — only those participants see it.

The visible_to(envelope, agent_id) helper says whether a given participant should see an envelope:

1
2
3
4
from autogen.beta.network import visible_to

if visible_to(env, my_agent_id):
    process(env)

Views (FullTranscript, WindowedSummary) honour the audience: an envelope addressed only to [bob] doesn't appear in carol's projection. See Views & Skills.

Priority#

class Priority(IntEnum):
    LOW = 0
    NORMAL = 1
    HIGH = 2
    URGENT = 3

The hub processes higher-priority envelopes ahead of lower-priority ones in queue order. Use sparingly; most application envelopes should leave priority at NORMAL.

Sending Raw Envelopes#

The session.send(text, audience=...) helper wraps the envelope construction for you. When you need a custom event type or to set fields the helper doesn't expose, build an Envelope and post it directly:

from autogen.beta.network import Envelope, EV_TEXT

envelope = Envelope(
    session_id=session.session_id,
    sender_id=alice.agent_id,
    audience=[bob.agent_id],
    event_type="myapp.review_request",
    event_data={"document_id": "doc-123", "kind": "security"},
)
await alice.send_envelope(envelope)

The hub doesn't validate event_type against any allowlist; custom types pass through unmodified. Adapters fold only event types they recognise — substantive ones (EV_TEXT, plus EV_PACKET under the workflow adapter) and lifecycle ones (EV_SESSION_*). Custom event types are written to the WAL and delivered to participants but don't advance turn-taking state.

Causation#

causation_id lets you mark an envelope as "in reply to" another:

await session.send(reply_text, causation_id=incoming_envelope.envelope_id)

The default handler does this automatically when it replies to an inbound EV_TEXT. Custom handlers should set it when they're producing a logical reply — useful for tooling that builds threaded views of a session.

Reading the WAL#

The hub maintains a per-session write-ahead log:

1
2
3
wal = await hub.read_wal(session_id)
for env in wal:
    print(f"{env.sequence:>3}  {env.event_type}  from={env.sender_id[:8]}")

Envelopes appear in admission order. The WAL is the canonical replay surface — Hub.hydrate() re-folds it through each adapter to rebuild in-memory state on restart.

Custom Event Types — Practical Tips#

When you define your own event types:

  1. Use a dotted namespace prefix ("myapp.review_request", not "review") to avoid collisions with future ag2.* events.
  2. Make event_data JSON-serialisable (no datetimes, dataclasses, etc.) so it round-trips through the store cleanly.
  3. If multiple participants need to react, set audience=None. If only one, address it specifically; views will filter it out from non-recipients.
  4. Don't rely on adapters to do anything special with custom types — they pass through. Your custom handler is responsible for processing them.

Inspecting Frames#

Below the envelope layer is the frame layer (HelloFrame, SendFrame, NotifyFrame, …) — that's the link transport. Most users don't touch it. It surfaces only when you're implementing a custom transport (see Agent Clients) or debugging a connection issue.