# Agent Runtime

Unified execution system for all Hive agents. Every agent — single-entry or multi-entry, headless or TUI — runs through the same runtime stack.

## Topology

```
                     AgentRunner.load(agent_path)
                              |
                         AgentRunner
                     (factory + public API)
                              |
                       _setup_agent_runtime()
                              |
                        AgentRuntime
                   (lifecycle + orchestration)
                      /       |       \\
               Stream A   Stream B   Stream C    ← one per entry point
                  |           |          |
            GraphExecutor  GraphExecutor  GraphExecutor
                  |           |          |
              Node → Node → Node  (graph traversal)
```

Single-entry agents get a `"default"` entry point automatically. There is no separate code path.

## Components

| Component | File | Role |
| --- | --- | --- |
| `AgentRunner` | `runner/runner.py` | Load agents, configure tools/LLM, expose high-level API |
| `AgentRuntime` | `runtime/agent_runtime.py` | Lifecycle management, entry point routing, event bus |
| `ExecutionStream` | `runtime/execution_stream.py` | Per-entry-point execution queue, session persistence |
| `GraphExecutor` | `graph/executor.py` | Node traversal, tool dispatch, checkpointing |
| `EventBus` | `runtime/event_bus.py` | Pub/sub for execution events (streaming, I/O) |
| `SharedBufferManager` | `runtime/shared_state.py` | Cross-stream state with isolation levels |
| `OutcomeAggregator` | `runtime/outcome_aggregator.py` | Goal progress tracking across streams |
| `SessionStore` | `storage/session_store.py` | Session state persistence (`sessions/{id}/state.json`) |

## Programming Interface

### AgentRunner (high-level)

```python
from framework.runner import AgentRunner

# Load and run
runner = AgentRunner.load("exports/my_agent", model="anthropic/claude-sonnet-4-20250514")
result = await runner.run({"query": "hello"})

# Resume from paused session
result = await runner.run({"query": "continue"}, session_state=saved_state)

# Lifecycle
await runner.start()                           # Start the runtime
await runner.stop()                            # Stop the runtime
exec_id = await runner.trigger("default", {})  # Non-blocking trigger
entry_points = runner.get_entry_points()       # List entry points

# Context manager
async with AgentRunner.load("exports/my_agent") as runner:
    result = await runner.run({"query": "hello"})

# Cleanup
runner.cleanup()          # Synchronous
await runner.cleanup_async()  # Asynchronous
```

### AgentRuntime (lower-level)

```python
from framework.runtime.agent_runtime import AgentRuntime, create_agent_runtime
from framework.runtime.execution_stream import EntryPointSpec

# Create runtime with entry points
runtime = create_agent_runtime(
    graph=graph,
    goal=goal,
    storage_path=Path("~/.hive/agents/my_agent"),
    entry_points=[
        EntryPointSpec(id="default", name="Default", entry_node="start", trigger_type="manual"),
    ],
    llm=llm,
    tools=tools,
    tool_executor=tool_executor,
    checkpoint_config=checkpoint_config,
)

# Lifecycle
await runtime.start()
await runtime.stop()

# Execution
exec_id = await runtime.trigger("default", {"query": "hello"})              # Non-blocking
result = await runtime.trigger_and_wait("default", {"query": "hello"})      # Blocking
result = await runtime.trigger_and_wait("default", {}, session_state=state) # Resume

# Client-facing node I/O
await runtime.inject_input(node_id="chat", content="user response")

# Events
sub_id = runtime.subscribe_to_events(
    event_types=[EventType.CLIENT_OUTPUT_DELTA],
    handler=my_handler,
)
runtime.unsubscribe_from_events(sub_id)

# Inspection
runtime.is_running           # bool
runtime.event_bus            # EventBus
runtime.state_manager        # SharedBufferManager
runtime.get_stats()          # Runtime statistics
```

## Execution Flow

1. `AgentRunner.run()` calls `AgentRuntime.trigger_and_wait()`
2. `AgentRuntime` routes to the `ExecutionStream` for the entry point
3. `ExecutionStream` creates a `GraphExecutor` and calls `execute()`
4. `GraphExecutor` traverses nodes, dispatches tools, manages checkpoints
5. `ExecutionResult` flows back up through the stack
6. `ExecutionStream` writes session state to disk

## Session Resume

All execution paths support session resume:

```python
# First run (agent pauses at a client-facing node)
result = await runner.run({"query": "start task"})
# result.paused_at = "review-node"
# result.session_state = {"memory": {...}, "paused_at": "review-node", ...}

# Resume
result = await runner.run({"input": "approved"}, session_state=result.session_state)
```

Session state flows: `AgentRunner.run()` → `AgentRuntime.trigger_and_wait()` → `ExecutionStream.execute()` → `GraphExecutor.execute()`.

Checkpoints are saved at node boundaries (`sessions/{id}/checkpoints/`) for crash recovery.

## Event Bus

The `EventBus` provides real-time execution visibility:

| Event | When |
| --- | --- |
| `NODE_STARTED` | Node begins execution |
| `NODE_COMPLETED` | Node finishes |
| `TOOL_CALL_STARTED` | Tool invocation begins |
| `TOOL_CALL_COMPLETED` | Tool invocation finishes |
| `CLIENT_OUTPUT_DELTA` | Agent streams text to user |
| `CLIENT_INPUT_REQUESTED` | Agent needs user input |
| `EXECUTION_COMPLETED` | Full execution finishes |

In headless mode, `AgentRunner` subscribes to `CLIENT_OUTPUT_DELTA` and `CLIENT_INPUT_REQUESTED` to print output and read stdin. In TUI mode, `AdenTUI` subscribes to route events to UI widgets.

## Storage Layout

```
~/.hive/agents/{agent_name}/
  sessions/
    session_YYYYMMDD_HHMMSS_{uuid}/
      state.json              # Session state (status, memory, progress)
      checkpoints/            # Node-boundary snapshots
      logs/
        summary.json          # Execution summary
        details.jsonl         # Detailed event log
        tool_logs.jsonl       # Tool call log
  runtime_logs/               # Cross-session runtime logs
```
