Memory
Workspace memories that agents keep across conversations, as versioned files or recalled records.
A memory is what agents keep across conversations: preferences, decisions, conventions and reference facts. It belongs to a workspace, and several conversations can use it at once. There are two kinds:
- A file memory is a small tree of text files. The Service keeps its files, and the history of every change, in PostgreSQL. Every write checks the file's version at the moment it runs, so a change based on stale content fails and returns the current file instead of overwriting another conversation's work. File memory explains what the model sees and how to write good memory files.
- A record memory is a set of short text records, such as "The user prefers metric units", kept in a mem0 backend through a Memory Provider. At the start of each run, the records closest to the run's input are recalled into it. Records have no versions: the last write wins.
Threads mount memories under names. Each run freezes the thread's memory mounts when it is accepted, sees each memory's context or recalled records at its start, and changes the memories through the memory_file_* and memory_record_* tools.
All API paths below are under /api/v1.
Create a file memory
Creating and changing memories needs write:
curl -X POST "$A13N_URL/api/v1/memories" \
-H "Authorization: Bearer $A13N_API_KEY" -H "Content-Type: application/json" \
-d '{"name": "Team conventions", "type": "postgres",
"guide": "Keep one file per topic. Record decisions with their reason.",
"always_load": ["README.md"]}'typeispostgres, the Service's own store and the default. The response'sid(mem_…) identifies the memory; memories have no key.guidetells agents what belongs in this memory and how to organize it, up tomemory.guide_bytes. Leave it out or set it tonullto use the deployment's guide for the memory's kind (memory.default_guide.fileormemory.default_guide.record, else the built-in one);""gives the memory no guide. The memory'sinherited_guideshows the guidenullresolves to.always_loadnames up to 64 paths whose full content leads the memory's context in every run. A path need not exist yet. Only people who may change the memory choose them, so a conversation cannot pin its own writes into every later one.PATCH …/memories/{memory_id}with the memory'sIf-Matchchangesname,description,labels,guideandalways_load. Runs that start afterwards use the change.DELETE …/memories/{memory_id}withIf-Matchdeletes the memory with its files, history and thread mounts. A running run that uses it getsmemory_deletedfrom its next memory tool call.
GET …/memories lists the workspace's memories, filtered by label, kind (file or record) and type. A file memory shows file_count, content_bytes and history_bytes; they are null for a record memory.
Record memories
Set up a Memory Provider
A Memory Provider is a provider of kind memory: one account of a record memory backend. Add it to the workspace in /api/v1/memory-providers.
For the hosted mem0 Platform, use type mem0_platform with an API key from the mem0 dashboard; config.base_url defaults to https://api.mem0.ai:
curl -X POST "$A13N_URL/api/v1/memory-providers" \
-H "Authorization: Bearer $A13N_API_KEY" -H "Content-Type: application/json" \
-d '{"type": "mem0_platform", "name": "mem0", "config": {}, "credential": {"api_key": "m0-..."}}'For self-hosted mem0, run the mem0 REST server with its own model, embedder and vector store, and use type mem0_oss with the server's address as base_url:
curl -X POST "$A13N_URL/api/v1/memory-providers" \
-H "Authorization: Bearer $A13N_API_KEY" -H "Content-Type: application/json" \
-d '{"type": "mem0_oss", "name": "mem0",
"config": {"base_url": "https://mem0.internal.example.com"}, "credential": {"api_key": "..."}}'The self-hosted credential is optional and is sent as X-API-Key; purging a deleted memory's records needs the server's admin key. A server on a private network or on plain HTTP must be allowed by the deployment's outbound policy. The self-hosted server lists at most 1000 records of a memory.
POST …/memory-providers/{provider_id}/test lists one page of a namespace no memory uses, which checks the address and key without changing anything.
Create a record memory
curl -X POST "$A13N_URL/api/v1/memories" \
-H "Authorization: Bearer $A13N_API_KEY" -H "Content-Type: application/json" \
-d '{"name": "User facts", "type": "mem0_platform", "provider_id": "memprov_...",
"guide": "Record one stable fact about the user per record."}'typeis the provider's type, and the provider must be an enabled Memory Provider of the workspace.always_loaddoes not apply.- Each record memory owns a namespace in the backend, which is mem0's
user_id. By default it isa13n-and 32 hex characters derived from the memory's ID. Setnamespaceto adopt records that already exist under auser_id, such as ones your application wrote; it is 1 to 256 printable characters with no whitespace and no*. One namespace belongs to one memory: another memory using it is409 already_exists. type,provider_idandnamespacenever change.- Deleting a record memory deletes the records in its namespace too, in the background. Until that finishes, a new memory cannot take the namespace (
409 conflictwith reasonnamespace_purging). If the backend keeps refusing, the purge stops afteroutbox.defaults.max_attemptstries and the records stay in the backend. The mem0 Platform finishes a purge on its own after accepting it, so its records can linger briefly.
Read and edit records
People can read and correct what agents recorded. Reading needs read; adding, updating and deleting need run.
| Request | Does |
|---|---|
GET …/memories/{id}/records?limit=&cursor= | Lists records in the backend's order, with next_cursor for the next page |
POST …/memories/{id}/records/search | Returns up to limit records closest in meaning to {query}, closest first, with score |
POST …/memories/{id}/records | Adds {text} |
PUT …/memories/{id}/records/{record_id} | Replaces the record's whole text with {text} |
DELETE …/memories/{id}/records/{record_id} | Deletes the record |
A record holds 1 to memory.record_chars characters (8000 by default). Records have no ETag, so the last write wins. A write the backend does not confirm answers 409 conflict with reason write_unconfirmed: it may or may not have happened, so list or search before trying again. A backend that cannot answer is 503 unavailable with dependency memory:{type}, and a disabled provider is 422 disabled. Record changes are audited with the record ID, never the text.
Recall
When a run starts, each mounted record memory with recall on searches for the memory.recall_limit records (5 by default) closest to the run's input, and the run receives them before its input as untrusted data, at most memory.recall_bytes (8 KiB) per memory. Recall waits at most memory.recall_seconds (2 seconds); a memory whose search fails or is too slow is skipped, and the run continues without it. Only the first input of a run recalls, so a run's history does not fill with repeated records.
Mount a memory on a thread
A thread's memory mounts decide what its later runs use:
curl -X POST "$A13N_URL/api/v1/threads/$THREAD/memories" \
-H "Authorization: Bearer $A13N_API_KEY" -H "Content-Type: application/json" -H "If-Match: $THREAD_ETAG" \
-d '{"name": "team", "memory_id": "mem_...", "access": "write"}'namematches^[a-z][a-z0-9-]{0,62}$; the model addresses the memory by it.accessisread, which offers only the reading and searching tools, orwrite, which offers every tool.recall(defaulttrue) lets a record memory recall records into each run; file memories ignore it.- A thread mounts each name and each memory once.
PATCH …/threads/{thread_id}/memories/{name}changes a mount'saccessandrecall; to change its memory, remove it and add it again. - A thread holds at most
memory.mounts_per_threadmemory mounts (8 by default); a request beyond that is409 conflictwith reasonmemory_mount_limit. - Mount changes take the thread's
If-Match, return the thread's new ETag, needrun, and affect runs accepted afterwards.GET …/threads/{thread_id}/memorieslists the mounts with the thread's ETag, andDELETE …/threads/{thread_id}/memories/{name}removes one. - New threads and forks take initial mounts in their
memoriesfield. A fork copies its origin thread's memory mounts. Archiving a thread removes them.
To give every conversation of an agent a memory, set the agent's memory_mounts to [{name, memory_id, access, recall}]. They join a thread when its first run is accepted, for each name and memory the thread does not use yet. Afterwards the thread's own mounts decide, so removing one keeps it removed. A default whose memory was deleted fails that first run with invalid_argument, and defaults that would take the thread over its limit fail it with memory_mount_limit.
An async subagent's thread starts with the parent run's memory mounts and then adds its own agent's defaults. Inline subagents get no memory tools or context.
What agents get
The memory toolset is enabled by default. Its tools are:
- the Harness file memory tools, with tool keys
file_viewthroughfile_deleteand permission IDsmemory.file.viewthroughmemory.file.delete, offered for mounted file memories; - the record tools
memory_record_search,memory_record_list,memory_record_add,memory_record_updateandmemory_record_delete, with tool keysrecord_searchthroughrecord_deleteand permission IDsmemory.record.searchthroughmemory.record.delete, offered for mounted record memories.
Disabling a tool removes it from every mount; disabling the toolset leaves the run its memories' context and recall without tools.
At the start of a run, each file memory adds one context block: its always-loaded files and an index of its files the first time a conversation sees it, and afterwards only the files changed since, including changes by other conversations and people. A conversation whose history was compacted gets full context again. A run's memory context shares memory.context_bytes (32 KiB by default), of which each memory's always-loaded files take at most memory.always_load_bytes.
Each memory call checks the run's access to the workspace: read to view, list and search, run to change a file or record. A refused call fails with forbidden, and a call to a record memory whose provider was disabled fails with unavailable. A failed file call changes nothing; the model reads the file and decides again.
A worker can stop after a memory write and before the run records its progress. The recovered attempt then reports that the call's outcome is unknown, and the model reads the file, or searches the records, before writing again. A file write keeps its run and tool call in the history.
Files and history
People can read and correct what agents wrote. Reading needs read, editing and restoring need run, and purging history needs write. {path} is the file's path, such as prefs/language.md.
| Request | Does |
|---|---|
GET …/memories/{id}/files?prefix= | Lists files without content, in path order, under a directory prefix ending in / |
GET …/memories/{id}/files/{path} | Returns one file with its content and ETag |
POST …/memories/{id}/files | Creates {path, content}; 409 already_exists when the path is taken |
PUT …/memories/{id}/files/{path} | Replaces the content of the file If-Match names |
POST …/memories/{id}/files/move | Moves {source, destination} of the file If-Match names to a free path |
DELETE …/memories/{id}/files/{path} | Deletes the file If-Match names |
GET …/memories/{id}/revisions | Lists changes newest first, filtered by path or run_id |
GET …/memories/{id}/revisions/{seq} | One change with previous_content, the content it left, and unified-diff hunks |
POST …/memories/{id}/revisions/{seq}/restore | Puts back the content that change replaced, as a new change; If-Match names the file now at the path, if any |
DELETE …/memories/{id}/revisions?path= | Deletes the retained history of one path; the file stays |
Each revision records who made the change: run_id and tool_call_id for an agent's tool call, principal_id for both agents and people. A file's ETag changes with every change, so an edit based on an old read answers 412 precondition_failed.
A file holds at most memory.max_file_bytes (64 KiB by default). Each file keeps its latest memory.revisions_per_file changes (10 by default), and one memory's content and history together fit memory.max_total_bytes (32 MiB by default): the oldest history is pruned first, and only content alone over the limit refuses a change, with 409 conflict and reason memory_full. See the settings reference for every memory.* setting.