Session store

A session is a folder. Everything the server knows about a run — position, variables, history, children — lives in one sealed session.json inside a planning folder in your workspace. Agents identify a run with a derived six-character session_index, not a path or hand-carried token; the server holds the truth. Every piece of the design follows from making that file trustworthy, resumable, and inspectable.

The implementation is src/utils/session/; the file's exact shape is session-file.schema.json, and the user-level view of the same folder is in artifact and workspace isolation. The server needs a workspace at launch, and every authenticated call reads or writes disk.

The planning folder and the session tree A planning folder contains session.json, a .session-token seal, a README, and artifacts. The session.json is expanded to show that it holds the parent session state and, embedded inside it under triggeredWorkflows, complete child sessions of the same shape, nesting recursively. session.json canonical state .session-token HMAC seal of the JSON README.md human-readable progress artifacts plans, reviews, close-out session.json, expanded seq · ts · variables · history · activeCheckpoint · status triggeredWorkflows[0].state an embedded child session, same shape children nest recursively; the whole tree lives in this one file each child carries a parentSession back-reference
The planning folder and the session tree. Four files matter: session.json (canonical state), .session-token (its seal), the folder README.md (progress a human can read), and the artifacts the workflow produces. A work package that dispatches child workflows keeps their complete sessions embedded inside the parent file, so resuming the parent resumes the whole tree.

The session index

Tools identify a session with a six-character token like K7KJTQ rather than a path. The index is derived, not stored: the HMAC of the planning folder's resolved absolute path (keyed by the server key) is truncated to 30 bits and encoded as six RFC 4648 base32 characters (derivation.ts). That gives three properties:

What the file records

Field groupContents
IdentityschemaVersion, sessionIndex, workflowId, workflowVersion (captured at start, checked for drift on every call), agentId, startedAt.
PositioncurrentActivity, completedActivities, skippedActivities, status (running, completed, aborted), and activeCheckpoint — the gate that pauses navigation while a decision is outstanding.
Datavariables, the schema-validated bag mutated only through declared paths (checkpoint effects and recorded outputs — see state management), plus checkpointResponses, which caches each resolved checkpoint so a replayed yield returns the earlier answer instead of asking twice.
Orderingseq, a monotonic counter bumped by every mutation, and ts — together they make "which write is newer" a comparison, not a guess.
DeliverycontextMode (persistent or fresh) and deliveredContent, the per-agent ledger of content hashes behind reference-not-repeat delivery.
StructuretriggeredWorkflows (embedded child sessions), parentSession (the back-reference), and planningFolderPath, re-stamped on resume so the file stays correct if the folder moves.
HistoryThe append-only event log described next.

History: the append-only record

Every consequential moment appends a typed event: workflow lifecycle (workflow_started, workflow_completed, workflow_triggered…), activity boundaries (activity_entered, activity_exited, activity_skipped), steps, checkpoints (checkpoint_reached, checkpoint_response, checkpoint_replayed), decisions and loops, variable_set, and error — twenty-three event types in all (state.schema.ts).

Two of them do double duty as fidelity instruments: get_technique and get_resource append technique_fetched and resource_fetched events, and next_activity later cross-references those against the step manifest — a manifested step nobody actually fetched draws a warning. The history is how the server distinguishes "did the work" from "described the work".

Nested and transient sessions

When a workflow dispatches a child (the dispatch model), the child's complete session embeds under the parent's triggeredWorkflows — one file continues to hold the whole work package, which is what makes resume-by-folder-name total. A child reaching its terminal activity notifies the parent so the embedded entry flips to completed. Parent chains are walked, not trusted: nesting more than five levels deep draws a warning, since chains that long usually indicate a dispatch mistake rather than a real process.

Meta-orchestration sessions that exist only to choose a workflow start transient — created under the system temporary directory with a synthetic slug. The first dispatch_child that commits to real work promotes the session into the workspace planning root, taking its slug from the request (or a dated fallback), and the temporary folder is discarded. Nothing about a browse-only conversation ever lands in your repository.

Integrity and migration

The seal answers one question: is this file exactly what the server last wrote? Writes are atomic (temp file, then rename), the HMAC is computed over canonicalized JSON, and reads verify it with a timing-safe comparison before parsing. The goal is catching corruption, hand-edits, restored backups, and racing writers — incoherence, not an adversary-proof security boundary. One server key, cheap digests, hard failure on mismatch.

Older planning folders that predate the current layout are upgraded in place by migration.ts the first time they are touched; an upgrade that cannot complete raises MigrationError rather than leaving a half-converted folder.

Next

How a tool call reads and writes this state safely is the request lifecycle; the checkpoint pause encoded in activeCheckpoint is the checkpoint model.