Skip to main content
Give a deepagents agent persistent, searchable memory through Synap. deepagents reaches storage through BackendProtocol — a filesystem interface — and reaches memory through MemoryMiddleware, which pastes whole AGENTS.md files into the system prompt. This package plugs Synap into both, and adds a retrieval path the stock middleware cannot express.
Requires Python 3.11+ and deepagents>=0.7.4.This covers the deepagents library — agents you build with create_deep_agent(...). The separate deepagents-code terminal agent constructs its memory backend internally and does not accept a custom one, so it cannot use this package.
Mount SynapBackend on a route, never as backend= on its own. As the default backend it would route the agent’s source-code reads and writes through a memory API, and the agent would lose its working tree.

Overview

Three surfaces, mapped onto deepagents’ own extension points. Adopt only the ones you need. All of them take an already-constructed MaximemSynapSDK — your app owns the SDK and its credentials.

When this is worth it

The payoff scales with how much the agent remembers, and it is worth being concrete about that. In our own testing, an agent answering a question against a 200-memory scope needed 96 characters of retrieved context to get it right. The same task with a plain-file memory backend pasted 24,853 characters — the entire corpus — into the prompt on every turn to reach the same answer. That gap is the reason this integration exists, and it widens as memory grows. The trade is latency and small-corpus behaviour: a retrieval call is a network round trip, so expect a couple of extra seconds per turn versus reading a local file, and on a handful of memories a file backend is both faster and more reliable. Use Synap where memory outgrows a file — long-lived agents, many sessions, memory shared across projects — and leave a small static AGENTS.md on FilesystemBackend. CompositeBackend lets you do both at once.

Setup

Basic integration

Mount Synap at /memories/ and leave the repository on a normal filesystem backend:
CompositeBackend routes by longest path prefix. Anything under /memories/ reaches Synap; everything else goes to the repository, untouched.

Core concepts

This is the part worth knowing. On a Synap route, the agent’s grep tool is not a regex match over file bytes. The pattern is passed to Synap as a natural-language query:
Stock deepagents grep can only find what is literally written in a file. This searches by meaning instead, so a memory can be phrased differently from the query and still match. Matches are attributed to /memories/AGENTS.md, so the agent can read that file for more context. If your agent tends to write regexes, say so in its system prompt. A model that assumes literal matching will write ^prefer.* and misread the misses.
Retrieval is a relevance search, not a guarantee. Some phrasings return nothing even when a matching memory is in scope, so treat an empty grep as “no strong match for this wording” rather than “not in memory”. Where it matters, have the agent try a second phrasing, or read /memories/AGENTS.md directly for the unfiltered recall block.

A new scope needs a corpus before recall is useful

Recall is only as good as what is in the scope, and a nearly-empty scope returns nothing at all rather than a little. In testing, a scope holding three memories returned an empty context block on every setting, while the same scope at a dozen memories returned content consistently. That is worth knowing because of how it looks from the outside: the agent reads /memories/AGENTS.md, gets file_not_found, and behaves exactly as if the integration were broken. It isn’t — there is genuinely nothing to return yet. Seed a real corpus, or let the agent accumulate one over several sessions, before judging recall quality.

The recall file is synthesized, not stored

/memories/AGENTS.md does not exist anywhere. Reading it calls sdk.fetch() and returns the formatted context as the file body. That is what makes create_deep_agent(memory=["/memories/AGENTS.md"]) work with no other changes.

Queued writes and read-after-write

Synap ingestion is asynchronous — create returns {ingestion_id, document_id, status: QUEUED}. A memory written a moment ago is not yet retrievable, so an agent that writes a file and reads it back in the same turn would otherwise get nothing, which reads as data loss. SynapBackend keeps a short-TTL, per-process write-through cache so your own writes are always readable back. It is a read-after-write guarantee, not semantic dedup, and it does not survive a restart. The package never polls wait_for_completion on the agent’s path. Waiting would cost turn latency without improving the answer: Synap may split one submitted document into several memories, or merge it with existing ones, so there is no count to wait for.

There is no delete

sdk.memories.create() returns an ingestion_id; sdk.memories.delete() needs a memory_id. They are different identifiers, and the memory does not exist yet at write time. So a path written through this backend cannot be resolved back to a durable memory. delete is optional in BackendProtocol, so SynapBackend inherits the default that raises NotImplementedError, and CompositeBackend reports it cleanly. Remove memories through the Synap API or dashboard with a memory id.

Complete example: query-conditioned recall

create_deep_agent(memory=[...]) installs deepagents’ own MemoryMiddleware, which calls backend.download_files(paths) — paths, and nothing else. Even with SynapBackend underneath, that is one unqueried digest per run. SynapMemoryMiddleware reads the pending user message first and passes it as the search query, so what lands in the prompt is scoped to what was actually asked:
Use memory=[...] or SynapMemoryMiddleware, not both — they write to the same part of the system prompt, and you would pay for two retrievals to say the same thing twice.

Advanced patterns

Short-term context

Long-term memory and short-term context are different things. Short-term is the compacted history of the current conversation:
That is a snapshot taken once, at construction. For a long-running agent, use SynapShortTermMiddleware instead — it refreshes each turn:

Tuning retrieval

Reads sit on the agent’s startup path; grep is a deliberate question. They get different defaults, and both are configurable:

Error policy

Read failures are always reported as file_not_found, never any other code. That is deliberate: deepagents’ MemoryMiddleware raises ValueError on any download error code except file_not_found, so returning anything else during a Synap outage would end the agent run instead of degrading to an empty memory block.

Going further

  • Mount the backend and the tools together. The backend covers automatic recall through AGENTS.md; the tools cover deliberate lookups with names the model already understands.
  • Set document_type and ingest_mode on writes when the agent is storing something other than notes — for example document_type="meeting-transcript".
  • Scope with customer_id alone for shared team memory, or user_id + customer_id for per-person memory inside an organisation.

Next steps

LangGraph

deepagents runs on LangGraph — the checkpointer integration composes with this one.

LangChain

Retrievers, tools, and short-term context for LangChain itself.