***
title: Events
description: The lifeblood of an Atoms application.
---------------------------------------------------
**Events** are typed objects that flow through the graph. They represent everything that happens in the system, from a user saying "Hello" to an agent deciding to call a function.
***
## Flow
Understanding how events move is key to understanding Atoms:
1. **Transport Layer**: Receives WebSocket message → Converts to `SDKEvent`.
2. **Session**: Routes event to Root Nodes.
3. **Nodes**: Process event → Emit new events to Children.
4. **Sink**: Catches output events → Converts to WebSocket message → Sends to Client.
***
## Structure
All events inherit from `SDKEvent`, which is a Pydantic model (`TypedModel`). This ensures type safety and easy serialization.
```python
class SDKEvent(TypedModel):
id: str # unique identifier for the event
type: str # specific event type string (e.g., "agent.speak")
timestamp: datetime = Field(default_factory=datetime.now)
metadata: Dict # optional payload for custom data
```
***
## Common Events
| Event Type | Description |
| :------------------------------ | :---------------------------------- |
| `SDKSystemLLMRequestEvent` | Contains the user's message/input. |
| `SDKAgentLLMResponseChunkEvent` | A text chunk generated by an agent. |
| `SDKSystemUserJoinedEvent` | Triggered when a client connects. |
| `SDKAgentEndCallEvent` | Signal to end the session. |
***
## Custom Events
### Signaling between agents
While standard system events handle core mechanics like text and audio, you often need to signal custom business logic states—like `PaymentFailed` or `EscalationRequired`.
**Why Custom Events?** Unlike passing untyped dictionaries, defining `SDKEvent` subclasses gives you type safety, validation, and clearer intent in your code.
### Implementation
Inherit from `SDKEvent` and specify a unique type string.
```python
# 1. Define the Event
class EscalationEvent(SDKEvent, type="escalation_event"):
reason: str
severity: Optional[str] = "medium"
```
Use `self.send_event()` to broadcast the event to downstream nodes.
```python
class SupportBot(OutputAgentNode):
async def generate_response(self, messages):
# ... analysis logic ...
if "angry" in user_sentiment:
# Emit the custom event
await self.send_event(EscalationEvent(
reason="User is frustrated",
severity="high"
))
```
Override `process_event` to listen for your custom type.
```python
class SupervisorNode(Node):
async def process_event(self, event: SDKEvent):
# Check for your custom event type
if isinstance(event, EscalationEvent):
print(f"Escalation received: {event.reason}")
# Take action...
# Important: Propagate event to children!
await self.send_event(event)
```
***
## Best Practices
Because every event is a Pydantic model, you can trust `event.content` exists if `isinstance` checks pass.
```python
# Bad: Accessing dict keys blindly
if event.get("type") == "message":
print(event["content"])
# Good: Using the model
if isinstance(event, SDKSystemLLMRequestEvent):
print(event.content) # IDE autocompletes this!
```
If your node receives an event it doesn't understand, it **must** still pass it on. If you forget `send_event()`, the stream dies at your node.
```python
async def process_event(self, event):
if isinstance(event, MyType):
await handle(event)
# ALWAYS do this
await self.send_event(event)
```
When defining custom events, use a namespaced type string like `myapp.order.created` to avoid collisions with future system events.