Skip to content

Events Reference ​

Events are the fundamental communication mechanism in the agent framework. This page provides a complete reference for the event hierarchy, how to choose the right base event, and a catalog of all available events.

Control vs display vs combined events ​

The framework distinguishes between event categories based on their effect on the workflow:

CategoryBase classTriggers dispatcherUse case
ControlControlEventYesWorkflow state transitions, step dependencies
DisplayDisplayEventNoUI updates, streaming output, observability
Control and DisplayControlAndDisplayEventYesBoth: advances workflow AND shows in UI

Control events influence the workflow's control flow. When a ControlEvent is published, the dispatcher evaluates all steps to determine if any new steps should execute. Only ControlEvent types (or subclasses) can satisfy step input requirements.

Display events do not trigger the dispatcher. Use them for high-frequency updates that should appear in the UI but should not cause step re-evaluation. A streamed LLM response may emit hundreds of ChunkEvent instances per minute; making these control events would cause unnecessary dispatcher overhead.

Combined events need both behaviors — they influence workflow AND appear in the UI. Most semantic events (LLMEvent, RetrieverEvent, etc.) inherit from ControlAndDisplayEvent.

Protocol events are combined events. Calls between steps and capabilities, such as a capability request and its result or a tool-loop decision, are part of the protocol, and an admin reading a run needs to see them. The event history lists display events only, so these inherit from ControlAndDisplayEvent and give themselves a _display_name and _display_description. The description is a short progress phrase ("Recalling memories"), since chat clients such as OpenWebUI show it as a live status while the agent works. Each one also has a component in the admin UI's event history. Keep plain ControlEvent for internal bookkeeping no reader needs. Only the dispatcher's JetStream copy triggers steps; the display copy goes out over NATS Core and never re-triggers anything.

python
from swiss_ai_hub.core.events.control.control_event import ControlEvent
from swiss_ai_hub.core.events.display.display_event import DisplayEvent
from swiss_ai_hub.core.events.control_and_display_event import ControlAndDisplayEvent

# Control event: triggers dispatcher, can satisfy step dependencies
class AnalysisCompleteEvent(ControlEvent):
    summary: str

# Display event: does NOT trigger dispatcher, UI-only
class ProgressUpdateEvent(DisplayEvent):
    percent_complete: float
    status_message: str

# Combined: triggers dispatcher AND displays in UI
class RetrieveEvent(ControlAndDisplayEvent):
    nodes: list[IngestedNode]

Events as flow carriers ​

Events serve two distinct purposes:

  1. Data carriers: Transporting values between steps
  2. Flow carriers: Controlling execution order independent of data

A step may depend on an event solely to ensure execution ordering, without requiring any data from it:

python
class PathA(ControlEvent):
    pass  # No fields — pure flow control

class PathB(ControlEvent):
    pass

@step()
async def decide(self, event: StartEvent) -> PathA | PathB:
    if condition():
        return PathA()
    return PathB()

@step()
async def handle_path_a(self, _: PathA) -> StopEvent:
    # Underscore signals: "I need this event for flow control, not data"
    return StopEvent()

The _: EventType convention indicates dependency on an event's existence rather than its contents. This pattern is essential for:

  • Conditional branching: Different steps execute based on which event type was emitted
  • Sequencing: Ensuring step B waits for step A without needing A's output data
  • Synchronization barriers: Waiting for a signal that work completed

Event hierarchy ​

BaseEvent
├── ControlEvent                    # Triggers dispatcher
│   ├── StartEvent                  # Workflow entry points
│   │   └── UserMessageEvent        # User-initiated workflow
│   ├── StopEvent                   # Workflow termination
│   └── ExceptionEvent              # Error signaling
│
├── DisplayEvent                    # UI-only, no dispatcher trigger
│   ├── ChunkEvent                  # Streaming text chunks
│   ├── ThoughtEvent                # Agent reasoning display
│   └── CostEvent                   # Cost tracking display
│       └── LLMCostEvent            # LLM-specific costs
│
├── ControlAndDisplayEvent          # Both behaviors
│   ├── SemanticEvent               # OpenInference-compatible (OTEL/Phoenix/Langfuse)
│   │   ├── LLMEvent                # LLM invocation
│   │   │   └── LLMStopEvent        # Terminal LLM response
│   │   ├── RetrieverEvent          # Document retrieval
│   │   ├── RerankerEvent           # Result reranking
│   │   ├── EmbeddingEvent          # Embedding generation
│   │   ├── ToolEvent               # Tool invocation
│   │   ├── GuardEvent              # Guardrail evaluation
│   │   ├── ChainEvent              # Chain execution
│   │   └── AgentEvent              # Agent invocation
│   │
│   ├── HumanInTheLoopRequestEvent  # HITL requests
│   ├── HumanInTheLoopResponseEvent # HITL responses
│   ├── AgentInTheLoopRequestEvent  # AITL delegation
│   ├── AgentInTheLoopResponseEvent # AITL results
│   ├── BotInTheLoopRequestEvent    # BITL Teams/Slack requests
│   ├── BotInTheLoopResponseEvent   # BITL responses
│   │
│   ├── BaseRetrieveMemoryEvent     # Memory retrieval
│   │   ├── RetrieveUserMemoryEvent
│   │   └── RetrieveOrganizationMemoryEvent
│   ├── BaseStoreMemoryEvent        # Memory persistence
│   │   ├── StoreUserMemoryEvent
│   │   └── StoreOrganizationMemoryEvent
│   │
│   └── RouterEvent                 # LLM routing decisions

Choosing the right base event ​

When creating a custom event, inherit from the most specific applicable base class:

If your event represents...Inherit fromBenefits
Workflow start conditionStartEventRecognized as entry point
User message initiating workflowUserMessageEventChat history, locale, user identity
Workflow terminationStopEventSignals completion
Error/failureExceptionEventError handling patterns
LLM invocation resultLLMEventToken counts, messages, OpenInference spans
LLM terminal responseLLMStopEventCombines LLM data with workflow termination
Document retrievalRetrieverEventRetrieved nodes, OpenInference spans
Reranking operationRerankerEventInput/output nodes, OpenInference spans
Embedding generationEmbeddingEventVectors, model info, OpenInference spans
Tool/function callToolEventTool name, parameters, OpenInference spans
Guardrail checkGuardEventGuard result, OpenInference spans
Human approval neededHumanInTheLoopRequestEventWorkflow suspension, UI prompt
Agent delegationAgentInTheLoopRequestEventCross-agent communication
Memory retrievalRetrieveUserMemoryEvent / RetrieveOrganizationMemoryEventMemory search results
Memory storageStoreUserMemoryEvent / StoreOrganizationMemoryEventMemory persistence confirmation
Streaming text chunkChunkEventReal-time UI updates (display-only)
Agent thought/reasoningThoughtEventTransparency display (display-only)
Cost informationLLMCostEventToken usage, pricing (display-only)
Generic workflow stateControlAndDisplayEventTriggers dispatcher + UI display
Generic UI updateDisplayEventUI-only, no dispatcher overhead

Semantic events and OpenInference ​

SemanticEvent subclasses implement the to_semantic_convention() method, producing attributes compatible with the OpenInference specification. This enables integration with:

  • Arize Phoenix: Trace visualization and debugging
  • Langfuse: LLM observability and analytics
  • Any OpenTelemetry-compatible system: Standard span attributes
python
from swiss_ai_hub.core.events.semantic import RetrieverEvent

# RetrieverEvent automatically exports OpenInference attributes:
# - openinference.span.kind: "RETRIEVER"
# - retrieval.documents.{i}.document.id
# - retrieval.documents.{i}.document.content
# - retrieval.documents.{i}.document.score

event = RetrieverEvent.from_nodes(retrieved_nodes)
otel_attributes = event.to_semantic_convention()

When building agents that require observability, prefer semantic events over generic ControlAndDisplayEvent:

python
# Preferred: semantic event for observability
from swiss_ai_hub.core.events.semantic import RetrieverEvent

@step()
async def retrieve(self, event: UserMessageEvent) -> RetrieverEvent:
    nodes = await retriever.retrieve(event.user_query)
    return RetrieverEvent.from_nodes(nodes)  # OpenInference-compatible

# Discouraged: generic event loses observability benefits
class MyRetrieveEvent(ControlAndDisplayEvent):
    nodes: list[NodeWithScore]  # No OpenInference integration

Available events reference ​

Control events (trigger dispatcher) ​

EventModulePurpose
ControlEventcontrol.ControlEventBase class for all control events
StartEventcontrol.start.StartEventWorkflow entry point
StopEventcontrol.stop.StopEventWorkflow termination
ExceptionEventcontrol.exception.ExceptionEventError signaling

Display events (UI only) ​

EventModulePurpose
DisplayEventdisplay.DisplayEventBase class for display events
ChunkEventdisplay.ChunkEventStreaming text output
ThoughtEventdisplay.ThoughtEventAgent reasoning transparency
CostEventcost.CostEventCost tracking base
LLMCostEventcost.LLMCostEventLLM token/cost reporting

Semantic events (OpenInference compatible) ​

EventModuleOpenInference span kind
SemanticEventsemantic.SemanticEventBase class (abstract)
LLMEventsemantic.llm.LLMEventLLM
LLMStopEventsemantic.llm.LLMStopEventLLM (terminal)
RetrieverEventsemantic.retriever.RetrieverEventRETRIEVER
RerankerEventsemantic.reranker.RerankerEventRERANKER
EmbeddingEventsemantic.embedding.EmbeddingEventEMBEDDING
ToolEventsemantic.tool.ToolEventTOOL
GuardEventsemantic.guard.GuardEventGUARDRAIL
ChainEventsemantic.chain.ChainEventCHAIN
AgentEventsemantic.agent.AgentEventAGENT

Interaction events ​

EventModulePurpose
UserMessageEventuser.UserMessageEventUser-initiated workflow start
HumanInTheLoopRequestEventhuman_in_the_loop.requestBase class for HITL requests
HumanInTheLoopInputRequestEventhuman_in_the_loop.requestPopup with text input field
HumanInTheLoopConfirmationRequestEventhuman_in_the_loop.requestYes/No button selection
HumanInTheLoopChatRequestEventhuman_in_the_loop.requestChat message (fallback)
HumanInTheLoopResponseEventhuman_in_the_loop.responseBase class for HITL responses
AgentInTheLoopRequestEventagent_in_the_loop.requestDelegate to another agent
AgentInTheLoopResponseEventagent_in_the_loop.responseDelegated agent result
AgentInTheLoopExceptionEventagent_in_the_loop.exceptionDelegated agent failure
BotInTheLoopRequestEventbot_in_the_loop.requestSend message to Teams/Slack channel
BotInTheLoopResponseEventbot_in_the_loop.responseResponse from Teams/Slack user

HITL helper classes (not events, but workflow utilities):

HelperUI behavior
HumanInTheLoopInputPopup dialog for free-form text entry
HumanInTheLoopConfirmationYes/No button selection
HumanInTheLoopChatMessage in chat stream (fallback for simple UIs/APIs)

Memory events ​

EventModulePurpose
BaseRetrieveMemoryEventmemory.retrieveMemory retrieval base
RetrieveUserMemoryEventmemory.retrieveUser-scoped memory retrieval
RetrieveOrganizationMemoryEventmemory.retrieveOrg-scoped memory retrieval
BaseStoreMemoryEventmemory.storeMemory storage base
StoreUserMemoryEventmemory.storeUser-scoped memory storage
StoreOrganizationMemoryEventmemory.storeOrg-scoped memory storage
AddMemoryToChatHistoryEventmemory.historyExtended chat history

Guard events ​

EventModulePurpose
GuardAcceptEventguard.GuardAcceptEventGuard passed
GuardRejectionEventguard.GuardRejectionEventGuard rejected
AgentSuitabilityAcceptEventguardAgent can handle request
AgentSuitabilityRejectEventguardAgent cannot handle request
ContextSufficientAcceptEventguardSufficient context available
ContextInsufficientRejectEventguardInsufficient context
SensitiveInfoAcceptEventguardNo sensitive info detected
SensitiveInfoRejectEventguardSensitive info detected

Capability events ​

Requests and results of the capabilities every chat blueprint calls (see the agent SDK's capabilities). All of them are ControlAndDisplayEvents, unless noted otherwise.

EventModulePurpose
ContextualizeConversationEventconversationAsks for the turn's query, meta-question gate and title
ConversationContextualizedEventconversationThe limited history and the query the turn is answered for
ComposeContextEventconversationAsks for the prompt with context blocks merged in
ContextComposedEventconversationThe history the model receives, within the input budget
CompleteConversationEventconversationEnds the turn: follow-up questions, then the stop event
NotAMetaQuestionEventself_awarenessThe message is a normal request; releases the entry steps
RecallMemoryEventmemory.recallAsks for the user and organization memories relevant to a query
MemoryRecalledEventmemory.recallOne block per memory scope
MemoryStorageRequestedEventmemory.requestStarts the memory writer's own run, without waiting for it
SearchKnowledgeEventknowledgeAsks for the referenced collections' best sections for a query
KnowledgeSearchedEventknowledgeThe block found and its sections, listed as sources
ReadAttachedFilesEventattached_fileAsks for the message's files, fitted to the room left
AttachedFilesReadEventattached_fileThe files' contents as one context block
AttachedFileEventattached_file (DisplayEvent)One file: the text the model received, its status and its citation id

Tool-loop events ​

The model deciding which tools to use, until it is done. All of them are ControlAndDisplayEvents unless noted. The loop's state travels on these events, so the history shows every iteration.

EventModulePurpose
RunToolLoopEventtool_loopAsks the loop to run a tool set, answering or gathering context
ToolLoopIterationEventtool_loopThe model's turn to decide, one per iteration
ToolCallsDecidedEventtool_loopThe tools the model chose this iteration
ToolCallApprovedEventtool_loopOne call cleared to run, after any approval
ToolResultEventtool_loopOne call's result, shown in the tool's block in the chat
ToolLoopFinishedEventtool_loopThe reply (answering) or the gathered context (gathering)
ToolLoopStatusEventtool_loop (DisplayEvent)What the loop is doing while it gathers in the background
ToolLoopCondensedEventtool_loop (DisplayEvent)Earlier results or turns condensed to fit the model's context
ToolApprovalRequestEventtool_loopA yes/no confirmation before a tool runs (a HITL request)
ToolApprovalResponseEventtool_loopThe user's answer to it

Utility events ​

EventModulePurpose
RouterEventrouter.RouterEventLLM routing decision
LanguageEventcommon.LanguageEventLanguage detection
LimitChatHistoryEventcommon.LimitChatHistoryEventTruncated history
StandaloneQuestionCondenserEventcommonQuestion reformulation

Custom events ​

Custom events are Pydantic models. All fields require type annotations:

python
from swiss_ai_hub.core.events.control.control_event import ControlEvent

class AnalysisCompleteEvent(ControlEvent):
    summary: str
    confidence: float
    findings: list[str]
    metadata: dict | None = None

The stop event constraint

No step may depend on StopEvent or any subclass as an input parameter. When a StopEvent is emitted, the run terminates and subsequent steps are not scheduled. See the execution model for details and the correct pattern.

Built with ❤️ in Switzerland 🇨🇭