Doctoring evener
On-demand forensic diagnosis of evener sessions, jobs, watches, and the session
tree — reading canonical durable state through the doctor_evener tool (the
in-process evener doctor data plane).
Core principle: You diagnose by reading evener's own folds and types through
the doctor_evener tool, never by hand-parsing JSONL. The tool imports evener's
canonical code (jobstore folds, provenance, transcript, apilog), so the
numbers they report are the numbers the runtime computed. A healthy run emits
zero findings.
HARD GATE: consult the data model, inspect through the tools, never hand-parse
Two coupled rules, both mandatory:
- Before reading any artifact, read
references/data-model.md. It is the
conceptual map an ad-hoc parser lacks — what is on disk, what each field
means, and which Go type is the source of truth. Hand-written parsers guessed
the JSONL shape wrong and returned confident zeros (0 communicate calls,
0 steering entries). Do not guess. Consult.
- Inspect through
doctor_evener, never an ad-hoc one-off parser. grep -c watch_send_pending overcounts deliveries (pending frames coalesce). grep -c delegate_send counted 5 where the real invocation count was 0 (the hits were
a tool list and an instruction). The tools exist precisely because these
reconstructions rot. Run the tool.
This mirrors evener's own agent/prompts/sections/transcripts.md rule: use the
transcript tools, do not read raw transcript files directly.
The diagnose → findings loop
- Pick / load a runbook (a markdown audit definition in
runbooks/).
- INSPECT by calling
doctor_evener with the command and selector. Pull live
state first — never hardcode session ids or thresholds.
- CLASSIFY each result: PASS-with-a-note, or a confirmed, actionable
problem.
- Emit a structured Finding per confirmed problem (see the contract below).
- Report back to the caller in natural language.
evener is the agentic loop — these are your steps, not a separate engine.
The evener doctor tools
Call the doctor_evener tool — the in-process equivalent of the evener doctor CLI, running against this session's own state root by default (no
shell, no PATH, no cwd dependence; the CLI shell-out failed with
command not found when the binary wasn't installed). Its selector argument
is the CLI's first positional: local:<id>, proj:<hash>:<id>, or a bare
<id> (searched across buckets). state_dir targets a different root.
Results are the CLI's --json struct shapes. The same commands also run as
evener doctor <cmd> for humans and scripts.
| Command |
Answers |
Key arguments |
locate <sel> |
where are this session's transcript / private API log / meta / jobs / client-mutation files? |
— |
sessions |
enumerate sessions for a batch study — id, bucket, timestamps, model(s), turn count, transcript bytes, subagent/parent linkage, outcome hint |
since DUR, bucket B |
transcript <sel> |
render the turns; how many real X calls? |
count, range, text_max, full_text |
transcript <sel> + full_text |
is one response repeating itself? Turn text and tool-result previews both render capped at 200 bytes by default; a salvaged partial response carries its repeated tool calls as text, so no tool-call metric counts them and the cap hides the whole loop. Narrow with range first — one turn can run to tens of kilobytes. |
range, text_max for a middle ground |
transcript <sel> + health |
one session's mechanical metrics in one shot — tool calls/errors by class, longest identical-call run (+ whether it's all errors), truncation warnings, steering counts, jobs by terminal reason, stale notifications, user corrections (proxy) |
— |
apilog <sel> |
summarize canonical sessions/<sid>.api.jsonl attempt identity/grouping/finality, tokens/latency, empty responses, errors, cache spikes; validate strictly decodes offset zero..EOF and reports every corrupt/malformed/oversized/unsupported record with its offset (whole-file scan, explicit diagnostics only) |
empty, errors, cache_spikes + threshold, summary, validate, recompute |
apilog <sel> + health |
one-line API-health verdict — attempts, recorded-empty count, retry-storm groups (≥3 attempts), unsettled groups, errors by class (quota/permanent/retryable) |
— |
jobs <sel> |
what jobs has this session run, and what state is job X in — status, reason, exit code, output bytes, start/end times, delegate/transcript/parent links |
job_id |
mutations <sel> |
did the user's input reach the daemon? — the journal of every client mutation the daemon accepted or rejected, plus the durable input queue, pending executions, accepted turns, and queue revision |
— |
watches <sel> |
distinct deliveries (collapsing coalescing), provenance, breaker telemetry (self-influence depth + runaway drops), and the target job's state — "why didn't my watch fire" |
watch_id, self_loops |
tree <sel> |
parent ↔ delegate/observer tree across buckets |
depth, observers |
turnids |
sweep every session under the state root for reserved turn ids minted inside the transcript's entry-index namespace — duplicate/overflow collisions |
— |
audit |
run a runbook's mechanical audit: checks across a whole session set, deduped into contract-valid Findings, plus a pattern × session-count summary and every non-mechanical CLASSIFY step surfaced as manual (never silently skipped) |
runbook NAME, sessions <sel,...> | since DUR |
plugins |
plugin-store health — registry/disk drift, marketplace health, component validity, auto-upgrade sanity. Note: its store-writability probe creates and removes one temp file in the plugin store (not session state), and it reads the default plugin store root, not the session's state root. |
— |
Flag-level detail lives in the CLI's per-subcommand --help and in the
doctor_evener tool's argument descriptions, not here.
apilog empties caveat: recorded_empty (and apilog --health's
errors_by_class) reflect the compact fields recorded at call time, not a
re-decode of the response body. Logs written before the
Responses-API-decoder fix (WS1, merged to main 2026-08-06 as 812eb5c15)
can under-record non-empty responses as empty; call doctor_evener with
command apilog and recompute: true to re-extract text/tool-call counts
from the stored body for those rows (adds recomputed_txt/recomputed_tools
columns and a recomputed_nonempty total) before trusting an empty-response
verdict on pre-fix logs.
The Finding contract (in brief)
A Finding is structured JSON with: signature (a stable dedup key), severity,
category, title, description, evidence (at least the doctor_evener
call that surfaces it), and suggestedFix routing (diagnosis /
runbook / skill). Every finding is actionable or it is not emitted — no
FYI/PASS noise. Healthy ⇒ zero findings. Full schema:
references/finding-contract.md (read it before you emit).
When to pull each reference
- "What is on disk / what does this field mean?" →
references/data-model.md (ALWAYS, before reading any artifact)
- "I see weird behavior X — what is it?" →
references/failure-modes.md
- "I'm about to emit a finding" →
references/finding-contract.md
- "I'm writing or registering a runbook" →
references/writing-runbooks.md
- "I want to repair a runbook, a doctor tool, or a core skill" →
references/repair-guardrails.md (ALWAYS, before any repair)
Anti-patterns
| Don't |
Do |
| Hand-parse JSONL with grep/jq/python |
Call doctor_evener |
Read an artifact before reading data-model.md |
Consult the data model first |
Count watch_send_pending lines as deliveries |
Read distinct deliveries from doctor_evener watches |
| Read a watch with zero deliveries as broken delivery machinery |
Read the target job: line on the same row — a target already terminal, or one that produced zero output bytes, could never match the condition |
jq the client-mutation store to see whether a message arrived |
doctor_evener mutations — absence from the journal is the "it never arrived" verdict |
Treat a delegate_send text mention as a call |
doctor_evener transcript with count: delegate_send |
Read a turn that ends in … as the whole response, or conclude "no loop" from longest_identical_run: length=0 |
Both are blind to repetition inside one response: a salvaged partial response's repeated calls are text, not tool calls. Re-read the turn with doctor_evener transcript, range set to that turn and full_text: true, before ruling a loop out |
Treat any self-influenced delivery as a bug, or re-derive a loop from the Chain |
Self-influence is normal; flag only a runaway — read the recorded breaker telemetry (max_self_influence_depth, runaway_drops) via doctor_evener watches with self_loops: true |
| Emit a PASS / FYI / "looks fine" finding |
Emit only confirmed, actionable problems; healthy ⇒ zero |
| Silently apply a core-skill or doctor-tool repair |
Propose only, behind review + the validation gate (repair-guardrails.md) |
1---2name: doctoring-evener3description: Use when diagnosing a evener session, job, watch, or the session tree — investigating watch runaways (self-influence depth + runaway-fuse drops), dropped/coalesced deliveries, "how many times did X actually get called", stuck turns, or parent↔delegate↔observer linkage. Covers the evener doctor forensic tools, the Finding contract, runbook authoring, and graduated repair.4---56# Doctoring evener78On-demand forensic diagnosis of evener sessions, jobs, watches, and the session9tree — reading canonical durable state through the `doctor_evener` tool (the10in-process `evener doctor` data plane).1112**Core principle:** You diagnose by reading evener's own folds and types through13the `doctor_evener` tool, never by hand-parsing JSONL. The tool imports evener's14canonical code (`jobstore` folds, `provenance`, `transcript`, `apilog`), so the15numbers they report are the numbers the runtime computed. **A healthy run emits16zero findings.**1718## HARD GATE: consult the data model, inspect through the tools, never hand-parse1920Two coupled rules, both mandatory:21221. **Before reading any artifact, read `references/data-model.md`.** It is the23 conceptual map an ad-hoc parser lacks — what is on disk, what each field24 means, and which Go type is the source of truth. Hand-written parsers guessed25 the JSONL shape wrong and returned confident zeros (`0 communicate calls`,26 `0 steering entries`). Do not guess. Consult.272. **Inspect through `doctor_evener`, never an ad-hoc one-off parser.** `grep -c28 watch_send_pending` overcounts deliveries (pending frames coalesce). `grep -c29 delegate_send` counted 5 where the real invocation count was 0 (the hits were30 a tool list and an instruction). The tools exist precisely because these31 reconstructions rot. Run the tool.3233This mirrors evener's own `agent/prompts/sections/transcripts.md` rule: use the34transcript tools, do not read raw transcript files directly.3536## The diagnose → findings loop37381. **Pick / load a runbook** (a markdown audit definition in `runbooks/`).392. **INSPECT** by calling `doctor_evener` with the command and selector. Pull live40 state first — never hardcode session ids or thresholds.413. **CLASSIFY** each result: PASS-with-a-note, or a confirmed, actionable42 problem.434. **Emit** a structured Finding per confirmed problem (see the contract below).445. **Report** back to the caller in natural language.4546evener *is* the agentic loop — these are your steps, not a separate engine.4748## The evener doctor tools4950Call the `doctor_evener` tool — the in-process equivalent of the `evener51doctor` CLI, running against this session's own state root by default (no52shell, no PATH, no cwd dependence; the CLI shell-out failed with53`command not found` when the binary wasn't installed). Its `selector` argument54is the CLI's first positional: `local:<id>`, `proj:<hash>:<id>`, or a bare55`<id>` (searched across buckets). `state_dir` targets a different root.56Results are the CLI's `--json` struct shapes. The same commands also run as57`evener doctor <cmd>` for humans and scripts.5859| Command | Answers | Key arguments |60|---|---|---|61| `locate <sel>` | where are this session's transcript / private API log / meta / jobs / client-mutation files? | — |62| `sessions` | enumerate sessions for a batch study — id, bucket, timestamps, model(s), turn count, transcript bytes, subagent/parent linkage, outcome hint | `since DUR`, `bucket B` |63| `transcript <sel>` | render the turns; **how many real `X` calls?** | `count`, `range`, `text_max`, `full_text` |64| `transcript <sel>` + `full_text` | **is one response repeating itself?** Turn text and tool-result previews both render capped at 200 bytes by default; a salvaged partial response carries its repeated tool calls as *text*, so no tool-call metric counts them and the cap hides the whole loop. Narrow with `range` first — one turn can run to tens of kilobytes. | `range`, `text_max` for a middle ground |65| `transcript <sel>` + `health` | one session's mechanical metrics in one shot — tool calls/errors by class, longest identical-call run (+ whether it's all errors), truncation warnings, steering counts, jobs by terminal reason, stale notifications, user corrections (proxy) | — |66| `apilog <sel>` | summarize canonical `sessions/<sid>.api.jsonl` attempt identity/grouping/finality, tokens/latency, **empty responses, errors, cache spikes**; `validate` strictly decodes offset zero..EOF and reports every corrupt/malformed/oversized/unsupported record with its offset (whole-file scan, explicit diagnostics only) | `empty`, `errors`, `cache_spikes` + `threshold`, `summary`, `validate`, `recompute` |67| `apilog <sel>` + `health` | one-line API-health verdict — attempts, recorded-empty count, retry-storm groups (≥3 attempts), unsettled groups, errors by class (quota/permanent/retryable) | — |68| `jobs <sel>` | **what jobs has this session run**, and **what state is job X in** — status, reason, exit code, output bytes, start/end times, delegate/transcript/parent links | `job_id` |69| `mutations <sel>` | **did the user's input reach the daemon?** — the journal of every client mutation the daemon accepted or rejected, plus the durable input queue, pending executions, accepted turns, and queue revision | — |70| `watches <sel>` | distinct deliveries (collapsing coalescing), provenance, **breaker telemetry (self-influence depth + runaway drops)**, and the **target job's state** — "why didn't my watch fire" | `watch_id`, `self_loops` |71| `tree <sel>` | parent ↔ delegate/observer tree across buckets | `depth`, `observers` |72| `turnids` | sweep every session under the state root for reserved turn ids minted inside the transcript's entry-index namespace — duplicate/overflow collisions | — |73| `audit` | run a runbook's mechanical `audit:` checks across a whole session set, deduped into contract-valid Findings, plus a pattern × session-count summary and every non-mechanical CLASSIFY step surfaced as `manual` (never silently skipped) | `runbook NAME`, `sessions <sel,...>` \| `since DUR` |74| `plugins` | plugin-store health — registry/disk drift, marketplace health, component validity, auto-upgrade sanity. Note: its store-writability probe creates and removes one temp file in the plugin store (not session state), and it reads the default plugin store root, not the session's state root. | — |7576Flag-level detail lives in the CLI's per-subcommand `--help` and in the77`doctor_evener` tool's argument descriptions, not here.7879**apilog empties caveat:** `recorded_empty` (and `apilog --health`'s80`errors_by_class`) reflect the compact fields recorded at call time, not a81re-decode of the response body. Logs written before the82Responses-API-decoder fix (WS1, merged to main 2026-08-06 as `812eb5c15`)83can under-record non-empty responses as empty; call `doctor_evener` with84command `apilog` and `recompute: true` to re-extract text/tool-call counts85from the stored body for those rows (adds `recomputed_txt`/`recomputed_tools`86columns and a `recomputed_nonempty` total) before trusting an empty-response87verdict on pre-fix logs.8889## The Finding contract (in brief)9091A Finding is structured JSON with: `signature` (a stable dedup key), `severity`,92`category`, `title`, `description`, `evidence` (at least the `doctor_evener`93call that surfaces it), and `suggestedFix` routing (`diagnosis` /94`runbook` / `skill`). **Every finding is actionable or it is not emitted** — no95FYI/PASS noise. **Healthy ⇒ zero findings.** Full schema:96`references/finding-contract.md` (read it before you emit).9798## When to pull each reference99100- "What is on disk / what does this field mean?" → **`references/data-model.md`** (ALWAYS, before reading any artifact)101- "I see weird behavior X — what is it?" → `references/failure-modes.md`102- "I'm about to emit a finding" → `references/finding-contract.md`103- "I'm writing or registering a runbook" → `references/writing-runbooks.md`104- "I want to repair a runbook, a doctor tool, or a core skill" → **`references/repair-guardrails.md`** (ALWAYS, before any repair)105106## Anti-patterns107108| Don't | Do |109|---|---|110| Hand-parse JSONL with grep/jq/python | Call `doctor_evener` |111| Read an artifact before reading `data-model.md` | Consult the data model first |112| Count `watch_send_pending` lines as deliveries | Read distinct deliveries from `doctor_evener` `watches` |113| Read a watch with zero deliveries as broken delivery machinery | Read the `target job:` line on the same row — a target already terminal, or one that produced zero output bytes, could never match the condition |114| `jq` the client-mutation store to see whether a message arrived | `doctor_evener` `mutations` — absence from the journal is the "it never arrived" verdict |115| Treat a `delegate_send` text mention as a call | `doctor_evener` transcript with `count: delegate_send` |116| Read a turn that ends in `…` as the whole response, or conclude "no loop" from `longest_identical_run: length=0` | Both are blind to repetition *inside* one response: a salvaged partial response's repeated calls are text, not tool calls. Re-read the turn with `doctor_evener` `transcript`, `range` set to that turn and `full_text: true`, before ruling a loop out |117| Treat any self-influenced delivery as a bug, or re-derive a loop from the `Chain` | Self-influence is normal; flag only a runaway — read the recorded breaker telemetry (`max_self_influence_depth`, `runaway_drops`) via `doctor_evener` `watches` with `self_loops: true` |118| Emit a PASS / FYI / "looks fine" finding | Emit only confirmed, actionable problems; healthy ⇒ zero |119| Silently apply a core-skill or doctor-tool repair | Propose only, behind review + the validation gate (`repair-guardrails.md`) |