Sessions

Metadata and Storage

Understand Active, Segments, cumulative Summaries, Assistant drafts, and recovery

Metadata and Storage

SDK Sessions are persisted under the user-level Workspace data directory. Applications should read and write through the Session API rather than use these files as their own database.

~/.downcity/agents/<agent_id>/workspaces/<workspace_id>/sessions/<session_id>/
├── meta.json
├── instruction.md
└── messages/
    ├── active.jsonl
    ├── assistant_message.json
    └── segments/
        ├── 000000000001-000000000900.jsonl
        └── 000000000901-000000001500.jsonl

session_id is unique within one AgentWorkspace. meta.json records both agent_id and workspace_id; the physical directory is already partitioned by both IDs. Different Agents may reuse the same session_id without sharing data, and the project directory never receives .downcity.

instruction.md is an optional complete system snapshot. A Session fixes its first generated system in memory by default. Local session.snapshot() creates or overwrites the file. session.syncshot() regenerates the in-memory system and also overwrites the file when it already exists. If the file is absent when restored, the Session regenerates from the Agent's current instruction and plugins; deleting the file restores that behavior.

Active and Segments

active.jsonl contains only real SessionMessage values still in the active window after the latest Compact. It has no fixed message-count or API page-size limit. The SDK does not estimate tokens before a model call. Compact is scheduled only when the Provider's real usage.totalTokens (or inputTokens + outputTokens when total is unavailable) reaches 95% of the model's configured context_window.

During Compact, the SDK selects floor(n / 2) of the oldest User and Assistant Messages in Active. It sends only that prefix, together with the previous cumulative Summary when one exists, to one summary-model call. The newer half is not part of the summary input and remains in Active. After the Assistant has finished persisting, the selected prefix is closed as an immutable Segment named by its sequence range. The Segment stores real Messages first and one cumulative Summary footer last:

message sequence 1
...
message sequence 450
summary through sequence 450

The next Compact selects the oldest half of the then-current Active window. Its footer merges the previous Summary with that selected prefix, so the Summary remains cumulative. A Summary is not a SessionMessage, consumes no sequence, and is never stored in Active.

The next real usage after Compact validates the result: 50% of context_window or less passes; above 50% schedules another deep compact before the next step. If a Provider returns a context-length error without usage, the SDK also forces an in-memory fold and retries.

Model context

Each model request reads only:

latest Segment cumulative Summary
+ every User / Assistant Message in Active

It does not scan all old Segments. Old Segments serve historical UI, Fork, and audit. session.messages() returns all Active Messages; passing before_sequence returns the immediately preceding whole Segment.

Session progression

The Composer only reads snapshots and returns model input or a compaction plan. SessionMessages owns the actual writes for Messages, Assistant drafts, and Segments.

Summary generation must succeed before a compaction plan can be committed. Provider errors and empty summaries fail Compact without creating a Segment or changing Active history.

assistant_message.json

Before completion, a streaming Assistant atomically replaces one complete draft instead of appending every Delta to Active. On completion, the final snapshot is appended to active.jsonl and the draft is removed. Each Session has at most one streaming draft. Text, Tool, and File Parts retain the model's generation order through their own sequence values.

Recovery and consistency

Compact atomically creates the Segment before atomically replacing Active. If the process exits between those operations, the original Messages temporarily exist in both files but are not lost. Initialization uses the latest Segment end sequence to remove the overlapping Active prefix.

When a Session starts or reopens, the SDK also checks the Assistant draft. Existing text, reasoning, tool, and file Parts are retained in their original order.

Performance boundaries

  • Normal model progression reads only the latest Summary and Active, so it does not grow linearly with total history.
  • Compact triggering uses only real Provider usage and performs no pre-call token estimation.
  • Each Compact makes exactly one summary-model call for the selected prefix.
  • Loading older history parses one Segment at a time.
  • Segment files are immutable and their names are the sequence index; no manifest.json is required.
  • fork() intentionally copies full history and therefore reads every Segment; it is a lower-frequency full-history operation.

meta.json

meta.json stores lightweight list and detail fields such as session_id, agent_id, title, model label, timestamps, message count, history bytes, and timezone. Runtime model instances are never persisted. The SDK updates these summaries as Session properties and Message state change.

Archiving a Session and Compact Segments are distinct. archive() moves the entire Session into archived sessions storage; Compact only closes an old Active prefix under the current Session's segments/ directory.