Skip to main content

Overview

Droidrun provides real-time event streaming that gives you visibility into agent execution as it happens. This allows you to build UIs, logging systems, or monitoring tools that react to agent actions in real-time. Under the hood, Droidrun uses llama-index workflows - an event-driven orchestration system that powers the agent architecture.

Basic Usage

from droidrun.agent.droid import DroidAgent

# Create and run agent
agent = DroidAgent(goal="Open Gmail and check inbox", config=config)
handler = agent.run()

# Stream events in real-time
async for event in handler.stream_events():
    if isinstance(event, ManagerPlanDetailsEvent):
        print(f"📋 Plan: {event.plan}")
        print(f"🎯 Current subgoal: {event.current_subgoal}")

    elif isinstance(event, ExecutorActionEvent):
        print(f"⚡ Action: {event.description}")
        print(f"💭 Thought: {event.thought}")

    elif isinstance(event, ScreenshotEvent):
        save_screenshot(event.screenshot, f"step_{event.step}.png")

    elif isinstance(event, CodeGenerationEvent):
        print(f"🐍 Generated code (step {event.step_number}):")
        print(event.code)

# Wait for final result
result = await handler
print(f"✅ Success: {result.success}")
print(f"📝 Reason: {result.reason}")

Event Types

Planning Events

ManagerContextEvent - Emitted after Manager prepares context:
class ManagerContextEvent(Event):
    """Signals that Manager has prepared context and is ready for LLM call"""
    pass  # No fields - this is just a signal event
ManagerResponseEvent - Emitted after Manager receives LLM response:
class ManagerResponseEvent(Event):
    output_planning: str  # Raw validated LLM response from Manager
ManagerPlanDetailsEvent - Emitted when Manager creates/updates a plan:
class ManagerPlanDetailsEvent(Event):
    plan: str              # Full task plan with subgoals
    current_subgoal: str   # Current subgoal being executed
    thought: str           # Manager's reasoning
    manager_answer: str    # Direct answer (if task is complete)
    memory_update: str     # Memory additions from this response
    success: bool | None   # True/False if complete, None if in progress
    full_response: str     # Complete LLM response from Manager

Execution Events

ExecutorContextEvent - Emitted after Executor prepares context:
class ExecutorContextEvent(Event):
    messages: list  # ChatMessage list for LLM
    subgoal: str    # Current subgoal being executed
ExecutorResponseEvent - Emitted after Executor receives LLM response:
class ExecutorResponseEvent(Event):
    response_text: str  # Raw LLM response from Executor
ExecutorActionEvent - Emitted when Executor selects an action:
class ExecutorActionEvent(Event):
    action_json: str    # JSON representation of selected action
    thought: str        # Executor's reasoning
    description: str    # Human-readable action description
    full_response: str  # Complete LLM response from Executor
ExecutorActionResultEvent - Emitted after Executor action execution:
class ExecutorActionResultEvent(Event):
    action: Dict        # The executed action
    outcome: bool       # Whether action succeeded
    error: str          # Error message (if failed)
    summary: str        # Human-readable summary of result
    thought: str        # Executor's reasoning
    action_json: str    # Raw action JSON
    full_response: str  # Complete LLM response from Executor
CodeGenerationEvent - Emitted when CodeAct generates code:
class CodeGenerationEvent(Event):
    code: str          # Generated Python code
    step_number: int   # Current step in execution
CodeExecutionResultEvent - Emitted after code execution:
class CodeExecutionResultEvent(Event):
    success: bool    # Whether execution succeeded
    output: str      # Execution output or error message

Visual Events

ScreenshotEvent - Emitted when a screenshot is captured:
class ScreenshotEvent(Event):
    screenshot: bytes  # PNG image data
    step: int         # Step number
AccessibilityTreeEvent - Emitted when UI tree is captured:
class AccessibilityTreeEvent(Event):
    tree: str         # Accessibility tree dump
    step: int         # Step number

Common Patterns

Building a Live UI

async def run_with_ui(goal: str):
    agent = DroidAgent(goal=goal, config=config)
    handler = agent.run()

    async for event in handler.stream_events():
        if isinstance(event, ManagerPlanDetailsEvent):
            ui.update_plan(event.plan)
            ui.update_current_step(event.current_subgoal)

        elif isinstance(event, ExecutorActionEvent):
            ui.add_action_log(event.description, event.thought)

        elif isinstance(event, ScreenshotEvent):
            ui.update_screenshot(event.screenshot)

    result = await handler
    ui.show_completion(result.success, result.reason)

Logging and Monitoring

import logging

logger = logging.getLogger("droidrun.monitor")

async def monitor_execution(goal: str):
    agent = DroidAgent(goal=goal, config=config)
    handler = agent.run()

    start_time = time.time()
    action_count = 0

    async for event in handler.stream_events():
        if isinstance(event, ExecutorActionEvent):
            action_count += 1
            logger.info(f"Action {action_count}: {event.description}")

        elif isinstance(event, CodeExecutionResultEvent):
            if not event.success:
                logger.error(f"Code execution failed: {event.output}")

    result = await handler
    duration = time.time() - start_time

    logger.info(f"Task completed in {duration:.2f}s with {action_count} actions")
    logger.info(f"Result: {result.success} - {result.reason}")

Notes

  • Events are streamed in real-time as the agent executes
  • Not all events are emitted in every execution (depends on mode and actions)
  • Reasoning mode emits ManagerContextEvent, ManagerResponseEvent, ManagerPlanDetailsEvent, ExecutorContextEvent, ExecutorResponseEvent, ExecutorActionEvent, and ExecutorActionResultEvent
  • Direct mode emits CodeGenerationEvent and CodeExecutionResultEvent
  • All events are Pydantic models with full type safety
  • The handler object is async - always use await handler to get the final result

Learn More

I