Sessions #
Session persistence format and CLI behavior.
Sessions #
Conversations persist as append-only JSONL under ${XDG_STATE_HOME:-~/.local/state}/fen/sessions/<cwd-slug>/<ISO>_<id>.jsonl. Line 1 is a {:type :session :version 2 :id :timestamp :cwd} header; subsequent lines are session entries. Message entries are {:type :message :id :parent-id :timestamp :message <canonical-msg>}. Compaction entries are {:type :compaction :id :parent-id :timestamp :summary :first-kept-entry-id :tokens-before :tokens-after :guidance :trigger}. Entry IDs are in-tree UUIDv7-shaped strings generated by fen.util.id; :parent-id points to the previously appended entry when available. Older version-1 files without entry IDs still load. The cwd-slug mirrors pi-mono's --<encoded-cwd>-- shape (slashes → -, sandwiched in --).
Flags:
--continue— replay the latest session for the current cwd before the first step.--no-session— skip persistence entirely.
The JSONL backend treats transcript files as the source of truth. Listing, finding, opening existing sessions, and replay may receive an optional cooperative yield callback from the runtime. The backend yields while scanning directories and large JSONL files, and cancellation must close open file handles before unwinding. An in-process metadata cache records file size, mtime, id, title, message count, and last entry id for unchanged files. The cache is only an optimization for list/find/latest/open-existing paths; load still reads the JSONL transcript when replaying a session.
What we deliberately don't have (vs pi-mono): branching/tree navigation, fork, model_change / thinking_level_change entries. Forward-compatible: readers should ignore unknown :type values.
Compaction #
/compact [guidance] summarizes older messages in the current session and keeps recent messages verbatim. It appends a :compaction entry rather than rewriting prior message entries. On --continue, the JSONL backend finds the latest valid compaction entry and reconstructs context as a synthetic summary user message followed by messages from :first-kept-entry-id onward. If the compaction entry is malformed or points at a missing message entry, it is ignored and the full message history is replayed.
The synthetic summary is in-memory replay context and is not written as a :message entry. After compaction, future turns append normally after the :compaction entry.
Saves are wired in packages/fen/src/fen/main.fnl as a flush closure that diffs agent.messages length before/after each step call. No metatables, no on-event coupling.