Agent sessions are ephemeral by nature but consequential by design. The conversation that produced an architectural decision, a manuscript revision, or a role mandate is evidence — it shows not just what was concluded but how and why. Without a canonical record, that evidence exists only in a platform-specific export format, inaccessible to future agents, unsearchable, and format-locked to a single model provider.
Session inscription is the method by which a conversation becomes a first-class HAAK document: attributable, indexed, model-independent, and durable.
#Why This Method Exists
The JSONL files Claude exports are implementation artifacts, not knowledge records. They encode Claude's internal turn structure, tool call results, and metadata specific to Anthropic's platform. Reading them requires knowing that encoding. Cross-referencing them against the entity graph requires a parser written against a private schema. Resuming them requires Claude specifically, not any agent.
The inscribed markdown is none of those things. It is a document that any agent can read, any human can read, and any future export format can target. The relationship between the JSONL and the markdown is the same as the relationship between a TeX source and a PDF: one is the platform artifact, the other is the publication. The markdown is the publication.
This method defines the canonical store, the document format, and the four operations — inscribe, append, export-claude, and check — that keep the store coherent.
#The Canonical Store
Sessions are stored at:
data/sessions/<agent_id>/<session_uuid>.md
The agentid is the primary bucket. Agents registered with a role use their role slug (e.g., historian, architect). Sessions without a registered role use the agent ID claude. The sessionuuid is the UUID from the session metadata, unchanged — it is the stable identity of the session across all representations.
Two index files maintain progressive disclosure:
data/sessions/index.md— lists all agents with session counts and date rangesdata/sessions/<agent_id>/index.md— lists that agent's sessions chronologically: UUID, title, date, one-line summary
Any agent navigating data/sessions/ can orient itself from the top-level index, descend to an agent's index, and select a specific session without reading session bodies. The session body is always detail on demand.
#The Document Format
Each inscribed session is a markdown file with YAML frontmatter and a structured body.
Frontmatter fields:
| Field | Content |
|---|---|
session_id | UUID — stable identity across representations |
agent_id | Role slug or claude |
role | Human-readable role name, or null |
model | Model identifier at time of session (e.g., claude-opus-4-6) |
started | ISO 8601 timestamp of first message |
ended | ISO 8601 timestamp of last message |
messages | Integer count of messages in the session |
source | Path to the original source file used for inscription |
Body structure:
# <Agent name> · <Date>
<One-line summary of the session's primary work>
---
### <ISO timestamp> · <role>
<message content>
<details><summary>Tool: <tool_name></summary>
<tool call input and output>
</details>
---
### <ISO timestamp> · <role>
...
The H1 combines the agent's display name with the session date. The one-line summary is a human-written description of what the session accomplished — inscribe generates a placeholder; agents or the historian refine it.
Message headers use ### <timestamp> · <role> where role is user or assistant. Content follows as plain markdown. Tool calls are enclosed in <details> blocks — present in the text, hidden by default in any browser that renders HTML5, invisible in plaintext readers that don't. They are never omitted: the record is complete or it is not a record.
Messages are separated by ---. The final message in a session has no trailing separator.
#Model Independence
The inscribed markdown is not Claude-specific. It does not assume Claude's turn structure, its tool call schema, or its continuation mechanism. Claude is one source format; the export-claude operation is one export target. Both are adapters at the boundary of the canonical form.
A session inscribed from a Claude JSONL looks identical in the store to a session inscribed from a hypothetical future format. Future model integrations add an inscription adapter — a parser that maps their native format to the canonical fields. The store itself never changes.
#Data Model
The inscription has three layers, each with a distinct role and access pattern.
#Layer 1 — Round files (backing store)
Individual rounds are stored as JSON files at data/sessions/<agent_id>/<uuid>/rounds/NNN.json. Each file contains:
round— integer sequence numberstarted— ISO 8601 timestampengagement_id— nullable, mutable retroactivelyuser— the user message contentagent— array of typed blocks: thinking blocks, tooluse+toolresult pairs, assistant texttoken_count— token cost of this round
The backing store is append-only and never modified. If engagement tagging is applied retroactively, the engagement_id is written to the index, not to the round file itself. The round files are the permanent record.
#Layer 2 — Index (SQL + markdown)
Each session has a session.db SQLite database at data/sessions/<agent_id>/<uuid>/session.db containing a rounds table and an FTS5 virtual table for search:
CREATE TABLE rounds (
session_id TEXT,
round INTEGER,
started TEXT,
engagement_id TEXT, -- mutable, nullable
user_preview TEXT, -- first 120 chars of user message
agent_preview TEXT, -- first 120 chars of assistant text
tool_count INTEGER,
thinking_count INTEGER,
thinking_chars INTEGER,
token_count INTEGER,
round_file TEXT, -- path to rounds/NNN.json
PRIMARY KEY (session_id, round)
);
CREATE VIRTUAL TABLE rounds_fts USING fts5(
session_id, round, user_preview, agent_preview,
content='rounds', content_rowid='rowid'
);
A cross-session index at data/sessions/sessions.db aggregates all sessions with the same schema, enabling queries across the full archive: SELECT * FROM rounds WHERE engagement_id = 'console-work' ORDER BY started.
The human-readable companion is index.md, generated from SQL at inscription time. Its table columns are:
round | timestamp | engagement | user_preview | agent_preview | tools | thinking | tokens
The index never loses rows. It is updated as rounds are added and when engagement tags are assigned. It is never the authority for round content — round files are — but it is the authority for navigation and search.
#Layer 3 — Context construction
A query over the SQL index selects which rounds to inject at full fidelity, which to represent as index references, and which to omit entirely. This is what surgery produces (method 54). The backing store is never modified; context construction is a view over it. The same session can be materialized in multiple ways for different purposes: full fidelity for archaeology, compressed for a successor agent, engagement-filtered for a specialist handoff.
#Operations
#inscribe
Parse a source file (Claude JSONL or other model format via adapter), produce the canonical markdown, write it to data/sessions/<agentid>/<sessionuuid>.md, and update both index files.
If a file already exists at the target path, inscribe checks the session_id in the frontmatter against the source UUID. If they match, it behaves as append (add only new messages). If they differ, it errors — two different sessions must not occupy the same path.
#append
Add only messages that are new relative to the existing inscribed file, identified by timestamp. The existing document is treated as authoritative up to its last message timestamp; only messages with later timestamps are written. Frontmatter fields ended and messages are updated. Index entries are updated. The body of existing messages is not touched.
Append is idempotent: running it twice against the same source produces no change on the second run. This is the guarantee that enables automated pipelines to call append on a live session without accumulating duplicates.
#export-claude
An adapter operation in the reverse direction: read the canonical markdown, produce a Claude JSONL at the path Claude expects for --resume:
~/.claude/projects/<project-slug>/<session_uuid>.jsonl
The JSONL is regenerated from the canonical markdown. Tool call blocks in <details> sections are reconstructed to Claude's tool result format. Messages not representable in Claude's schema are annotated with a warning comment but included.
export-claude is not a backup and not a sync. It is a one-way translation for the specific purpose of resuming a session in Claude's CLI. The canonical store is the authority; the JSONL is derived from it, never the reverse.
#check
Verify round-trip integrity: inscribe from source → export-claude → diff the resulting JSONL against the original source. Report what is lost or transformed in each direction.
Check does not write to the store. It is a diagnostic. It answers: does the inscription of this session preserve everything that matters? Where adapters are lossy, check makes the loss explicit so it can be documented or the adapter improved.
Check is run against new source formats before they are added to the inscription pipeline, and periodically against existing sessions when the inscription or export adapters change.
#Idempotency Guarantee
The core invariant: inscribe(source) → export-claude → inscribe(derived JSONL) produces a document identical to the first inscription. No drift. No duplication.
This guarantee is verified by check. It holds when:
- The JSONL produced by
export-claudeis semantically equivalent to the source - The inscription adapter is deterministic (same source always produces same markdown)
- Timestamps in the source are stable (not regenerated at export time)
If any of these conditions breaks, check reports the discrepancy. The canonical store is never modified by check — discrepancies are reported, not silently resolved.
#Relationship to Other Methods and Roles
The historian owns the temporal record; session inscription is the mechanism by which agent sessions enter that record. The historian directs what gets inscribed and when; the inscription method specifies how.
The librarian integrates inscribed sessions into the entity graph — a session registered as a situation entity, its messages as belongings, its participants as involved entities. The inscribed markdown is the historian's artifact; the graph registration is the librarian's.
Agent legacy (method 24) produces the artifacts a dying agent leaves behind — board posts, autobiographies, blog briefs. Session inscription preserves the conversation itself. They are complementary: legacy is curated externalization; inscription is complete archival. A session may be inscribed without triggering legacy. Legacy always assumes an inscribed record exists.
The agent router (architecture 36) tracks which sessions are live and which are ended. Ended sessions are candidates for inscription. The router's session registry is the source of session_uuid values that appear in the store.
Sessions also record which engagements they participated in via the engagements frontmatter field — a list of {id, phaseatstart, phaseatend} entries, one per engagement the session touched. The field defaults to an empty list and is populated when engagement.md files exist in the project; it is a stub until engagement tracking infrastructure is in place. Cross-reference: engagement-state.
Session inscription was specified 2026-03-19 to establish a model-independent canonical record for agent conversations. The driving constraint: Claude JSONL is a platform artifact, not a knowledge record. The markdown store is designed to survive model transitions and be readable by any agent or human without knowledge of any platform's schema.
Methods 53 — Session Inscription — 2026 — Zachary F. Mainen / HAAK