EventBus and SessionController API Reference¶
EventBus¶
PubSub event bus for cross-turn event streaming.
Decouples event producers (agents) from consumers (protocol handlers).
Events are broadcast to all subscribers for a given session via _send()
with no publish-side buffering or coalescing.
Event coalescing/merging is the subscriber's responsibility. Subscribers
should drain their queue using drain_and_merge() to batch-merge
consecutive same-type events (e.g., PartDeltaEvent text chunks) for
efficient processing.
Safety features:
- Bounded asyncio.Queue with configurable overflow policies
- Automatic cleanup of dead subscribers
- Queue.shutdown()-based shutdown (no sentinel None)
__init__
¶
__init__(
max_queue_size: int = DEFAULT_QUEUE_MAXSIZE,
replay_buffer_size: int = 100,
session_controller: SessionController | None = None,
overflow_policy: OverflowPolicy = "drop_oldest",
) -> None
Initialize the event bus.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
max_queue_size
|
int
|
Maximum buffer size for subscriber queues. |
DEFAULT_QUEUE_MAXSIZE
|
replay_buffer_size
|
int
|
Maximum number of events retained per session for replay. |
100
|
session_controller
|
SessionController | None
|
Optional session controller for hierarchy queries. |
None
|
overflow_policy
|
OverflowPolicy
|
Policy for handling full subscriber queues.
One of |
'drop_oldest'
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
clear_replay_buffer
¶
clear_replay_buffer(session_id: str) -> None
Clear the replay buffer for a session.
Removes all historical events from the replay buffer so that new subscribers only receive events from this point forward. This should be called at the start of each turn to prevent stale events (including terminal events like StreamCompleteEvent) from previous turns being replayed to new subscribers.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session_id
|
str
|
The session whose replay buffer to clear. |
required |
close_session
async
¶
close_session(session_id: str) -> None
Close all subscriptions for a session.
Shuts down all subscriber queues to signal QueueShutDown to
consumers, and clears the replay buffer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session_id
|
str
|
The session to close subscriptions for. |
required |
get_subscriber_counts
async
¶
publish
async
¶
Publish an event to all subscribers for a session.
Wraps the event in an EventEnvelope and sends it directly via _send().
PartDeltaEvent with delta=None is dropped (no content to deliver).
Coalescing is handled subscriber-side by drain_and_merge().
Each published event is assigned a monotonically increasing event_id
so that subscribers can request conditional replay via last_event_id.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session_id
|
str
|
The session that produced the event. |
required |
event
|
Any
|
The event to broadcast. |
required |
subscribe
async
¶
subscribe(
session_id: str,
scope: str = "session",
*,
replay: bool = True,
last_event_id: int | None = None
) -> Queue[EventEnvelope]
Subscribe to events for a session.
New subscribers receive replayed historical events from the replay buffer before live events. Events published during the replay phase are drained and re-inserted after historical events to preserve ordering and avoid loss.
Conditional replay is supported via replay and last_event_id:
replay=False: Skip the replay buffer entirely (zero historical events). Only live events published after subscription are delivered.replay=True, last_event_id=None: Replay all buffered events (default — backward compatible).replay=True, last_event_id=N: Replay only events withevent_id > N. If the buffer's oldest event hasevent_id > N + 1(gap detected, meaning events N+1..oldest-1 have been evicted), fall back to replaying all events from that buffer to avoid silent data loss.
For scope="all", the filtering applies per-buffer after collecting
from all buffers.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session_id
|
str
|
The session to subscribe to. |
required |
scope
|
str
|
Subscription scope - "session" (exact match), "descendants" (self + children), or "subtree" (self + parent + siblings). Deprecated: descendants scope The "descendants" scope is deprecated for protocol server use.
It has known issues with replay buffer data loss, O(N) recursive
traversal, and duplicate deliveries. Protocol servers should use
"session" scope with explicit child consumers via
|
'session'
|
replay
|
bool
|
If False, skip replay buffer (no historical events). |
True
|
last_event_id
|
int | None
|
If provided with |
None
|
Returns:
| Type | Description |
|---|---|
Queue[EventEnvelope]
|
An |
unsubscribe
async
¶
Unsubscribe from events.
Shuts down the subscriber's queue so the consumer receives
QueueShutDown. Cleans up empty subscriber lists to prevent
memory leaks.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session_id
|
str
|
The session to unsubscribe from. |
required |
queue
|
Queue[EventEnvelope]
|
The queue returned by subscribe(). |
required |
SessionController¶
Bases: SessionControllerAgentMixin, SessionControllerRunsMixin, SessionControllerCloseMixin
Manages per-session agent lifecycle.
Extracted from ACP's AgentPoolACPAgent._session_agents and OpenCode's ServerState._session_agents.
Safety features: - Single global lock for session creation (no DCL) - Per-session turn lock for serialization - Explicit cleanup of all resources - Support for all agent types (with per-session agents for NativeAgentConfig only)
runtime_registry
property
¶
Runtime agent registry for programmatically-created agents.
__init__
¶
__init__(
pool: AgentPool[Any],
store: SessionPersistence | None = None,
cleanup_callback: Callable[[str], Awaitable[None]] | None = None,
max_concurrent_runs: int | None = None,
session_ttl_seconds: float = DEFAULT_SESSION_TTL_SECONDS,
cleanup_interval_seconds: float | None = None,
deferred_cleanup_interval_seconds: float = 60.0,
) -> None
Initialize the session controller.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
pool
|
AgentPool[Any]
|
The agent pool to resolve agents from. |
required |
store
|
SessionPersistence | None
|
Optional session store for persistence. |
None
|
cleanup_callback
|
Callable[[str], Awaitable[None]] | None
|
Optional callback invoked when a session is cleaned up. |
None
|
max_concurrent_runs
|
int | None
|
Maximum number of concurrent runs across all sessions. |
None
|
session_ttl_seconds
|
float
|
TTL for idle sessions in seconds. |
DEFAULT_SESSION_TTL_SECONDS
|
cleanup_interval_seconds
|
float | None
|
Interval for the session TTL cleanup
loop. Defaults to |
None
|
deferred_cleanup_interval_seconds
|
float
|
Interval for the deferred call expiry cleanup loop in seconds. |
60.0
|
cancel_all_pending_questions
¶
cancel_run_for_session
¶
Cancel the active run for a session.
Only cancels the run if the RunHandle is still active
(complete_event is not set). If the handle has already
completed, the cancel is skipped.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session_id
|
str
|
The session whose run should be cancelled. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
|
bool
|
session/run was not found or already completed. |
cancel_session_pending_questions
¶
close_session
async
¶
close_session(session_id: str) -> None
Close a session and clean up resources.
Delegates to :meth:_close_session_run_turn which performs
checkpoint-on-close (if needed) and then the standardized
7-step cleanup ordering via :meth:_close_session_unlocked.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session_id
|
str
|
The session to close. |
required |
find_sessions_by_agent_name
¶
get_children
¶
get_or_create_session
async
¶
get_or_create_session(
session_id: str,
agent_name: str | None = None,
parent_session_id: str | None = None,
lifecycle_policy: str | None = None,
**metadata: Any
) -> tuple[SessionState, bool]
Get or create a session.
Uses single global lock for simplicity and safety. Session creation is infrequent - no need for DCL optimization.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session_id
|
str
|
Unique identifier for the session. |
required |
agent_name
|
str | None
|
Name of the agent to associate with the session. |
None
|
parent_session_id
|
str | None
|
Optional parent session ID for hierarchical sessions. |
None
|
lifecycle_policy
|
str | None
|
Optional lifecycle policy override. |
None
|
**metadata
|
Any
|
Arbitrary metadata to attach to the session. |
{}
|
Returns:
| Type | Description |
|---|---|
SessionState
|
A tuple of (session_state, was_created) where was_created is True |
bool
|
if the session was newly created, False if it already existed. |
get_or_create_session_agent
async
¶
get_or_create_session_agent(
session_id: str, agent_name: str | None = None, input_provider: Any | None = None
) -> BaseAgent[Any, Any]
Get or create a dedicated agent for a session.
Delegates agent creation to AgentFactory.create_session_agent(),
handling caching, session lookup, and config resolution locally.
NOTE: Always acquires self._lock to prevent races with close_session().
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session_id
|
str
|
Unique identifier for the session. |
required |
agent_name
|
str | None
|
Name of the agent to use. |
None
|
input_provider
|
Any | None
|
Optional input provider for the agent. |
None
|
Returns:
| Type | Description |
|---|---|
BaseAgent[Any, Any]
|
The agent instance (per-session or shared). |
get_parent
¶
get_parent(session_id: str) -> SessionState | None
Get the parent session state for a session.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session_id
|
str
|
The child session ID. |
required |
Returns:
| Type | Description |
|---|---|
SessionState | None
|
The parent session state, or None if not found. |
get_session
¶
get_session(session_id: str) -> SessionState | None
Get a session by ID.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session_id
|
str
|
The session ID to look up. |
required |
Returns:
| Type | Description |
|---|---|
SessionState | None
|
The session state, or None if not found. |
get_session_agent
¶
Get the agent for a session.
Returns the per-session agent if one exists, otherwise the shared agent that was assigned to the session. If the session has no agent assigned yet, a warning is logged and None is returned.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session_id
|
str
|
The session ID to look up. |
required |
Returns:
| Type | Description |
|---|---|
BaseAgent[Any, Any] | None
|
The agent instance, or None if the session is unknown. |
list_pending_permissions
¶
list_pending_permissions() -> list[PendingPermission]
List all pending permissions across sessions.
Returns:
| Type | Description |
|---|---|
list[PendingPermission]
|
A list of pending permissions. Currently returns an empty list. |
list_pending_questions
¶
list_sessions
¶
list_sessions() -> list[SessionInfo]
List all active sessions.
Returns:
| Type | Description |
|---|---|
list[SessionInfo]
|
A list of SessionInfo DTOs for all active sessions. |
revoke_inject
¶
Revoke a pending steer or followup message by ID.
In the per-prompt model, revocation is handled by
SessionState.revoke() which cancels queued steer messages
in feedback_queue.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session_id
|
str
|
The session containing the message. |
required |
message_id
|
str
|
The ID of the message to revoke. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
|
bool
|
if the session is not found. |
start_cleanup_task
async
¶
Start background tasks for session cleanup.
Launches two background tasks:
- _cleanup_loop: periodically closes expired sessions (TTL-based).
- _start_cleanup_loop: periodically expires stale deferred calls.
Both tasks are stored in _background_tasks to prevent garbage
collection mid-execution (per asyncio.create_task best practice).
wait_for_completion
async
¶
Wait for the active run on a session to complete.
In the per-prompt model, complete_event fires when the
RunHandle's single-turn generator terminates. If
_consume_run() chains to a new RunHandle, the caller must
re-check session.current_run_id to detect the new turn.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session_id
|
str
|
The session to wait for. |
required |
timeout
|
float | None
|
Maximum seconds to wait. Defaults to 300 seconds. |
300
|
Returns:
| Type | Description |
|---|---|
str
|
The |
Raises:
| Type | Description |
|---|---|
SessionNotFoundError
|
If the session does not exist. |
TimeoutError
|
If the run does not complete within
|