Memory
Three kinds of memory work today. A session holds one conversation: the turns and the tool calls, kept by the box so the next message lands on the same thread. Retrieval holds your documents, chunked and embedded in the box's own Postgres, so a tool can search them by meaning and the model can answer from them. Long-term memory keeps explicit facts and recent call summaries per person in that Postgres. Studio reads sessions and retrieval live. Its Memory screen lets you inspect and erase customer memory for each agent.
One conversation, three turns. The third question ("that order") makes sense because the box remembered the first two.
What an agent remembers
| Kind | What it holds, and where |
|---|---|
| Session | The turns of one conversation, the tool state between them and any tool held for a decision. The box keeps it in its own Postgres, in a schema of its own (stackbone_checkpoints), and Studio lists it under Sessions. You open one by sending a session key with the request; the agent needs no code. See Keep the conversation. |
| Retrieval (RAG) | Your documents, split into chunks and stored with an embedding, grouped in collections in the box's Postgres (stackbone_platform.rag_*, pgvector), with the uploaded originals in its object store. A tool calls stackbone.rag.retrieve() and hands the hits to the model; you fill a collection from Studio, from code or from the CLI. See Ground answers. |
| Long-term memory | Durable facts about one person, plus recent call summaries, in stackbone_platform.memory_*. stackbone.memory.add() updates individual facts, recall() reads them, and deleteAll() erases the person's facts and calls. See Keep facts across sessions. |
Your own tables are none of these. stackbone.database is where your
business data goes (orders, tickets, users), typed by you and migrated by
you. Memory is what the runtime keeps about the conversation; retrieval is
what it keeps about your documents. The
last section compares the three stores.
Keep the conversation
A chat turn is stateless unless you say otherwise. Over the OpenAI or
Anthropic wire you replay the whole messages[] array on every call, and the
box forgets the turn as soon as it answers. Send an x-stackbone-session
header with any stable string you choose and the box keeps the conversation
on its side: the next call with the same value lands on the same thread, with
the earlier turns, the tool results and any tool it is holding for a person.
# First turn: the box opens the session and answers.
curl http://127.0.0.1:4242/openai/v1/chat/completions \
-H 'content-type: application/json' \
-H "authorization: Bearer $STACKBONE_API_KEY" \
-H 'x-stackbone-session: customer-4711' \
-d '{"model":"support","messages":[{"role":"user","content":"Where is order A-1001?"}]}'
# Second turn, same key: no need to replay the first message.
curl http://127.0.0.1:4242/openai/v1/chat/completions \
-H 'content-type: application/json' \
-H "authorization: Bearer $STACKBONE_API_KEY" \
-H 'x-stackbone-session: customer-4711' \
-d '{"model":"support","messages":[{"role":"user","content":"Can I still return it?"}]}'The AG-UI wire needs no header: its threadId is the session key, and every
non-empty threadId is durable. The Playground in Studio is an AG-UI client,
so every conversation you start there is a session, and New conversation
starts another one.
Behind the header, the box does two things. It writes the graph's checkpoint
after every turn into its own Postgres, in a schema called
stackbone_checkpoints, so a session survives a restart. It also keeps a
sessions row that Studio lists, with each turn linked to its run.
The box derives the session id from your key and the agent name, so the same
key always names the same session, and Studio can rebuild a conversation on any
machine from the key alone.
Every conversation the box keeps, for every agent, newest first.
Open one and Studio prints the transcript: each turn with the user message,
the agent reply, its tokens and duration, and a Run detail link to the
tool calls behind it. Continue in Playground reopens the same thread and
sends the next message on it, even after the session completed. From a
terminal,
stackbone runs list
reads the same turns.
Four rules:
- A held tool needs a session. A tool listed under
interruptOnparks the turn on the checkpoint until a person decides. Without a session key there is nowhere to park, so the turn cannot pause. While a decision is pending, a new message on that session answers409. - Grouping without state.
x-stackbone-conversationgroups turns into one session row in Studio but keeps the wire stateless: you still replaymessages[], and nothing can pause. Send one header or the other, not both. - The key never comes back. No wire returns the session id. Keep your key (a customer id, a ticket number) and reuse it.
- Nothing expires. Sessions and their checkpoints stay until you delete them from the box's Postgres. There is no retention window and no delete from Studio.
The wire contract, both headers and the AG-UI shape are on the agent protocol page.
Ground answers in your documents
Retrieval is a tool like any other. It embeds the question, finds the closest
chunks in a collection and hands them to the model, which answers from them.
stackbone.rag.retrieve() searches one collection, the one you name in
namespace (it defaults to default), and every hit carries its content
and a score between 0 and 1.
import { tool } from '@langchain/core/tools';
import { z } from 'zod';
import { defineDeepAgent } from '@stackbone/sdk/deep';
import { stackbone } from '@stackbone/sdk';
const searchDocs = tool(
async ({ query }: { query: string }) => {
const result = await stackbone.rag.retrieve({
text: query,
topK: 3,
namespace: 'help-center', // the collection to search
});
if (result.error) return `search_failed: ${result.error.code}`;
if (result.data.length === 0) return 'No matching article in the help center.';
return result.data.map((hit) => `[${hit.score.toFixed(2)}] ${hit.content}`).join('\n\n');
},
{
name: 'search_docs',
description: 'Search the help center before answering a question about policies.',
schema: z.object({ query: z.string() }),
},
);
export default defineDeepAgent({
name: 'support',
model: 'openai/gpt-4o-mini',
tools: [searchDocs],
});The instruction is a prompt in the catalogue, owned by this agent and keyed by its name. You write and publish it in Studio:
You are the support agent for Acme.
Use search_docs for any question about policies and answer from what it returns.A grounded turn in the Playground: the query the model chose, the chunks that came back, and the answer built from them.
You can fill a collection three ways. They all land in the same tables, so the agent finds a hand-uploaded document the same way it finds one your code ingested:
| From | How | Good for |
|---|---|---|
| Studio | RAG screen: New collection, then Upload document (.txt, .md or .pdf, up to 25 MB). The upload returns at once; the box parses, chunks, embeds and stores in the background, and the document's row shows the status of that job with a link to its run. |
An operator feeding a knowledge base with no code. |
| Code | stackbone.rag.ingest({ id, collection, chunks }) for a one-off, inside a 'use step' for anything slow, or ingestDocuments() from @stackbone/sdk/workflow to run the built-in ingest workflow. Re-ingesting the same id replaces its chunks, so a re-crawl converges instead of duplicating. |
A crawler, a webhook or a nightly reindex. |
| CLI | stackbone rag ingest <path> --collection <name>, plus collections create, list, query, jobs, retry and cancel. |
A script, or a first load from your laptop. |
A collection in Studio: what is in it, how each document's ingest went, and the original to download.
Search on the same screen runs the query your tool would run: pick the collection, type the question, and the hits come back with their scores, their metadata and the model that embedded them. Use it to check why the agent answered what it did, or to see that a document did not chunk the way you expected.
The same query the tool makes, run by hand from Studio.
Three contracts keep the code path and the Studio path in agreement. The box
embeds every chunk in a collection with one model,
openai/text-embedding-3-small. Studio's uploads and searches always use it,
and so does your code unless you pass another model on the stackbone.rag
call (rag.embeddingModel in agent.yaml parses but changes nothing). If you
do pass another model, feed the collection from code only, because a document
uploaded by hand would never match a query your tool embeds differently. The
vector dimension (1536) is part of the schema, so any model you pick must return
1536 dimensions, and switching means re-ingesting everything. A query reaches
one collection only, so name it in the tool.
Keep facts across sessions
A session remembers a conversation. It does not remember that this customer is
on the Pro plan or prefers Spanish. stackbone.memory keeps those explicit
facts per agent and person across sessions, together with a short history of calls.
await stackbone.memory.add('Prefers dark mode and a Spanish UI.', {
agentId: 'support',
userId: 'user_42',
call: { startedAt: new Date().toISOString(), outcome: 'scheduled' },
});
const { data } = await stackbone.memory.recall({
agentId: 'support',
userId: 'user_42',
calls: 5,
});One model call receives the current facts under short stable handles and the new
transcript, then returns per-fact add, update, or delete operations plus a bounded
summary. The SDK validates the whole response before applying those operations
and inserting the call record in one transaction. Untouched facts retain their
identity and creation time. The box stores everything in its own Postgres; there
is no external memory service or vector index. Memory uses
openai/gpt-5.6-luna with high reasoning effort by default. You can change both
values through the client configuration or their environment fallbacks. Read
stackbone.memory for the full request, result, and
configuration shapes.
Deep agents can declare independent memory read/save defaults and extraction instructions in code. The box settings contract lets Studio override each field and reset it to the current code value. Both flags default to off. See Per-agent defaults and overrides for the declaration and HTTP contract.
In Studio, open an agent in Catalog and choose its Memory tab. You can change reading, saving and extraction instructions. Each field shows where its current value comes from. Reset removes that field's Studio override and restores its current code value or the default.
Choose the customer identity field once, such as phoneNumber, email, customerId
or a custom literal key. Studio asks you to confirm that choice. The key stays
fixed when you reset settings or erase a customer's memory. Your integration
must provide the selected field as structured identity data. Standard OpenAI
user, Anthropic metadata.user_id, AG-UI forwardedProps.customerId and SDK
customerId supply only the customerId field. A turn without the selected
identity continues without memory tools.
Open the global Memory screen to inspect retained records. Choose an agent, then search by phone, email or another customer identifier. Open a customer to read facts and retained interaction summaries. Delete all asks you to confirm both the customer and agent, then erases that customer's memory for that agent.
The model chooses when to use memory_read or memory_save, according to the
current independent flags. Text does not automatically recall or save. Save-only
mode returns an acknowledgement without exposing stored facts or old summaries.
The runtime binds both tools to the agent and person; different customers using
the same session key also receive separate conversation history. See
Text memory tools and customer identity.
Voice can read memory at call start and save the heard conversation after the
call, according to the independent settings. See Voice.
Memory, retrieval and your database
Pick the store by what you need to do:
| You want to… | Reach for |
|---|---|
| Follow up on the previous message without replaying it | A session. The runtime writes it on every turn, into stackbone_checkpoints in the box's Postgres. |
| Answer from a manual, a policy or a help center | Retrieval. Studio, your ingest code or the CLI fill it, into stackbone_platform.rag_* in the same Postgres. |
| Remember a preference or a fact about a user, across sessions | Long-term memory. stackbone.memory stores per-person facts and recent call summaries in the box's Postgres. |
| Store an order, a ticket or a user record and query it with SQL | Your database. Your code writes it through stackbone.database, into your own tables in the same Postgres, with a schema you migrate yourself. |
All three share the box's Postgres with your tables, and the DB Explorer in
Studio shows the schemas side by side. But only your tables are yours to shape:
the runtime owns stackbone_checkpoints, and the platform provisions and owns
the rag_* and memory_* tables. Read them if you like; do not migrate them. See
Database for what lives where.
What it needs
| Capability | What the box needs |
|---|---|
| Durable sessions | @langchain/langgraph-checkpoint-postgres in the workspace. stackbone init adds it. Without it the box logs a warning at boot and serves every turn stateless, session key or not. |
| A session over the wire | An x-stackbone-session header on the OpenAI and Anthropic wires; a threadId on AG-UI; nothing extra in the Playground. |
| Retrieval | A model provider on the box (it embeds through the same endpoint the agents use). The platform provisions the rag_* schema on every install; there is no vector service to run. |
| Uploads from Studio | The box's workflow runtime, which runs the ingest in the background. A workspace scaffolded by stackbone init has it. If an upload answers rag_ingest_unavailable, that runtime is not mounted on this box. |
| Long-term memory | A model provider on the box and the platform migrations. The box advertises memory.basic; no separate memory service or key is required. See stackbone.memory. |
Read more
stackbone.rag: parse, chunk, ingest, retrieve, delete, and the errors each can return.- A retrieval-backed support agent: a full agent you can copy.
- Agent protocol: the session headers, tool approvals and the AG-UI thread.
- API and Agents: the wires and the screens around them.
- Storage: where the uploaded originals go.
- Governance: the rest of Studio.