Sessions and memory
Sessions are interactive, multi-turn conversations with an agent. Memory distillates are the long-term memory layer: durable facts and preferences distilled by the LLM from raw session transcripts and timeline events, stored and re-injected into future sessions.
Sessions
Session turns run in the control plane's session handler, not the run executor. Each message turn calls the provider-failover chain directly: streaming when no tools are attached, otherwise a non-streaming tool loop of up to 5 rounds. No run row is created. Sessions maintain a messages JSONB column on the sessions table; the full history is passed as context on each turn.
Lifecycle
# Create a session
POST /v1/sessions
{ "agentName": "my-agent" }
→ { "id": "sess_abc123", "status": "active" }
# Send a message (streams events on GET /v1/sessions/{id}/events)
POST /v1/sessions/sess_abc123/messages
{ "content": "What can you help me with?" }
# Stop the session (marks it stopped and terminates its microVM; a turn already in flight still completes)
POST /v1/sessions/sess_abc123/stop
# Delete
DELETE /v1/sessions/sess_abc123Token streaming
Session replies stream via GET /v1/sessions/{id}/events (SSE). The three named events — message_delta, message_completed, message_error — and the TypeScript SDK sessions.streamMessage() async iterator are covered in full in Token streaming.
Durability
Session turns are not journaled. A control-plane crash mid-turn loses that turn; resend the message. Provider requests still carry an Idempotency-Key header derived from the request body. For crash-resume from the journal, use a run (see Durable execution).
SDK
const client = new LanternClient({ apiKey: "..." });
// Create
const session = await client.sessions.create({ agent: "my-agent" });
// Send (returns immediately; the reply arrives on sessions.events())
await client.sessions.sendMessage(session.id, { content: "Hello" });
// Send + stream tokens
for await (const event of client.sessions.streamMessage(session.id, {
content: "Summarise the quarter.",
})) {
if (event.type === "delta") process.stdout.write(event.delta);
}
// Clean up
await client.sessions.delete(session.id);Session-scoped microVMs (flag-gated)
When an agent declares isolation: "microvm", sessions optionally reuse a single long-lived VM across all turns (session-scoped microVMs, ADR 0022). The VM is spawned on the first turn and kept alive between turns, so later messages skip the per-turn VM boot. The VM is terminated when the session is deleted or stopped, or reaped after LANTERN_SESSION_VM_IDLE_TTL (default 30 minutes) of inactivity.
Memory distillates
LANTERN_MEMORY_DISTILL=1 to enable. When unset, the distillation loop is a no-op and the endpoints return empty responses. Zero behavior change when the flag is off.Raw timeline events (stored by memory_ingest.go and identity.go) and recent session transcripts accumulate over time. The memory distillation pass runs every 6 hours by default (override: LANTERN_MEMORY_DISTILL_INTERVAL, Go duration string; minimum 1 minute) and uses the LLM to extract durable facts, preferences, and relationship notes — one compact row per (topic, person) in the memory_distillates table.
Quality bar
The LLM is called via the provider-failover chain (never a hardcoded vendor — invariant #6). Items are only persisted when the LLM returns:
confidence ≥ 0.60(the prompt's own stated minimum)- A non-empty
topicandcontent
Any error (DB, LLM, parse) → no writes, never a crash. PII in session content is never logged above debug level (invariant #10).
Distillate shape
{
"id": "mem_abc123",
"person_id": "per_abc123", // omitted when not tied to a person
"topic": "preferred communication style",
"content": "Prefers short, direct replies. No bullet lists.",
"confidence": 0.87,
"source_kind": "session",
"source_ref": "sess_abc123", // omitted when empty
"created_at": "2026-07-23T10:00:00Z",
"updated_at": "2026-07-23T18:00:00Z"
}Endpoints
| Method | Path | Description |
|---|---|---|
GET | /v1/memory/distillates | List active (non-superseded) distillates for the tenant. Returns { enabled: false } when flag is off. |
POST | /v1/memory/distill | Trigger a distillation pass now. Returns { found: N } where N is the number of items persisted. |
Environment variables
| Variable | Default | Purpose |
|---|---|---|
LANTERN_MEMORY_DISTILL | off | 1 / true / on enables the distillation loop and endpoints. |
LANTERN_MEMORY_DISTILL_INTERVAL | 6h | Go duration string for how often the loop runs. Minimum 1 minute. |
RLS and multi-tenancy
The memory_distillates table is RLS-enforced (migration 0013). All DB writes use db.WithTenantConn; reads use srv.WithTenant. No cross-tenant data can be read or written by the distillation pass.