LanternDocs

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.

In plain termsA session is a conversation — the back-and-forth kind, where the agent remembers what you said two messages ago. Memory is what survives after the conversation ends: instead of storing every word forever, Lantern periodically distills transcripts into durable facts ("prefers morning meetings", "works at Acme") and quietly brings those facts into future conversations. Talk to the agent next month and it still knows you.

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_abc123

Token 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

Flag-gated, default OFF. Set 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 topic and content

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

MethodPathDescription
GET/v1/memory/distillatesList active (non-superseded) distillates for the tenant. Returns { enabled: false } when flag is off.
POST/v1/memory/distillTrigger a distillation pass now. Returns { found: N } where N is the number of items persisted.

Environment variables

VariableDefaultPurpose
LANTERN_MEMORY_DISTILLoff1 / true / on enables the distillation loop and endpoints.
LANTERN_MEMORY_DISTILL_INTERVAL6hGo 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.

Related: Token streaming — the event contract for session turns. API reference — full session and memory endpoint shapes.