Sessions and Resume

By default a run lives only in memory. With --checkpoint it is journaled to disk as an append-only record stream, so it can be suspended and resumed later, in a fresh process or on another machine. The journal is also the foundation for the durability guarantees the agent loop relies on.

Where it lives

internal/runstate: the record schema in record.go, the pure fold in state.go, the storage interfaces in store.go, the file backend in filestore.go, the resume guard in fingerprint.go, and locking in lock_unix.go. The transcript replay is resume_replay.go; the session ls|show|rm subcommands are in session_command.go.

A run is a record stream

A run is an ordered sequence of self-describing records. A MetaRecord is always first at sequence one. Each model turn is an AssistantRecord stored verbatim, each tool result a ToolResultRecord, each interactive follow-up a UserRecord, and the run ends with a single TerminalRecord carrying its reason. The schema is at Version = 2; a reader accepts any older version but refuses a newer one, because every version only adds records or optional fields.

Fold replays a record set into a resumable RunState with no IO (state.go:93). The state carries the committed conversation prefix, cumulative counters derived from the journal so they can never drift, the next iteration number, and any in-flight tool batch. It deliberately holds no client, credentials, or config; those are rebuilt on resume.

Metaseq 1AssistantLLM turnResulttoolResulttoolTerminalreasonFoldpure, no IORunStatemessages + counters
Records append left to right; resume folds them into `RunState`. Records are fsynced, so only the last line can ever be torn.

Suspend, crash, and resume

The ordering within a turn is deliberate: the assistant turn is journaled before any tool runs, and each tool result is journaled the instant that tool completes. That gives two semantics.

  • A clean suspend is exactly-once. The terminal record marks the boundary, and resume never repeats a tool call or a model call.
  • A crash resumes from the last recorded event, so at most one tool call is repeated: the one in flight when the process died, whose side effect completed but whose result never reached disk. On resume, completePending reuses journaled results for answered tools and re-runs only the unanswered ones.

Every append is a write followed by an fsync, and the first append also fsyncs the directory, so a new run survives a crash. Reading tolerates exactly one torn tail line, which is safe because on an append-only fsynced file only the last record can be incomplete.

The resume guard

Continuing a conversation against a changed model, prompt, or tool set can be incoherent, so a resume is refused when the configuration no longer matches. The fingerprint covers the model, a hash of the system prompt, a hash of the tool set, the thinking mode, and both budgets (fingerprint.go:21). Hashes are stored, never the prompt itself, so nothing leaks. A mismatch names exactly what changed and refuses unless --force is given.

Load-bearing decision

Prompt caching, the memory index, and the resume reminder are all appended after the fingerprint is computed, so none of them can perturb the comparison. Memory drift between suspend and resume never blocks a resume; memory is data, not configuration.

Storage and locking

Sessions live under the XDG state directory, $XDG_STATE_HOME/fisk-ai/runs or ~/.local/state/fisk-ai/runs, never the working directory, so runs do not leak into repositories. Each run is a <id>.json journal plus an <id>.lock file; the directory is 0700 and journals are 0600. On unix a per-run advisory flock held for the life of an open journal prevents two processes appending to the same run, and the kernel releases it on exit so a crash leaves no stale lock. Load and List do not lock, since they only read.

Naming

The user-facing term is “session” (session ls, --name), while the internal package and types say “run” (RunID, RunState, runstate). They are the same value.

Next

Continue to Memory for durable notes that persist across separate runs.