Skip to content

CLI reference

hansard replay <session_dir>    # ordered transcript, attribution inline
hansard verify <session_dir>    # integrity check: seq gaps, missing footers, unresolved refs
hansard inspect <session_dir>   # one-screen summary + attribution method breakdown
hansard prune <dir> --older-than 30d [--yes]

replay, verify, and inspect are read-only — none of them ever writes to, modifies, or deletes a session directory. prune is the one destructive command in the tool.

replay, verify, and inspect all accept --json (machine-readable output), --no-color (disable ANSI colour), and --ascii (force ASCII box/glyph characters instead of Unicode). prune accepts --json and --no-color too, but not --ascii: its output is plain text with no Unicode box-drawing or glyph characters to begin with, so there is nothing for the flag to do — adding it would be surface area with no behaviour behind it. All output below is real, captured against the session produced by the Quickstart example.

hansard replay

$ hansard replay ./sessions/s_01KZVVGRNR3D5V708Z4NPGG11H
session s_01KZVVGRNR3D5V708Z4NPGG11H | corrected view | 1 person | 1 writer | less than a second

20:44:44.090  priya    restart the payments worker

20:44:44.090  - turn begins
              agent saw: priya's message

20:44:44.090  * restart_worker(service="payments")
              `- caused by priya | recorded by the agent | 1.0

20:44:44.090  * output: Restarted the payments worker.
              `- caused by priya | recorded by the agent | 1.0

20:44:44.090  - turn ends (ok)

--user USER_ID filters to only events involving one person. --raw skips apply_corrections and shows the log exactly as originally written — see The log format for why that distinction matters. --html renders a self-contained HTML file instead — one page, no external requests, with collapsible turns and a user filter — for sharing a transcript with someone who wasn't there.

hansard verify

$ hansard verify ./sessions/s_01KZVVGRNR3D5V708Z4NPGG11H
hansard verify: sessions\s_01KZVVGRNR3D5V708Z4NPGG11H: clean (1 segment checked, no seq gaps or duplicates, 1/1 writer.end footers present, 3/3 references resolved)

A clean run states what was checked, not just that nothing went wrong — printing nothing would be indistinguishable from a no-op. verify also catches what read_session alone would abort on: it checks every segment independently, so one corrupt segment degrades the report instead of blanking it, and it re-derives seq gap/duplicate detection itself, since nothing beneath it does that check.

hansard inspect

$ hansard inspect ./sessions/s_01KZVVGRNR3D5V708Z4NPGG11H
session s_01KZVVGRNR3D5V708Z4NPGG11H
view: corrected
1 person | 1 writer | 7 events | less than a second
participants: priya

event counts:
  agent.action: 1
  agent.output: 1
  message: 1
  turn.end: 1
  turn.start: 1
  writer.end: 1
  writer.start: 1

attribution profile:
  2 attributed actions, average confidence 1.00
  recorded by the agent (explicit): 2

The attribution profile doubles as a diagnostic for the integrator: a session where most attributed actions land on temporal (the engine had to guess from timing) rather than explicit/turn_context (the agent recorded a real cause) means the host application isn't passing caused_by/context_message_idsinspect says so directly, as a printed nudge, once there's a large enough sample to generalize from.

With --json:

$ hansard inspect ./sessions/s_01KZVVGRNR3D5V708Z4NPGG11H --json
{
  "hansard_inspect_version": 1,
  "session_dir": "sessions\\s_01KZVVGRNR3D5V708Z4NPGG11H",
  "sid": "s_01KZVVGRNR3D5V708Z4NPGG11H",
  "view": "corrected",
  "participants": ["priya"],
  "writers": ["w_01KZVVGRNRKM87Z6X6TXYQR7EX"],
  "event_counts": {
    "writer.start": 1,
    "message": 1,
    "turn.start": 1,
    "agent.action": 1,
    "agent.output": 1,
    "turn.end": 1,
    "writer.end": 1
  },
  "total_events": 7,
  "duration_seconds": 0.000577,
  "attributable_count": 2,
  "method_counts": { "explicit": 2 },
  "average_confidence": 1.0,
  "nudge": null
}

hansard prune

The one destructive command — dry-run by default. It prints what it would delete without touching anything; --yes performs the deletion. Exit code is 0 if every session could be classified, 1 if any session was unreadable and therefore left out (dry-run or not). See Redaction and retention for the full reasoning.

$ hansard prune ./sessions --older-than 30d
hansard prune: DRY RUN -- nothing has been deleted. Pass --yes to actually delete.
hansard prune: sessions (older than 30d)
nothing eligible for deletion
kept, not old enough (1):
  - s_01KZVVGRNR3D5V708Z4NPGG11H  last activity 2026-08-12T20:44:44.090538Z (2.4m old)
hansard prune: DRY RUN -- nothing has been deleted. Pass --yes to actually delete.

Lowering the threshold below the session's actual age flips the same session into the deletion list — still a dry run, still nothing touched:

$ hansard prune ./sessions --older-than 1m
hansard prune: DRY RUN -- nothing has been deleted. Pass --yes to actually delete.
hansard prune: sessions (older than 1m)
would delete (1):
  - s_01KZVVGRNR3D5V708Z4NPGG11H  last activity 2026-08-12T20:44:44.090538Z (2.2m old)  [would delete]
hansard prune: DRY RUN -- nothing has been deleted. Pass --yes to actually delete.

Age is computed from the session's own last recorded event timestamp, never filesystem mtime — a restored backup has fresh mtimes and stale contents, and mtime-based pruning would silently keep the wrong sessions. --older-than accepts an integer followed by d/h/m (days/hours/ minutes) — 30d, 24h, 90m — and rejects anything else, including a bare 0, so a typo can never silently become "delete everything."