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 handler
  • inside_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_id field and event_children property on every BaseEvent.
  • Context-Aware Dispatch: The EventBus.dispatch() method in bubus/service.py uses 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_children during dispatch, allowing the event_children property to return a flat list of all descendants without recursive queries.
  • Visualization Support: The logging utilities in bubus/logging.py leverage event_parent_id to 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →