Sessions

There is one store root: $HORON_HOME/sessions (normally ~/.horon/sessions). Every file is owner-only, every accessor re-validates the session id, and accessors reject symbolic links, non-directory components and components owned by another user. A well-formed id therefore cannot point outside the store.

What is on disk

sessions/
  <id>/
    events.jsonl             append-only canonical event log (the original)
    blobs/<sha256>.json      externalized payloads, named by their own hash
    local-head.json          explicit head selection (original)
    local-runtime.json       resume binding: working_dir, migration_epoch, provider_session_id, status
    checkpoint.json          derived cache: head, metadata, compaction, tail
    sparse-index.jsonl       derived cache: every 128th event plus message offsets
    summary.json             derived cache: one catalog row for this session
  <id>.json                  legacy snapshot (preserved, never rewritten)
  .catalog.index.jsonl       derived cache: one row per session, for listing
  .origin-id                 this HOME's writer identity

The originals of session content are events.jsonl and blobs/. Archive, compaction and rewind append records and never delete an original. Derived caches can be rebuilt, so a crash never costs content: only the caches are discarded on load when their stamp does not match.

The runtime journal holds a second copy of the resume binding. Every run.started and run.stopped row may carry binding with the same fields plus a rev, and the highest rev wins on read, so the older of the two originals is never overwritten.

Inspecting and repairing

horon session inspect <id>     # layout, heads, archive state, damage (read-only)
horon session doctor [<id>]    # validate originals without repairing
horon session migrate          # migrate legacy originals (dry run unless --apply)
horon session export <id>      # Markdown transcript; secrets redacted
horon session restore <id>     # restore an archived session

horon export <session> --out file.md writes the same transcript to a file. Tool results over 4 KiB are truncated in the export, matching the export_session_markdown harness API reply.

Runtime state

The durable journal records every accepted input, so a run that stopped mid-turn can be inspected and settled from outside:

horon runtime-state show --session <id>
horon runtime-state resolve-input --session <id> --input <id> --action retry

show lists the live inputs and which outcomes are undetermined. resolve-input takes retry (run the message again) or cancel (drop it).

Replication

Two instances replicate whole records and blobs; inbound chunks stage under .blob-staging/<sha256>/ and whole-record staging under .record-transfer/, so an interrupted transfer resumes rather than restarting. Every accessor re-checks the digest, and a mismatched value installs nothing.