Sessions & 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
Every session runs on the shared tier (inline executor). Each message turn triggers one inline run using the plain-LLM tool-use loop. 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 (cancels any in-flight turn)
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
The same durability primitives that apply to headless runs apply to sessions. Each turn's LLM call carries an idempotency key derived from sha256("runID|stepID|attempt"). The result is journaled in journal_events. If the control-plane crashes mid-turn, the recovery sweep re-drives the run; the checkCachedLLMStep cache returns the prior response so the LLM is not re-called.
SDK
const client = new LanternClient({ apiKey: "..." });
// Create
const session = await client.sessions.create({ agentName: "my-agent" });
// Send (waits for the full response)
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 subsequent messages pay only warm-path latency (~150ms) rather than a cold boot (~1.5s) per turn. The VM is terminated when the session is deleted or stopped.
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",
"topic": "preferred communication style",
"content": "Prefers short, direct replies. No bullet lists.",
"confidence": 0.87,
"personHint": "Shekhar",
"sourceKind": "session",
"createdAt": "2026-07-23T10:00:00Z",
"updatedAt": "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.