LanternDOCS

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.

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

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_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

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

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",
  "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

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.