# Sessions & Persistence

SQLite is the AgentOS source of truth for public session metadata, prompts, pending requests, and durable ACP events. Adapter-owned session files or databases remain private adapter state; AgentOS does not mirror them into the VFS or import adapter replay into public history.

## One SQLite descriptor per VM

VM creation supplies exactly one trusted database descriptor:

- `actor_uds` talks directly to Rivet actor SQLite through its authenticated Unix socket.
- `sqlite_file` opens one local SQLite file for the logical VM on the sidecar's bounded blocking executor.

The sidecar resolves the descriptor once and injects the same database handle into VFS metadata, VFS block storage, and the AgentOS core session store. Filesystem plugins receive only filesystem policy, not another SQLite path or UDS credential. One Rivet actor is one logical VM, so tables do not repeat a VM ID.

Each actor-UDS transaction generates a fresh UUID transaction key and attaches it to `BEGIN`, every statement, and `COMMIT` or `ROLLBACK`. Rivet owns transaction affinity and isolation. AgentOS adds no second UDS mux, pool, writer scheduler, or retry layer.

## Independent schema owners

Three owners share the physical per-VM database without sharing a migration sequence:

| Owner | Version table | Data tables |
|---|---|---|
| Native filesystem | `agentos_fs_schema_version` | `agentos_fs_*` |
| Sidecar/core durable state | `agentos_core_schema_version` | `agentos_core_*` |
| TypeScript actor hosting metadata | `agentos_actor_schema_version` | `agentos_actor_*` |

Each version table is a `STRICT` singleton table. Its owner alone validates and advances its append-only migration ladder, updating the schema and version in the same transaction. A future version fails closed. There is no shared schema-version table, global AgentOS migration sequence, or AgentOS use of `PRAGMA user_version`; Rivet-owned schema metadata remains independent. Owners do not create cross-owner foreign keys.

## Session event log

`agentos_core_events` keeps the durable envelope in scalar columns: public session ID, sequence, timestamp, negotiated ACP version, internal storage kind, correlation ID, payload byte length, and permission outcome fields. `payload_json` contains only the exact native ACP payload:

- `session_update` stores an ACP `SessionUpdate`, including its `sessionUpdate` discriminator, optional fields, and `_meta`.
- `permission_request` stores the ACP `RequestPermissionRequest` selected for public delivery.
- `permission_response` stores the ACP `RequestPermissionResponse`.

The internal `session_update` storage kind is not a public event discriminator. On read, AgentOS exposes `SessionUpdate.sessionUpdate` as the event's top-level `type` and places that ACP variant's native fields directly beside it. Permission request and response fields are flattened the same way. The public AgentOS durability envelope is reconstructed from the scalar columns; it is not duplicated inside `payload_json`, and the row is not a raw JSON-RPC frame. This keeps event data ACP-native while giving AgentOS a stable sequence, timestamp, and public request correlation.

Complete user content is committed before prompt dispatch. Agent message and thought deltas are live and ephemeral; they receive no durable sequence and are coalesced into native ACP chunk updates only at a completed-message boundary. Other ACP updates are stored unchanged. Events are published only after their SQLite transaction commits. If an adapter emitted an event that never committed, SQLite wins, and AgentOS never automatically resends a prompt whose delivery is uncertain.

## Trusted plaintext storage

The per-VM SQLite database is trusted runtime storage. Session environment values, MCP headers or credentials, prompts, message content, tool arguments and results, permission payloads, and cached adapter metadata may be stored as plaintext JSON or SQLite values so a session can restore after sleep. AgentOS does not currently encrypt, redact, or replace these values with secret references. Operators must protect database and backup access according to the sensitivity of the workload.

## Retention and default bounds

Defaults are generous, VM-configurable `limits.acp` safeguards. AgentOS warns at 80% and returns a typed error naming the setting to raise when a hard-admission limit is reached.

| Durable collection | Default | Contract |
|---|---:|---|
| Sessions | 10,000 per VM | Hard admission limit; sessions are never evicted implicitly. |
| History | 1,000,000 events and 1 GiB per session | Oldest committed events are pruned in bounded batches; an older reconnect cursor returns `history_cursor_expired`. |
| Prompt/idempotency records | 100,000 per session; 1,000,000 per VM | Oldest unreferenced terminal records are pruned to admit new prompts. An idempotency key is authoritative only while its prompt record is retained. |
| Pending `ask` permissions | 1,000 per session; 10,000 per VM | Hard admission limit with no time-based expiry; lifecycle resolution is required. |
| Terminal permission outcomes | 10,000 per session; 100,000 per VM | Oldest outcomes are pruned. A late response gets its specific terminal reason while retained and `request_not_found` after pruning. |

History counters and payload-byte totals are updated transactionally with insertion and pruning. Bootstrap reconciliation repairs counter drift from event rows. A single batch larger than the configured history event or byte bound fails atomically rather than partially committing.

Related request bounds default to 64 MiB and 16,384 blocks per prompt, 4 MiB of fallback continuation context, and 10,000 rows per session-list or history page.

## Adapter restoration

Public session IDs do not depend on adapter IDs. When no live route exists, the sidecar starts the configured adapter and tries native ACP restoration in this order:

1. `session/resume` when advertised by `sessionCapabilities.resume`.
2. Stable `session/load` when `loadSession` is advertised.
3. `session/new` plus bounded continuation context derived from recent SQLite history.

Notifications replayed by `resume` or `load` are discarded. This avoids duplicate public history and accommodates adapters whose restoration behavior differs despite ACP negotiation.

## Actor lifecycle

Merely having durable sessions does not keep the actor awake. The prompt action uses actor `keepAwake` from dispatch through the terminal SQLite commit so the VM cannot sleep halfway through a turn. Read-only session operations never start an adapter and do not hold the actor awake.

## Core tables

- `agentos_core_sessions`: scalar session state and creation options, complex creation-option JSON, cached negotiation, retained-history counters, sequence bounds, and timestamps.
- `agentos_core_events`: ordered exact ACP payloads with a scalar AgentOS envelope.
- `agentos_core_prompts`: prompt idempotency hashes and terminal result or error; accepted input is represented by durable native ACP user updates rather than duplicate prompt JSON.
- `agentos_core_permission_records`: actionable and terminal permission correlation.
- `agentos_core_permission_outcomes`: bounded terminal outcomes used to answer late responses.

The filesystem's `agentos_fs_*` and actor's `agentos_actor_*` tables share the database handle but remain independently owned.
