Skip to content

Sessions and memory

A session is a multi-turn conversation with an agent. The full history is passed to the model on every turn, so the agent remembers what you said two messages ago. Memory is what survives a conversation: an optional pass distills transcripts into durable facts such as “prefers morning meetings” and re-injects them into future sessions. You keep what matters without storing or replaying every word.

Sessions and memoryA session turn calls the provider chain directly with the full history; memory distillation runs separately, off by default.
CallerPOST …/messagesSession handlercontrol planeProvider chainfailoverGET …/eventsSSESubscriberSDK · consolesessions.messagesJSONB historyTimeline eventsper personPerson graph/v1/peopleDistillationPAL_MEMORY_DISTILLmemory_distillatestopic · person · fact
Swipe sideways to pan.

1. A message is posted

The caller sends content to the session.

  • Outside PAL
  • Control plane
  • Data stores

Session turns run in the control plane’s session handler, not the run executor, so no run row is created. Each turn calls the provider-failover chain: streaming when no tools are attached, otherwise a non-streaming tool loop of up to 5 rounds. History lives in the messages JSONB column of sessions.

POST /v1/sessions { "agentName": "my-agent" } → { "id": "sess_abc123", "status": "active" }
POST /v1/sessions/sess_abc123/messages { "content": "What can you help me with?" }
GET /v1/sessions/sess_abc123/events # SSE: the reply streams here
POST /v1/sessions/sess_abc123/stop # marks it stopped, terminates its microVM; a turn in flight still completes
DELETE /v1/sessions/sess_abc123

With the TypeScript SDK:

import { PalClient } from "@pal-runtime/sdk";
const client = new PalClient({ apiKey: "...", baseUrl: "http://localhost:8080" });
const session = await client.sessions.create({ agent: "my-agent" });
for await (const event of client.sessions.streamMessage(session.id, {
content: "Summarise the quarter.",
})) {
if (event.type === "delta") process.stdout.write(event.delta);
}
await client.sessions.delete(session.id);

The event contract is in Streaming.

When an agent declares isolation: "microvm", sessions can reuse one long-lived VM across all turns (ADR 0022). The VM is spawned on the first turn, so later messages skip the boot. It is terminated when the session is deleted or stopped, or reaped after PAL_SESSION_VM_IDLE_TTL (default 30 minutes) of inactivity.

Raw timeline events (memory_ingest.go, identity.go) and recent session transcripts accumulate. A distillation pass runs every 6 hours by default (PAL_MEMORY_DISTILL_INTERVAL, a Go duration, minimum 1 minute). It asks the LLM, through the provider-failover chain, to extract durable facts, preferences and relationship notes, one compact row per (topic, person) in memory_distillates.

An item is persisted only if the LLM returns confidence >= 0.60 and a non-empty topic and content. Any error (database, LLM, parse) results in no writes. Session content is never logged above debug level.

{
"id": "mem_abc123",
"person_id": "per_abc123",
"topic": "preferred communication style",
"content": "Prefers short, direct replies. No bullet lists.",
"confidence": 0.87,
"source_kind": "session",
"source_ref": "sess_abc123"
}

person_id and source_ref are omitted when empty.

Method Path Description
GET /v1/memory/distillates Active (non-superseded) distillates for the tenant. Returns { enabled: false } when the flag is off
POST /v1/memory/distill Run a pass now. Returns { found: N }, the number of items persisted

The memory_distillates table is row-level-security enforced, and the pass reads and writes through tenant-scoped connections. Full shapes: REST API. For how PAL uses one memory across iMessage and WhatsApp, see Your documents and memory.