# Event Schema Documentation

Agents emit JSON events via stdout to communicate with the agent-preflight harness. The runner parses these and uses them for budget tracking, eject policy evaluation, and cache analytics.

## Event Format

All events are single-line JSON objects, one per line:

```json
{"type": "event_type", "field1": "value1", "field2": "value2", ...}
```

Events are logged to `events.jsonl` in the model's run directory (append-only, one JSON object per line).

## Standard Event Types

### tool_execution_start

Emitted when an agent starts calling a tool.

```json
{
  "type": "tool_execution_start",
  "toolName": "bash",
  "timestamp": "2026-03-13T12:00:00Z"
}
```

**Used by:**
- Budget tracker: increments `context["tool_calls"]`
- Eject policies: available in `context`

### tool_execution_end

Emitted when a tool call completes. **Include `usage` to track cache metrics.**

```json
{
  "type": "tool_execution_end",
  "toolName": "bash",
  "success": true,
  "result": "exit code 0",
  "timestamp": "2026-03-13T12:00:05Z",
  "usage": {
    "prompt_tokens": 2006,
    "completion_tokens": 86,
    "prompt_tokens_details": {"cached_tokens": 1920},
    "cache_read_input_tokens": 1920,
    "cache_creation_input_tokens": 86
  }
}
```

**Used by:**
- Budget tracker: increments `context["tool_calls"]`
- Cache analytics: aggregates cache metrics into `CacheStats`

### agent_start

Emitted when the agent begins execution.

```json
{
  "type": "agent_start",
  "model_id": "anthropic/claude-sonnet-4",
  "timestamp": "2026-03-13T12:00:00Z"
}
```

**Used by:**
- Status dashboard: marks run as started

### agent_end

Emitted when the agent completes (success or failure). Signals the runner to gracefully shut down the container.

```json
{
  "type": "agent_end",
  "status": "success",
  "message": "Task completed successfully",
  "output_file": "/workspace/output/result.json",
  "timestamp": "2026-03-13T12:05:00Z"
}
```

**Used by:**
- Runner: terminates container gracefully
- Status dashboard: marks run as complete

### snapshot

Emitted when the agent saves a snapshot/checkpoint.

```json
{
  "type": "snapshot",
  "file": "/workspace/output/snapshot_001.py",
  "timestamp": "2026-03-13T12:01:00Z"
}
```

**Used by:**
- Status dashboard: increments `snapshots` counter

### message_start / message_end

Emitted for multi-turn interactions (optional, informational).

```json
{
  "type": "message_start",
  "role": "user",
  "content": "Fix the bug in main.py",
  "turn": 1
}
```

### error

Emitted when an error occurs (agent-specific).

```json
{
  "type": "error",
  "error_type": "ToolExecutionError",
  "message": "bash command timed out",
  "timestamp": "2026-03-13T12:00:10Z"
}
```

**Used by:**
- Eject policies: available in `events`
- Status dashboard: can log errors

### raw

Any unparseable line is logged as type "raw".

```json
{"type": "raw", "data": "some unparseable output"}
```

## Using Events in Eject Policies

Eject policies receive all events emitted so far:

```python
from agent_preflight import EjectPolicy, EjectDecision

class MyEject(EjectPolicy):
    def check(self, *, events, context, elapsed_s, **_):
        # Count tool calls from events
        tool_calls = sum(1 for e in events if e.get("type") == "tool_execution_start")

        # Check for errors
        errors = [e for e in events if e.get("type") == "error"]
        if errors and elapsed_s > 60:
            return EjectDecision(should_eject=True, reason="errors after 1 minute")

        return EjectDecision(should_eject=False)
```

## Budget File Injection

While events are the primary communication channel, agents can also read `/workspace/output/budget.txt` for real-time cost visibility (updated every 30 seconds):

```
Budget: $0.086 remaining of $0.15 (57% left)

Recent costs:
  - $0.0012  (bash)
  - $0.0031  (read_file)

Total actions: 14  |  Avg cost/action: $0.0010
Estimated actions remaining: ~86
```

## Example: Cache-Aware Agent

```python
import json
import sys

# Call OpenRouter API
response = requests.post(
    "https://openrouter.ai/api/v1/chat/completions",
    json={"model": "anthropic/claude-sonnet-4", "messages": [...]},
    headers={"Authorization": f"Bearer {os.getenv('OPENROUTER_API_KEY')}"}
)

# Emit tool event with cache metrics
usage = response.json().get("usage", {})
print(json.dumps({
    "type": "tool_execution_end",
    "toolName": "openrouter_api",
    "success": True,
    "usage": usage,
}))
sys.stdout.flush()
```

## Debugging Events

View events in real time during a run:

```bash
tail -f runs/run-20260313-120000/model-name/events.jsonl | jq .
```

Or summarize a completed run:

```bash
cat runs/run-20260313-120000/model-name/events.jsonl | jq .type -s | uniq -c
```
