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.
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:
- Deterministic resume. The same folder always yields the same index, so "resume the work package" needs only the folder name — no registry to consult or corrupt.
- Cheap to type, hard to forge. Thirty bits is short enough for a human transcript, and because derivation is keyed, an index is only meaningful to the server that issued it. A malformed one fails
INVALID_INDEX; two folders that collide fail loudly withCOLLISIONrather than silently sharing state. - Children get indexes too. Embedded child sessions derive their index from the parent path plus their position, so a worker can address its own session while it remains physically inside the parent's file.
What the file records
| Field group | Contents |
|---|---|
| Identity | schemaVersion, sessionIndex, workflowId, workflowVersion (captured at start, checked for drift on every call), agentId, startedAt. |
| Position | currentActivity, completedActivities, skippedActivities, status (running, completed, aborted), and activeCheckpoint — the gate that pauses navigation while a decision is outstanding. |
| Data | variables, 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. |
| Ordering | seq, a monotonic counter bumped by every mutation, and ts — together they make "which write is newer" a comparison, not a guess. |
| Delivery | contextMode (persistent or fresh) and deliveredContent, the per-agent ledger of content hashes behind reference-not-repeat delivery. |
| Structure | triggeredWorkflows (embedded child sessions), parentSession (the back-reference), and planningFolderPath, re-stamped on resume so the file stays correct if the folder moves. |
| History | The 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.