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 boundarySummaries 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.