Sessions

Metadata and storage

Session SQLite, canonical messages, and Composer context policies

Metadata and storage

A persisted Session belongs to the Agent storage scope provided by City. Without City, the default is in-memory storage. Use the Session API instead of treating these files as an application database.

~/.downcity/agents/<agent_id>/sessions/<origin_type>/<session_id>/
├── session.db
└── attachments/

Each Session has one session.db with three stable tables:

  • session_state: identity, configuration, title, preview, and the system snapshot.
  • messages: the User/Agent Message envelope.
  • message_parts: one row per Part; each content value belongs to one Part.

The API reconstructs SessionMessage aggregates when reading. The table layout is not exposed to the UI or model providers. Canonical history is never deleted or rewritten by context compaction.

Composer and derived context

SessionComposer generates the final system, messages, and tools sent to the model. The caller passes canonical history and a namespaced derived store into every call; a Composer never opens Session storage by itself.

The default DefaultSessionComposer treats each Part as the context-processing boundary and stores checkpoints in its own composer_adaptive_part_checkpoints derived table. Its compose() returns:

explicit <session-context-summary> system block
+ canonical Parts after the summary boundary

Summaries and any other projected context are rebuildable derived data, not Messages. When a provider reports a context limit, or real usage crosses the pressure threshold, Session asks the Composer to advance its derived state through advance_context(). There is no public session.compact(), Active file, Segment file, or second message log.

Recovery and transactions

The Message envelope and its Parts commit in one SQLite transaction; Mutations publish only after commit. On startup, SessionMessages closes the non-terminal Agent Messages and running Actions selected by Storage; Storage does not interpret domain terminal states. Attachments remain under attachments/, while Parts store attachment references.

Model streaming follows one boundary: events drive live rendering, memory assembles the stream, and SQLite stores only stable semantic checkpoints. Ordinary chunks do not write SQLite; complete model steps, Tool transitions, Interactions, Actions/Errors, and Message closure do.

Legacy JSONL data is not migrated by Desktop or Runtime. Run the external one-time scripts/migrate-session-storage-to-sqlite.mjs script when migration is needed. Development databases already written with an older SQLite schema use scripts/migrate-session-sqlite-v3.mjs; Runtime only checks the version and never mutates an old database implicitly. Databases written before Interactions became part of their Tool Part still carry standalone Interaction rows and are rejected on read; run scripts/migrate-session-interaction-into-tool.mjs to move them back into the owning Tool Part.