stackbone.memory

stackbone.memory keeps a small, explicit memory for each person across agent sessions. It is self-hosted: facts and call records live in the box's own Postgres under stackbone_platform, and the box uses its configured model provider to update them. There is no third-party memory service or separate memory credential.

Every operation requires agentId, the deep agent's catalog name, and an opaque userId. A voice agent can use the caller's phone number; another integration can use an account or CRM id. Agents keep separate memories for the same user. The SDK preserves userId exactly.

How an update works

At the end of a conversation, call add with the transcript. The model receives the person's current facts under short handles such as f1 and the new transcript in one request. It returns per-fact add, update, or delete operations and a short call summary. The SDK validates every operation before writing anything, then applies only those changes and adds the call record in one transaction. Unchanged facts keep their id and creation time; an empty operation list keeps every fact.

This lets the model keep unaffected facts while rewriting or dropping facts the new conversation contradicts. A person is expected to have only a few dozen facts, so there is no embedding or vector-search step.

import { stackbone } from '@stackbone/sdk';

const result = await stackbone.memory.add(
  [
    { role: 'user', content: 'I am interested in the AI master. Call me after 16:00.' },
    { role: 'assistant', content: 'I will arrange an afternoon follow-up.' },
  ],
  {
    agentId: 'support',
    userId: '+34111111111',
    call: {
      startedAt: '2026-10-26T09:00:00.000Z',
      durationSeconds: 83,
      outcome: 'scheduled',
      // Optional outside a run. Inside a workflow or agent run, the SDK fills
      // this from the ambient run context automatically.
      runId: externalRunId,
    },
  },
);

if (result.error) throw result.error;
console.log(result.data.facts);
console.log(result.data.call?.summary);

Every method returns the standard Result<T> envelope: { data, error: null } | { data: null, error: SdkError }.

add(content, request)

content can be a transcript string or an OpenAI-shaped ChatCompletionMessageParam[]. request.agentId and request.userId are required. request.call is optional metadata. request.instructions optionally supplies trusted agent-specific extraction policy (1–20,000 characters); omitted instructions use the generic person-memory policy. The bounded operation/summary protocol is the same for both. Every successful add stores one call record, using the update time as startedAt when that metadata is absent. call.runId is optional for callers outside a workflow or agent run. During a run, the SDK records the ambient run id automatically; an explicit call.runId takes precedence.

const { data, error } = await stackbone.memory.add(transcript, {
  agentId: 'support',
  userId: callerPhone,
  call: {
    startedAt,
    durationSeconds,
    outcome: 'transferred',
  },
});

On success, data.facts is the full stored list after the update and data.call is the new record. Fact content is limited to 500 characters, the resulting memory to 50 unique facts, and summaries to 600 characters. A stale update returns memory_conflict instead of overwriting a concurrent add or undoing a deleteAll.

recall(request)

Returns every fact for the person and their newest call records first. calls defaults to 5 and accepts an integer from 0 to 100. An unknown userId returns two empty arrays.

const result = await stackbone.memory.recall({
  agentId: 'support',
  userId: callerPhone,
  calls: 3,
});

if (result.error) throw result.error;

const factsForPrompt = result.data.facts.map((fact) => fact.content);
const recentCalls = result.data.calls;

deleteAll(request)

Deletes every fact and call record for one (agentId, userId) pair without touching another agent or person. deleted is the combined number of deleted fact and call rows. The box keeps a revision counter per agent and user so an already-running add cannot restore the deleted data.

deleteAll erases the person's memory facts and stored call summaries. The memory model call records its model, usage and cost, and latency in run history, but never its prompt or response content. deleteAll does not erase run history itself: for example, a transcript supplied as a workflow input remains in that workflow's run history.

const result = await stackbone.memory.deleteAll({ agentId: 'support', userId: callerPhone });
if (result.error) throw result.error;
console.log(result.data.deleted);

Remaining methods

search, get, list, update, delete, history, and endSession remain typed placeholders and return not_implemented. The live per-person API is add, recall, and deleteAll.

Configuration

Memory uses the same OpenAI-compatible provider as stackbone.ai. Pick the extractor model and provider in Studio System → Memory extraction. Your client configuration supplies a fallback when Studio has no selection:

import { createClient } from '@stackbone/sdk';

createClient({
  memory: {
    model: 'openai/gpt-5.6-luna',
    reasoningEffort: 'medium',
  },
});
Client fallback key Environment fallback Default
memory.model STACKBONE_MEMORY_MODEL openai/gpt-5.6-luna
memory.reasoningEffort STACKBONE_MEMORY_REASONING_EFFORT high

For each extraction, the model follows this order:

  1. The model and provider selected in Studio System.
  2. The client's memory.model.
  3. STACKBONE_MEMORY_MODEL.
  4. The platform default, openai/gpt-5.6-luna.

The workspace chat default does not participate. The System card shows the platform fallback; your private SDK configuration can supply a different one. A provider selected in Studio pins requests through the box proxy. Explicit client credentials send requests directly, with the provider's ordinary model ID.

The reasoning effort accepts the same values as a stackbone.ai chat request: none, minimal, low, medium, high, or xhigh. A client option takes precedence over its environment fallback.

Change extraction guidance in System

Use the Memory extraction card's prompt editor to publish guidance for the next extraction. A saved draft does not run until you publish it. Reset unpublishes the prompt and restores the built-in guidance. Blank published text and a deleted prompt also use the built-in guidance. A prompt read failure logs the fallback; a settings read failure fails the extraction visibly.

This guidance applies to the extractor across agents. Each target agent keeps its own memory.instructions policy, or the instructions passed to add, separately. Neither policy can replace the compiled JSON output protocol, inert transcript framing, fact handles, validation, limits, agent/person scope, revision checks or erasure protection. Extraction telemetry never records prompt or response content.

The box must advertise the additive memory.basic capability and have the platform migration applied. The tables are provisioned alongside the other stackbone_platform tables.

Per-agent defaults and overrides

Declare memory defaults on a deep agent:

import { defineDeepAgent } from '@stackbone/sdk/deep';

export default defineDeepAgent({
  name: 'support',
  memory: {
    identityKey: 'customerId',
    read: true,
    save: false,
    instructions: 'Remember product preferences and agreed follow-ups.',
  },
});

read and save are independent. Each defaults to false when neither code nor Studio declares it. Omitted instructions uses a generic policy for stable person facts, preferences and commitments. These settings describe the built-in memory integrations. Explicit calls to stackbone.memory.add, recall and deleteAll use their supplied scope independently of the flags.

The box exposes a settings contract for each registered deep agent, including agents whose graph cannot yet start. Studio overrides persist across restarts. Each field resolves from Studio, then the agent's current code, then the platform default. An explicit false is an override.

Method Route Behavior
GET /api/memory/settings/{agentId} Read overrides, effective value and per-field origins.
PUT /api/memory/settings/{agentId} Save one or more fields. Omitted fields stay unchanged. Requires config:write.
DELETE /api/memory/settings/{agentId}?field=read Remove one override. field accepts read, save or instructions. Requires config:write.
DELETE /api/memory/settings/{agentId} Remove all overrides. Requires config:write.

Every response contains agentId, overrides, value, origins and binding. binding is null until the first explicit identity configuration. Otherwise it contains identityKey, fixation source and fixedAt. For example, value.read is a boolean and origins.read is studio, code or default. Reset removes the stored field, so a later code change remains visible. Saves and resets record the changed field names in the activity log without recording the instruction text. Unknown agents return 404; invalid fields or values return 400.

Fixed identity key

Declare memory.identityKey in code or send identityKey to the settings PUT route. The first accepted configuration fixes the key permanently, even before memory is used. Equal repetitions succeed; a different key returns 409. Resetting settings, removing a code declaration and erasing a person's memory retain the key. Existing scoped records retain their opaque customerId meaning. New agents have no key and continue without memory until one is configured.

Text memory tools and customer identity

Supply structured identities through x-stackbone-identities JSON headers for OpenAI and Anthropic, forwardedProps.identities for AG-UI, or identities on SDK object input. A map accepts at most 32 literal field names of 1–100 characters, with non-empty opaque string values of at most 500 characters. Names are case sensitive and are not object paths. contact.email names one literal field. Preserve exact values, including whitespace and Unicode. Encode non-ASCII header characters with JSON Unicode escapes such as \u00e9.

Only the configured field supplies memory identity. Missing fields continue without memory and record the reason. A phone-shaped customerId cannot supply phoneNumber. Conflicting scalar and map copies of customerId return 400.

Text requests also supply an explicit opaque customer identity through the transport's standard field:

Transport Field
OpenAI Chat Completions user
Anthropic Messages metadata.user_id
AG-UI forwardedProps.customerId
callDeepAgent / streamDeepAgent Object input customerId

The identity must be a non-empty string of at most 500 characters. It is preserved exactly, including whitespace. Use the same value for every turn and continuation for that person. An operator login, API key or session key does not supply a customer identity. Without one, the turn continues without memory tools and the runtime logs the reason. String-form SDK calls have no customer identity.

import { callDeepAgent } from '@stackbone/sdk/workflow';

const reply = await callDeepAgent('support', {
  identities: { customerId: 'crm/person_42' },
  messages: [{ role: 'user', content: 'Please remember that I prefer email.' }],
});

Each turn resolves current settings and binds tools to the selected agent and customer. The model cannot choose either scope in tool arguments.

Effective read Effective save Tools offered
false false None
true false memory_read
false true memory_save
true true Both

The model chooses whether to invoke a tool. Text turns do not automatically recall or save. memory_read takes no arguments and returns saved facts and recent call summaries. memory_save({ content }) extracts from the supplied conversation text using the effective agent instructions and returns only { saved: true }. It may privately reconcile existing facts, but never returns them or old summaries to the conversation model. The two tool names are reserved while built-in memory is enabled.

Customer identity separates durable checkpoint history even when both flags are off. Reusing a session key for two customers opens two independent sessions. Configured keys with missing values use a separate session address. An unbound agent retains legacy scalar customerId session addresses while its memory stays disabled. Selected customerId addresses remain byte-identical. AG-UI HITL and client-tool continuations carry the same identity; detached approval and guardrail continuations recover the original identity from server-owned metadata. The selected snapshot remains authoritative through repeated pauses. Legacy detached approvals select once from their trusted scalar customerId; cold client-tool continuations without a trusted snapshot continue without memory.

Enabled tools require a current SDK descriptor with the extraTools capability. A failed required graph build, unavailable memory storage or unsupported descriptor surfaces an error instead of silently omitting memory. Stateless requests remain stateless when tools are enabled.

Memory during voice calls

Voice calls use the agent's fixed identity key and take a snapshot of its effective memory settings when the call opens. Studio changes apply to the next call. For an accepted telephone participant, the integration supplies sip.phoneNumber as the named phoneNumber identity. On an outbound call this is the customer's destination number. The trunk number and participant identity never replace it. An agent configured for email still needs an integration-provided email.

Effective read Effective save Voice behavior
false false Continue without reading or saving memory.
true false Recall this person's scoped facts and summaries before the greeting or first answer.
false true Save the heard conversation after closure, without showing old facts to the conversation model.
true true Recall before output and save after closure.

Voice agents have no built-in memory_read or memory_save tools. The call lifecycle performs these operations automatically. After the call closes, it saves committed user and assistant text once, including a verified played prefix of an interrupted reply. It omits interrupted output whose played portion cannot be verified, including a full generated fallback or an uncertain fixed phrase. Repeated close paths share the same completion, including extraction failures. The call lifecycle does not retry a failed extraction.

Browser calls can supply explicit test identities in POST /api/voice/rooms under identities. Without the selected identity, the call continues without memory and records the reason. Operator identity, session IDs and a phone-shaped customerId never stand in for phoneNumber.

Errors

Code Meaning
memory_invalid_request A required field or call metadata value is invalid.
memory_model_failed The configured model provider rejected or failed the request.
memory_invalid_model_output The model response was not valid bounded memory JSON.
memory_conflict Memory changed while the model request was in flight.
memory_persist_failed The transactional write failed.
memory_schema_missing The box has not applied the memory schema migration.
memory_unavailable A recall or erasure database operation failed.
capability_unavailable The negotiated box does not advertise memory.basic.

Invalid model output never changes the stored facts or creates a call record.

Where to go next

BUILT WITH ❤️ FROM CANADA AND SPAIN