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:
- The model and provider selected in Studio System.
- The client's
memory.model. STACKBONE_MEMORY_MODEL.- 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
stackbone.ai: configure the model provider used by memory.stackbone.database: work with the same box-owned Postgres.- Concepts → Agents and sessions: use session history for the live conversation and memory for facts that should outlive it.