How Event Parent-Child Relationships Are Tracked in Bubus: A Deep Dive into the EventBus Tree
Bubus automatically tracks event parent-child relationships by recording event_parent_id metadata on every BaseEvent and aggregating child events through context-aware dispatch logic in the EventBus service.
The browser-use/bubus library implements an implicit event tree that requires no manual bookkeeping. When events are dispatched from within handlers, the system automatically wires parent and child relationships using context variables and metadata fields defined in the core models.
The Metadata Foundation: event_parent_id and event_children
Bubus builds the event tree on two metadata properties attached to every BaseEvent instance.
The event_parent_id Field
In bubus/models.py, the BaseEvent class defines event_parent_id as a Pydantic field:
# bubus/models.py L38-L40
event_parent_id: UUIDStr | None = Field(
default=None,
description='ID of the parent event that triggered this event'
)
This field stores the UUID of the event that caused the current event to be dispatched. When None, the event is treated as a root node in the event tree.
The event_children Property
The same file defines a computed property that aggregates all child events from every EventResult attached to the parent:
# bubus/models.py L28-L34
@property
def event_children(self) -> list['BaseEvent[Any]']:
children: list[BaseEvent[Any]] = []
for event_result in self.event_results.values():
children.extend(event_result.event_children)
return children
This property walks all handler results attached to the event and concatenates their event_children lists, providing a flat list of direct descendants.
How Parent IDs Are Assigned During Dispatch
The EventBus.dispatch() method in bubus/service.py implements the logic that automatically assigns parent IDs using a context variable.
When an event is dispatched, the system checks _current_event_context, which holds the event currently being processed:
# bubus/service.py L14-L19
if event.event_parent_id is None:
current_event = _current_event_context.get()
if current_event is not None:
event.event_parent_id = current_event.event_id
This ensures that any event dispatched from within a handler automatically inherits the handler's event ID as its parent ID, unless explicitly overridden.
Recording Child Events in Real-Time
While a handler executes, Bubus tracks child events through three context variables:
_current_event_context– the event being processed_current_handler_id_context– the unique ID of the executing handlerinside_handler_context– boolean flag indicating handler execution context
When a new event is dispatched from within a handler, the system appends it to the event_children list of the handler's EventResult:
# bubus/service.py L20-L29
current_handler_id = _current_handler_id_context.get()
if current_handler_id is not None and inside_handler_context.get():
current_event = _current_event_context.get()
if (current_event is not None and
current_handler_id in current_event.event_results and
event.event_id != current_event.event_id):
current_event.event_results[current_handler_id].event_children.append(event)
This aggregation allows the event_children property to return a complete list of descendants without requiring recursive traversal at query time.
Visualizing the Event Tree
The logging utilities in bubus/logging.py leverage the parent-child metadata to render hierarchical event views. The system builds a parent-to-children mapping using event_parent_id:
# bubus/logging.py L71-L76
parent_to_children = defaultdict(list)
for event in eventbus.event_history.values():
parent_to_children[event.event_parent_id].append(event)
This mapping enables tree-structured logging output that reflects the actual execution flow of dispatched events.
Practical Examples
Simple Parent-Child Dispatch Inside a Handler
from bubus import EventBus, BaseEvent, Field
class ParentEvent(BaseEvent):
data: str = Field(default="parent")
class ChildEvent(BaseEvent):
data: str = Field(default="child")
bus = EventBus(name="demo")
@bus.on(ParentEvent)
def parent_handler(event: ParentEvent):
# Dispatch a child event while handling the parent
bus.dispatch(ChildEvent())
# Dispatch the root event
root = bus.dispatch(ParentEvent())
# Await completion
await root
# Inspect the tree
print(f"Parent ID: {root.event_id}")
print(f"Child's recorded parent ID: {root.event_children[0].event_parent_id}")
# → Child's recorded parent ID equals root.event_id
Accessing the Full Subtree from a Parent Event
# After the above dispatch has completed
all_descendants = root.event_children # direct children
deep_descendants = root.event_children # property already aggregates grandchildren, etc.
for child in deep_descendants:
print(f"Child {child.event_type}#{child.event_id[-4:]} – parent {child.event_parent_id[-4:]}")
Using expect to Wait for a Specific Child
@bus.on(ParentEvent)
def fire_child(event: ParentEvent):
bus.dispatch(ChildEvent())
# Dispatch parent and simultaneously await the child
parent = bus.dispatch(ParentEvent())
child = await bus.expect(ChildEvent) # blocks until a ChildEvent appears
assert child.event_parent_id == parent.event_id
Summary
- Automatic Metadata: Bubus tracks parent-child relationships through the
event_parent_idfield andevent_childrenproperty on everyBaseEvent. - Context-Aware Dispatch: The
EventBus.dispatch()method inbubus/service.pyuses context variables (_current_event_context) to automatically assign parent IDs when events are dispatched from within handlers. - Real-Time Aggregation: Child events are appended to
EventResult.event_childrenduring dispatch, allowing theevent_childrenproperty to return a flat list of all descendants without recursive queries. - Visualization Support: The logging utilities in
bubus/logging.pyleverageevent_parent_idto build parent-to-children maps for hierarchical event display.
Frequently Asked Questions
Do I need to manually set event_parent_id when dispatching events?
No. Bubus automatically assigns the event_parent_id using context variables when an event is dispatched from within a handler. You only need to set it manually if you want to override the default parent relationship or create artificial tree structures.
How do I access all descendants of an event, not just direct children?
Use the event_children property on any BaseEvent instance. This property aggregates the event_children lists from every EventResult attached to the event, returning a flat list that includes all descendants dispatched during the event's processing.
What happens if an event is dispatched outside of any handler?
If an event is dispatched outside a handler context, _current_event_context.get() returns None, so the event_parent_id remains None. This marks the event as a root node in the event tree, which is the expected behavior for top-level application events.
Where is the parent-child tracking logic implemented?
The core logic resides in bubus/service.py within the EventBus.dispatch() method, which manages context variables and assigns parent IDs. The data structures are defined in bubus/models.py (BaseEvent class), and visualization helpers are located in bubus/logging.py.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →