# Stream Log Analysis Patterns

## Log Format

Stream logs contain two interleaved formats:

### Bracketed app-level lines

```
[2026-03-24T08:36:45.445Z] [tag] key=value key=value …
```

Tags identify the event type. Key tags:

| Tag | Meaning |
|-----|---------|
| `managed-spawn` | New session started. Contains `pid`, `sessionKey`, `historyMessages`, `pluginDir`. This is the **session boundary marker**. |
| `stdin` | Prompt sent to Claude. `len` = character count, `preview` = first N chars. |
| `init` | MCP servers connected and tool count. |
| `tool-use` | Agent called a tool. `name` = tool name, `input` = JSON args. |
| `tool-result` | Tool returned. `name`, `error` (true/false), `output` (truncated). |
| `usage` | Token counts for this turn. `input`, `cache_create`, `cache_read`, `output`, `reported_window`. |
| `modelUsage` | Per-model cost breakdown JSON. Contains `costUSD`. |
| `context-overflow` | Session exceeded context window. Shows percentage and token counts. |
| `managed-append` | Response appended to session history. `sessionKey`, `historyAfter`, `responseTextLen`. |
| `unknown-type` | Unhandled stream event type. Usually `rate_limit_event`. |
| `subagent-start` / `subagent-end` | Specialist subagent dispatched/returned. |
| `agent-dispatch` / `agent-return` | Agent tool dispatch/return. |
| `heartbeat` | Long-running session keepalive. |
| `active` / `disabled` / `unchanged` | Plugin/tool status during init. |
| `expanded` | Skill or slash command expanded. |
| `browser-viewer` | VNC browser-viewer surface mounted on the chat. |

### Raw JSON lines

```json
{"type":"system","subtype":"init", ...}
{"type":"rate_limit_event", ...}
{"type":"user","message":{...}, ...}
{"type":"result","subtype":"success", ...}
```

The `result` event is the **session result**. It contains `is_error`, `duration_ms`, `num_turns`, `total_cost_usd`, `stop_reason`, `usage`, and `modelUsage`.

## Session Segmentation

Each session begins with a `[managed-spawn]` line and ends with either:
- A `result` JSON line (normal completion)
- The next `[managed-spawn]` line (if the session crashed without a result)

Extract `sessionKey` from `managed-spawn` and `session_id` from the `result` JSON.

## Conversation Identification

Three independent identifiers trace a conversation through the system:

| Identifier | Scope | Where it lives | Survives restart |
|------------|-------|-----------------|-----------------|
| `sessionKey` | Single agent process lifetime | Server memory, stream logs (`sessionKey=XXXXXXXX…`) | No |
| `sessionId` | Entire conversation (may span multiple sessionKeys) | Neo4j `Conversation` node, stream logs on resume/attribution | Yes |
| `visitorId` (public) / `userId` (admin) | Visitor or admin identity across conversations | `maxy_visitor` cookie (public), users.json (admin) | Yes |

### Finding the right session in logs

**From the admin UI**: The sessions modal and Claude info panel both display the first 8 characters of `sessionId`. Use `logs-read` with the corresponding `sessionKey` to pull the full timeline.

**From a sessionId**: The stream log lines that link `sessionId` to `sessionKey` are:
- `[session] conversation attributed: sessionId=XXXX… userId=… admin/…` (new sessions)
- `[session-resume] updated sessionId to XXXX… for session YYYY…` (resumed sessions)
- `[session] cold-resume visitor=XXXX… conversation=YYYY…` (public cold resume)

**From a visitorId**: Query Neo4j to find all conversations for a visitor, then trace the `sessionKey` into logs:
```cypher
MATCH (c:Conversation {visitorId: $visitorId, accountId: $accountId})
RETURN c.sessionId, c.sessionKey, c.createdAt
ORDER BY c.createdAt DESC
```

**Public agent sessions** use `[public-managed-spawn]` and `[public-managed-append]` tags instead of `[managed-spawn]` / `[managed-append]`. The `sessionKey` field is present in both.

## Error Classification

### Severity: ERROR

These indicate something broke and the agent could not complete the intended action.

| Pattern | How to detect | Known root cause |
|---------|--------------|-----------------|
| **MCP tool failure** | `[tool-result] name=mcp__* error=true` | Tool-specific — read the `output` for the error message |
| **Neo4j LIMIT float** | `error=true` + output contains `'50.0' is not a valid value` | LIMIT parameter passed as float instead of integer. Fix: wrap with `neo4j.int()` |
| **Bash command not found** | `error=true` + `command not found` | Missing system tool (e.g. `dig`, `nslookup` on Pi). The Pi may not have DNS utilities installed |
| **Browser CDP unreachable** | `error=true` + output contains `failed: cdp-unreachable` or `failed: websocket-unavailable` | The VNC Chromium's debug port isn't answering, or the CDP websocket wouldn't attach. Chromium isn't running or `CDP_PORT` is wrong — check `vnc-boot.log` |
| **Browser session lost** | `error=true` + output contains `failed: session-lost` | Agent ran an action (click/type/read) before `browser-navigate`; no live page is attached. Fix: navigate first |
| **Browser navigate failed** | `error=true` + output contains `failed: navigate-failed` | Navigation to the URL failed — bad URL, DNS not resolving, or the tunnel isn't running. The outcome's `detail` carries the underlying CDP error |
| **Browser timeout** | `error=true` + output contains `failed: load-timeout`, `failed: wait-timeout`, or `failed: command-timeout` | Page load, an awaited element, or a CDP command was too slow (common on Pi) |
| **Browser selector not found** | `error=true` + output contains `failed: selector-not-found` or `failed: option-not-found` | The CSS selector (or `<select>` option) matched nothing on the current page |
| **Browser evaluate failed** | `error=true` + output contains `failed: evaluate-failed` | In-page JavaScript threw — the agent's `browser-evaluate` expression, or an internal snapshot/probe script the page's JS context rejected |
| **Browser capture failed** | `error=true` + output contains `failed: pdf-failed` or `failed: screenshot-failed` | PDF or screenshot capture failed on the page |
| **Browser unsupported key** | `error=true` + output contains `failed: unsupported-key` | `browser-press-key` got a key name it can't map |
| **Tool hallucination** | `error=true` + `No such tool available` | Agent invented a tool name that doesn't exist (e.g. missing double underscore in MCP tool name) |
| **MCP validation error** | `error=true` + `Input validation error` | Agent passed wrong types or missing required fields to an MCP tool |
| **Cloudflare cert error** | `error=true` + `Could not read certificate` | Cloudflare auth certificate missing or corrupt |
| **MCP null dereference** | `error=true` + `Cannot read properties of null` | Bug in MCP server code — null check missing |

### Severity: WARNING

These indicate degraded behaviour or potential problems that didn't cause outright failure.

| Pattern | How to detect | Diagnosis |
|---------|--------------|-----------|
| **Context overflow** | `[context-overflow]` line | Session used more tokens than the model's context window. The percentage tells you how far over. Above 150% risks truncation and incoherent responses |
| **Repeated tool errors** | Same tool fails 3+ times in a session | Agent is retrying a broken tool instead of adapting. Check if it's the same error each time (systemic bug) vs different errors (exploratory) |
| **High cost session** | `total_cost_usd` > $0.50 in a single session result | Unusually expensive — check context overflow and turn count |
| **Long session** | `duration_ms` > 120000 (2 minutes) | May indicate agent looping or stuck on a task |
| **Web fetch unavailable** | Tool returns "not available in Phase 0" | Agent tried to use a capability that hasn't been implemented yet |
| **Rate limit events** | `[unknown-type] type=rate_limit_event` | API rate limit check. `status: "allowed"` is normal. Watch for non-allowed status |

### Severity: INFO (notable but not problems)

| Pattern | How to detect |
|---------|--------------|
| **Subagent dispatched** | `[subagent-start]` / `[agent-dispatch]` lines |
| **Skill expanded** | `[expanded]` line |
| **VNC browser-viewer mounted** | `[browser-viewer]` lines |
| **Session completed successfully** | `result` JSON with `is_error: false` |

## Diagnosis Heuristics

When multiple errors appear in the same session, look for causal chains:

- **navigate-failed → retry loop**: If `failed: navigate-failed` is followed by multiple attempts with different DNS resolution strategies (Bash dig, nslookup, DoH), the agent is trying to diagnose DNS itself. Root cause is usually the tunnel or DNS config; the outcome's `detail` string carries the underlying CDP error.

- **Contact-list LIMIT error → retry loop**: If `contact-list error=true` repeats 3+ times in one session, the agent is retrying with the same float parameter. This is a known Neo4j integer bug — the fix is in the MCP server, not the agent's behaviour.

- **Context overflow → degraded behaviour**: If a session has `context-overflow` above 150% AND errors after that point, the overflow likely caused the errors (truncated instructions, lost context).

- **selector-not-found loop**: If `failed: selector-not-found` repeats, the agent is querying an element that isn't on the page — it hasn't loaded yet (needs a `browser-wait` first) or the selector is wrong. The on-device tools use CSS selectors, not snapshot refs, so re-snapshotting is not the fix.

- **Timeout cluster**: Multiple `failed: load-timeout` or `failed: screenshot-failed` errors indicate Pi performance issues, not agent bugs. The Pi's limited CPU struggles to render heavy pages in the VNC Chromium.

## Report Format

Structure the report as follows:

```markdown
# Stream Log Report — [date]

## Summary
- **Time span**: [first timestamp] → [last timestamp]
- **Sessions**: [count] total, [errors] with errors, [clean] clean
- **Total cost**: $[sum of all session costs]
- **Context overflows**: [count] ([list percentages])

## Errors ([count])

### [Error category name]
- **Severity**: ERROR
- **Occurrences**: [count] across [N] sessions
- **Pattern**: [what happened]
- **Diagnosis**: [root cause if known, "investigation needed" if not]
- **Sessions affected**: [session keys or IDs]

[Repeat for each error category]

## Warnings ([count])

### [Warning category name]
- **Occurrences**: [count]
- **Pattern**: [what happened]
- **Impact**: [what this means for the user/system]

[Repeat for each warning category]

## Session Detail

### Session [N] — [sessionKey prefix]
- **Time**: [start] → [end] ([duration])
- **Turns**: [count]
- **Cost**: $[cost]
- **Result**: [success/error] — [brief outcome from result text]
- **Issues**: [list of errors/warnings in this session, or "Clean"]

[Repeat for each session]

## Recommendations

[Actionable items derived from the errors and warnings. Group by:
1. Bugs to fix (code changes needed)
2. Infrastructure issues (Pi setup, DNS, certificates)
3. Agent behaviour issues (retry loops, tool misuse)]
```

Adapt the format to the actual findings — omit empty sections. The goal is a report that a non-technical admin can scan for severity and a developer can use to prioritise fixes.
