How Event Path Tracking Works Across Multiple Bubus Buses

Bubus automatically records every EventBus an event travels through in the event_path list, enabling hierarchical routing and preventing infinite forwarding loops across interconnected buses.

The browser-use/bubus library implements a robust mechanism for tracking event propagation across multiple EventBus instances. By maintaining an event_path field on every event model, bubus creates an audit trail that shows exactly which buses have processed an event. This event path tracking is essential for building complex hierarchical event-driven architectures while avoiding circular dependencies.

The event_path Field in BaseEvent

In bubus/models.py, the BaseEvent model defines the path tracking infrastructure at line 237:

event_path: list[PythonIdentifierStr] = Field(
    default_factory=list,
    description='Path tracking for event routing'
)

This field starts as an empty list for new events and accumulates bus names as the event propagates. The model also provides an enhanced __str__ representation (lines 71-76) that displays the path for debugging purposes, excluding the origin function name to focus on bus traversal.

Updating the Path During Dispatch

When an event moves between buses, the EventBus.dispatch method in bubus/service.py (lines 530-540) updates the tracking information:

if self.name not in event.event_path:
    event.event_path.append(self.name)
else:
    logger.debug(
        f'⚠️ {self}.dispatch({event.event_type}) - Bus already in path, '
        f'not adding again. Path: {event.event_path}'
    )

First-time visits append the current bus name to the list. Repeated visits are ignored to prevent duplicate entries. The same BaseEvent instance travels through the entire chain, so the path accumulates cumulatively without object copying.

Loop Prevention via Path Inspection

To stop events from circulating infinitely, bubus checks the path before invoking forwarding handlers. The _would_create_loop logic in bubus/service.py (lines 1240-1248) implements this safety mechanism:

if hasattr(handler, '__self__') and isinstance(handler.__self__, EventBus) \
        and handler.__name__ == 'dispatch':
    target_bus = handler.__self__
    if target_bus.name in event.event_path:
        logger.debug(
            f'⚠️ {self} handler {get_handler_name(handler)} '
            f'({event}) skipped to prevent infinite forwarding loop '
            f'with {target_bus.name}'
        )
        return True   # skip the handler

If the target bus name already exists in event.event_path, the handler is skipped and a debug warning is emitted. This check runs automatically when one bus forwards events to another via handler registration.

Practical Examples

Simple Three-Level Hierarchy

Consider a parent-child bus relationship as tested in tests/test_eventbus.py:

parent_bus = EventBus(name='ParentBus')
child_bus = EventBus(name='ChildBus')
subchild_bus = EventBus(name='SubchildBus')

# Forwarding chain

child_bus.on('*', parent_bus.dispatch)
subchild_bus.on('*', child_bus.dispatch)

event = UserActionEvent(action='bubble_test', user_id='test')
subchild_bus.dispatch(event)

# Results in event.event_path == ['SubchildBus', 'ChildBus', 'ParentBus']

The event originates at SubchildBus, travels through ChildBus, and finally reaches ParentBus, with each step recorded in the path (see line 45 of the test file).

Circular Subscription Prevention

When buses forward to each other in a cycle, the path prevents infinite loops (see tests/test_eventbus.py, lines 26-30):

peer1 = EventBus(name='Peer1')
peer2 = EventBus(name='Peer2')
peer3 = EventBus(name='Peer3')

# Create a loop: Peer1 -> Peer2 -> Peer3 -> Peer1

peer1.on('*', peer2.dispatch)
peer2.on('*', peer3.dispatch)
peer3.on('*', peer1.dispatch)

When peer1 dispatches an event, the path becomes ['Peer1', 'Peer2', 'Peer3']. When peer3 attempts to forward back to peer1, _would_create_loop detects that 'Peer1' already exists in the path and halts the propagation, preventing the infinite cycle.

Accessing the Path in Application Code

User handlers can inspect the event history through the public event_path attribute:

async def my_handler(event: BaseEvent):
    print(event.event_path)  # ['Subbus', 'ParentBus']

    print(event)             # String representation includes path

Because the path is a standard Python list, you can check the origin, count hops, or make routing decisions based on previous processing steps.

Summary

  • event_path is a mutable list defined on BaseEvent in bubus/models.py (line 237) that tracks bus traversal history.
  • Dispatch augmentation in bubus/service.py (lines 530-540) appends bus names cumulatively as events propagate.
  • Loop prevention occurs via _would_create_loop (lines 1240-1248), which skips forwarding handlers when the target bus already appears in the path.
  • Hierarchical routing becomes traceable and debuggable through the path string representation and logging integration.
  • Circular dependencies are automatically detected and blocked without explicit user intervention.

Frequently Asked Questions

How does bubus prevent infinite loops when buses forward to each other?

Bubus checks event.event_path before executing forwarding handlers. If the target bus name already exists in the path list, the dispatch is skipped and a debug warning is logged. This automatic check in _would_create_loop (lines 1240-1248 of bubus/service.py) ensures cyclic bus relationships don't cause stack overflow or infinite processing.

Can I modify the event_path manually in my event handlers?

Yes, because event_path is a standard Python list attached to the event instance, you can inspect or modify it in your handlers. However, manual modifications are generally unnecessary since EventBus.dispatch automatically appends bus names, and the loop prevention logic depends on accurate path data.

What information does the event_path actually store?

The path stores bus names as strings (validated as PythonIdentifierStr), not bus objects or handler references. Each time an event enters a new bus via dispatch, that bus's name appends to the list if not already present, creating a chronological record of the event's journey through your bus hierarchy.

Does event path tracking impact performance?

The overhead is minimal because Bubus mutates the existing list in-place rather than copying event objects. The check for existing bus names uses simple list containment testing, and the loop prevention check only runs when forwarding between buses via handler registration, making it efficient for most use cases.

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 →