The Atoms graph is a DAG (Directed Acyclic Graph). Events can flow through multiple branches, but never in circles.
How Graphs Work
When you callsession.add_edge(parent, child), you’re creating a connection. Events emitted by the parent via send_event() are automatically queued for the child.
- Root: Entry point—receives events from the WebSocket
- Sink: Exit point—sends events back to the WebSocket
Building a Graph
Step 1: Add Nodes
session.add_node(node): Registers a Node instance with the session. The node must inherit from the base Node class.Step 2: Connect with Edges
The Resulting Graph
Graph Patterns
Automatic Connections
Nodes without explicit parents connect to Root. Nodes without explicit children connect to Sink.Cycle Detection
Graphs must not contain cycles. The session validates this at startup:Event Flow in Detail
When a node callssend_event():
- The event is queued for each child node
- Each child’s
process_event()is called asynchronously - Children can further propagate via their own
send_event()
Each node has its own event queue. Multiple events can be queued while a node is processing, and they’ll be handled in order.
Custom Routing
For dynamic routing, don’t usesend_event()—directly queue to specific children:
Best Practices
Keep graphs shallow
Keep graphs shallow
Deeply nested graphs increase latency. Events have to hop through every node.Bad:
A -> B -> C -> D -> E (5 hops)
Good: Router -> [A, B, C, D, E] (2 hops)Use meaningful node names
Use meaningful node names
You will thank yourself when reading logs.
Avoid fan-out > 3
Avoid fan-out > 3
If a node is sending to more than 3 children, it’s usually better to have a dedicated Router node that decides where the event goes, rather than broadcasting to everyone.
Draw it first
Draw it first
Graphs can get complex. Sketching the flow on paper (or Excalidraw) before coding saves a lot of headaches.

