maximem-synap-deepagents 0.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- maximem_synap_deepagents-0.1.1/PKG-INFO +169 -0
- maximem_synap_deepagents-0.1.1/README.md +153 -0
- maximem_synap_deepagents-0.1.1/maximem_synap_deepagents.egg-info/PKG-INFO +169 -0
- maximem_synap_deepagents-0.1.1/maximem_synap_deepagents.egg-info/SOURCES.txt +17 -0
- maximem_synap_deepagents-0.1.1/maximem_synap_deepagents.egg-info/dependency_links.txt +1 -0
- maximem_synap_deepagents-0.1.1/maximem_synap_deepagents.egg-info/requires.txt +7 -0
- maximem_synap_deepagents-0.1.1/maximem_synap_deepagents.egg-info/top_level.txt +1 -0
- maximem_synap_deepagents-0.1.1/pyproject.toml +35 -0
- maximem_synap_deepagents-0.1.1/setup.cfg +4 -0
- maximem_synap_deepagents-0.1.1/synap_deepagents/__init__.py +88 -0
- maximem_synap_deepagents-0.1.1/synap_deepagents/backend.py +571 -0
- maximem_synap_deepagents-0.1.1/synap_deepagents/middleware.py +384 -0
- maximem_synap_deepagents-0.1.1/synap_deepagents/short_term.py +181 -0
- maximem_synap_deepagents-0.1.1/synap_deepagents/tools.py +140 -0
- maximem_synap_deepagents-0.1.1/tests/test_backend.py +590 -0
- maximem_synap_deepagents-0.1.1/tests/test_middleware.py +369 -0
- maximem_synap_deepagents-0.1.1/tests/test_public_surface.py +108 -0
- maximem_synap_deepagents-0.1.1/tests/test_short_term.py +142 -0
- maximem_synap_deepagents-0.1.1/tests/test_tools.py +130 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: maximem-synap-deepagents
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Synap memory integration for deepagents — a memory backend, retrieval middleware, and tools
|
|
5
|
+
Author: Synap Team
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Keywords: synap,memory,deepagents,langchain,langgraph,ai,agents
|
|
8
|
+
Requires-Python: >=3.11
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
Requires-Dist: maximem-synap>=0.2.0
|
|
11
|
+
Requires-Dist: maximem-synap-integrations-common>=0.1.0
|
|
12
|
+
Requires-Dist: deepagents>=0.7.4
|
|
13
|
+
Provides-Extra: dev
|
|
14
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
15
|
+
Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
|
|
16
|
+
|
|
17
|
+
# synap-deepagents
|
|
18
|
+
|
|
19
|
+
Synap integration for [deepagents](https://docs.langchain.com/oss/python/deepagents/overview) — LangChain's agent harness.
|
|
20
|
+
|
|
21
|
+
deepagents stores memory as `AGENTS.md` files and pastes them whole into the system prompt. This package plugs Synap in underneath, and adds a retrieval path the stock middleware cannot express.
|
|
22
|
+
|
|
23
|
+
## Install
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install maximem-synap-deepagents
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Requires `deepagents>=0.7.4`, `maximem-synap>=0.2.0`.
|
|
30
|
+
|
|
31
|
+
## Quickstart
|
|
32
|
+
|
|
33
|
+
Mount Synap on a route with `CompositeBackend`:
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
from deepagents import create_deep_agent
|
|
37
|
+
from deepagents.backends import CompositeBackend
|
|
38
|
+
from deepagents.backends.filesystem import FilesystemBackend
|
|
39
|
+
from maximem_synap import MaximemSynapSDK
|
|
40
|
+
from synap_deepagents import SynapBackend
|
|
41
|
+
|
|
42
|
+
sdk = MaximemSynapSDK(api_key="sk-...")
|
|
43
|
+
|
|
44
|
+
backend = CompositeBackend(
|
|
45
|
+
default=FilesystemBackend(root_dir="/path/to/repo"),
|
|
46
|
+
routes={"/memories/": SynapBackend(sdk, user_id="alice")},
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
agent = create_deep_agent(
|
|
50
|
+
model="anthropic:claude-sonnet-5",
|
|
51
|
+
backend=backend,
|
|
52
|
+
memory=["/memories/AGENTS.md"],
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
agent.invoke({"messages": [{"role": "user", "content": "What do I prefer?"}]})
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
> **Mount it on a route, never as `backend=` on its own.** `SynapBackend` as the
|
|
59
|
+
> default backend would route the agent's source-code reads and writes through a
|
|
60
|
+
> memory API, and the agent would lose its working tree. The constructor cannot
|
|
61
|
+
> stop you.
|
|
62
|
+
|
|
63
|
+
## The three surfaces
|
|
64
|
+
|
|
65
|
+
| Surface | Class | Use when |
|
|
66
|
+
|---|---|---|
|
|
67
|
+
| Backend | `SynapBackend` | You want memory to reach the agent through its own `read_file` / `grep` / `write_file` tools |
|
|
68
|
+
| Middleware | `SynapMemoryMiddleware` | You want recall scoped to the user's actual question |
|
|
69
|
+
| Tools | `SynapSearchTool`, `SynapStoreTool` | You want the model to reach for memory deliberately |
|
|
70
|
+
|
|
71
|
+
They compose. A common setup is the backend for automatic recall plus the tools for deliberate lookups.
|
|
72
|
+
|
|
73
|
+
## `grep` is a semantic search
|
|
74
|
+
|
|
75
|
+
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:
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
# The agent runs this:
|
|
79
|
+
grep("what deployment process does the user follow", path="/memories/")
|
|
80
|
+
# It becomes this:
|
|
81
|
+
sdk.fetch(search_query=["what deployment process does the user follow"], mode="accurate")
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Stock deepagents `grep` can only find what is literally written in a file. This finds what the user said, however they said it. Matches are attributed to `/memories/AGENTS.md` so the agent can read the file for more.
|
|
85
|
+
|
|
86
|
+
Tell your agent about this in its system prompt if it tends to write regexes. A model assuming literal matching will write `^prefer.*` and misread the misses.
|
|
87
|
+
|
|
88
|
+
## Query-conditioned recall
|
|
89
|
+
|
|
90
|
+
`create_deep_agent(memory=[...])` installs deepagents' own `MemoryMiddleware`, which calls `backend.download_files(paths)` — paths, no query. Even with `SynapBackend` underneath, that is one unqueried digest per run.
|
|
91
|
+
|
|
92
|
+
`SynapMemoryMiddleware` reads the pending user message first and passes it as the search query:
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
from synap_deepagents import SynapMemoryMiddleware
|
|
96
|
+
|
|
97
|
+
agent = create_deep_agent(
|
|
98
|
+
model="anthropic:claude-sonnet-5",
|
|
99
|
+
middleware=[SynapMemoryMiddleware(sdk=sdk, user_id="alice")],
|
|
100
|
+
)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
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.
|
|
104
|
+
|
|
105
|
+
## Short-term context
|
|
106
|
+
|
|
107
|
+
Long-term memory and short-term context are different things. Short-term is the compacted history of the *current* conversation:
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
from synap_deepagents import synap_st_instructions
|
|
111
|
+
|
|
112
|
+
system_prompt = await synap_st_instructions(
|
|
113
|
+
sdk, "conv_abc", system="You are a helpful coding agent."
|
|
114
|
+
)
|
|
115
|
+
agent = create_deep_agent(model="...", system_prompt=system_prompt)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
That is a snapshot taken once. For a long-running agent, use `SynapShortTermMiddleware` instead, which refreshes each turn.
|
|
119
|
+
|
|
120
|
+
## Error policy
|
|
121
|
+
|
|
122
|
+
| Operation | Behaviour |
|
|
123
|
+
|---|---|
|
|
124
|
+
| Reads (`read`, `grep`, `ls`, `glob`, `download_files`) | Degrade — log at `ERROR`, return empty or `file_not_found` |
|
|
125
|
+
| Writes (`write`, `edit`, `upload_files`) | Raise `SynapIntegrationError` |
|
|
126
|
+
| Recall in middleware | Degrades to an empty block — a Synap outage must not end the run |
|
|
127
|
+
| `delete` | **Not implemented** — see below |
|
|
128
|
+
|
|
129
|
+
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.
|
|
130
|
+
|
|
131
|
+
## Why there is no `delete`
|
|
132
|
+
|
|
133
|
+
`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 — ingestion is queued. So a path written through this backend cannot be resolved back to a durable memory, and a path-addressed delete cannot be honoured.
|
|
134
|
+
|
|
135
|
+
`delete` is optional in `BackendProtocol`, so this backend inherits the default that raises `NotImplementedError`, and `CompositeBackend` reports it cleanly. Remove memories through the Synap API or dashboard with a memory id.
|
|
136
|
+
|
|
137
|
+
## Queued writes and read-after-write
|
|
138
|
+
|
|
139
|
+
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 get nothing — which reads as data loss.
|
|
140
|
+
|
|
141
|
+
`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. Set `cache_ttl_seconds=0` to disable it only if you can tolerate that.
|
|
142
|
+
|
|
143
|
+
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.
|
|
144
|
+
|
|
145
|
+
## Configuration
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
SynapBackend(
|
|
149
|
+
sdk,
|
|
150
|
+
user_id="alice", # at least one of user_id / customer_id required
|
|
151
|
+
customer_id="acme",
|
|
152
|
+
conversation_id=None,
|
|
153
|
+
recall_filename="AGENTS.md", # basename that maps to the synthesized recall doc
|
|
154
|
+
max_results=20,
|
|
155
|
+
mode="fast", # retrieval mode for reads (on the startup path)
|
|
156
|
+
grep_mode="accurate", # retrieval mode for grep (a deliberate question)
|
|
157
|
+
precision_level="high",
|
|
158
|
+
document_type="document", # writes are explicit intent, not chat transcript
|
|
159
|
+
ingest_mode="fast",
|
|
160
|
+
include_conversation_context=False,
|
|
161
|
+
cache_ttl_seconds=300,
|
|
162
|
+
)
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Tests
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
pytest integrations/synap-deepagents/tests -q
|
|
169
|
+
```
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# synap-deepagents
|
|
2
|
+
|
|
3
|
+
Synap integration for [deepagents](https://docs.langchain.com/oss/python/deepagents/overview) — LangChain's agent harness.
|
|
4
|
+
|
|
5
|
+
deepagents stores memory as `AGENTS.md` files and pastes them whole into the system prompt. This package plugs Synap in underneath, and adds a retrieval path the stock middleware cannot express.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install maximem-synap-deepagents
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Requires `deepagents>=0.7.4`, `maximem-synap>=0.2.0`.
|
|
14
|
+
|
|
15
|
+
## Quickstart
|
|
16
|
+
|
|
17
|
+
Mount Synap on a route with `CompositeBackend`:
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
from deepagents import create_deep_agent
|
|
21
|
+
from deepagents.backends import CompositeBackend
|
|
22
|
+
from deepagents.backends.filesystem import FilesystemBackend
|
|
23
|
+
from maximem_synap import MaximemSynapSDK
|
|
24
|
+
from synap_deepagents import SynapBackend
|
|
25
|
+
|
|
26
|
+
sdk = MaximemSynapSDK(api_key="sk-...")
|
|
27
|
+
|
|
28
|
+
backend = CompositeBackend(
|
|
29
|
+
default=FilesystemBackend(root_dir="/path/to/repo"),
|
|
30
|
+
routes={"/memories/": SynapBackend(sdk, user_id="alice")},
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
agent = create_deep_agent(
|
|
34
|
+
model="anthropic:claude-sonnet-5",
|
|
35
|
+
backend=backend,
|
|
36
|
+
memory=["/memories/AGENTS.md"],
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
agent.invoke({"messages": [{"role": "user", "content": "What do I prefer?"}]})
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
> **Mount it on a route, never as `backend=` on its own.** `SynapBackend` as the
|
|
43
|
+
> default backend would route the agent's source-code reads and writes through a
|
|
44
|
+
> memory API, and the agent would lose its working tree. The constructor cannot
|
|
45
|
+
> stop you.
|
|
46
|
+
|
|
47
|
+
## The three surfaces
|
|
48
|
+
|
|
49
|
+
| Surface | Class | Use when |
|
|
50
|
+
|---|---|---|
|
|
51
|
+
| Backend | `SynapBackend` | You want memory to reach the agent through its own `read_file` / `grep` / `write_file` tools |
|
|
52
|
+
| Middleware | `SynapMemoryMiddleware` | You want recall scoped to the user's actual question |
|
|
53
|
+
| Tools | `SynapSearchTool`, `SynapStoreTool` | You want the model to reach for memory deliberately |
|
|
54
|
+
|
|
55
|
+
They compose. A common setup is the backend for automatic recall plus the tools for deliberate lookups.
|
|
56
|
+
|
|
57
|
+
## `grep` is a semantic search
|
|
58
|
+
|
|
59
|
+
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:
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
# The agent runs this:
|
|
63
|
+
grep("what deployment process does the user follow", path="/memories/")
|
|
64
|
+
# It becomes this:
|
|
65
|
+
sdk.fetch(search_query=["what deployment process does the user follow"], mode="accurate")
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Stock deepagents `grep` can only find what is literally written in a file. This finds what the user said, however they said it. Matches are attributed to `/memories/AGENTS.md` so the agent can read the file for more.
|
|
69
|
+
|
|
70
|
+
Tell your agent about this in its system prompt if it tends to write regexes. A model assuming literal matching will write `^prefer.*` and misread the misses.
|
|
71
|
+
|
|
72
|
+
## Query-conditioned recall
|
|
73
|
+
|
|
74
|
+
`create_deep_agent(memory=[...])` installs deepagents' own `MemoryMiddleware`, which calls `backend.download_files(paths)` — paths, no query. Even with `SynapBackend` underneath, that is one unqueried digest per run.
|
|
75
|
+
|
|
76
|
+
`SynapMemoryMiddleware` reads the pending user message first and passes it as the search query:
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
from synap_deepagents import SynapMemoryMiddleware
|
|
80
|
+
|
|
81
|
+
agent = create_deep_agent(
|
|
82
|
+
model="anthropic:claude-sonnet-5",
|
|
83
|
+
middleware=[SynapMemoryMiddleware(sdk=sdk, user_id="alice")],
|
|
84
|
+
)
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
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.
|
|
88
|
+
|
|
89
|
+
## Short-term context
|
|
90
|
+
|
|
91
|
+
Long-term memory and short-term context are different things. Short-term is the compacted history of the *current* conversation:
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
from synap_deepagents import synap_st_instructions
|
|
95
|
+
|
|
96
|
+
system_prompt = await synap_st_instructions(
|
|
97
|
+
sdk, "conv_abc", system="You are a helpful coding agent."
|
|
98
|
+
)
|
|
99
|
+
agent = create_deep_agent(model="...", system_prompt=system_prompt)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
That is a snapshot taken once. For a long-running agent, use `SynapShortTermMiddleware` instead, which refreshes each turn.
|
|
103
|
+
|
|
104
|
+
## Error policy
|
|
105
|
+
|
|
106
|
+
| Operation | Behaviour |
|
|
107
|
+
|---|---|
|
|
108
|
+
| Reads (`read`, `grep`, `ls`, `glob`, `download_files`) | Degrade — log at `ERROR`, return empty or `file_not_found` |
|
|
109
|
+
| Writes (`write`, `edit`, `upload_files`) | Raise `SynapIntegrationError` |
|
|
110
|
+
| Recall in middleware | Degrades to an empty block — a Synap outage must not end the run |
|
|
111
|
+
| `delete` | **Not implemented** — see below |
|
|
112
|
+
|
|
113
|
+
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.
|
|
114
|
+
|
|
115
|
+
## Why there is no `delete`
|
|
116
|
+
|
|
117
|
+
`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 — ingestion is queued. So a path written through this backend cannot be resolved back to a durable memory, and a path-addressed delete cannot be honoured.
|
|
118
|
+
|
|
119
|
+
`delete` is optional in `BackendProtocol`, so this backend inherits the default that raises `NotImplementedError`, and `CompositeBackend` reports it cleanly. Remove memories through the Synap API or dashboard with a memory id.
|
|
120
|
+
|
|
121
|
+
## Queued writes and read-after-write
|
|
122
|
+
|
|
123
|
+
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 get nothing — which reads as data loss.
|
|
124
|
+
|
|
125
|
+
`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. Set `cache_ttl_seconds=0` to disable it only if you can tolerate that.
|
|
126
|
+
|
|
127
|
+
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.
|
|
128
|
+
|
|
129
|
+
## Configuration
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
SynapBackend(
|
|
133
|
+
sdk,
|
|
134
|
+
user_id="alice", # at least one of user_id / customer_id required
|
|
135
|
+
customer_id="acme",
|
|
136
|
+
conversation_id=None,
|
|
137
|
+
recall_filename="AGENTS.md", # basename that maps to the synthesized recall doc
|
|
138
|
+
max_results=20,
|
|
139
|
+
mode="fast", # retrieval mode for reads (on the startup path)
|
|
140
|
+
grep_mode="accurate", # retrieval mode for grep (a deliberate question)
|
|
141
|
+
precision_level="high",
|
|
142
|
+
document_type="document", # writes are explicit intent, not chat transcript
|
|
143
|
+
ingest_mode="fast",
|
|
144
|
+
include_conversation_context=False,
|
|
145
|
+
cache_ttl_seconds=300,
|
|
146
|
+
)
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Tests
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
pytest integrations/synap-deepagents/tests -q
|
|
153
|
+
```
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: maximem-synap-deepagents
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Synap memory integration for deepagents — a memory backend, retrieval middleware, and tools
|
|
5
|
+
Author: Synap Team
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Keywords: synap,memory,deepagents,langchain,langgraph,ai,agents
|
|
8
|
+
Requires-Python: >=3.11
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
Requires-Dist: maximem-synap>=0.2.0
|
|
11
|
+
Requires-Dist: maximem-synap-integrations-common>=0.1.0
|
|
12
|
+
Requires-Dist: deepagents>=0.7.4
|
|
13
|
+
Provides-Extra: dev
|
|
14
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
15
|
+
Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
|
|
16
|
+
|
|
17
|
+
# synap-deepagents
|
|
18
|
+
|
|
19
|
+
Synap integration for [deepagents](https://docs.langchain.com/oss/python/deepagents/overview) — LangChain's agent harness.
|
|
20
|
+
|
|
21
|
+
deepagents stores memory as `AGENTS.md` files and pastes them whole into the system prompt. This package plugs Synap in underneath, and adds a retrieval path the stock middleware cannot express.
|
|
22
|
+
|
|
23
|
+
## Install
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install maximem-synap-deepagents
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Requires `deepagents>=0.7.4`, `maximem-synap>=0.2.0`.
|
|
30
|
+
|
|
31
|
+
## Quickstart
|
|
32
|
+
|
|
33
|
+
Mount Synap on a route with `CompositeBackend`:
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
from deepagents import create_deep_agent
|
|
37
|
+
from deepagents.backends import CompositeBackend
|
|
38
|
+
from deepagents.backends.filesystem import FilesystemBackend
|
|
39
|
+
from maximem_synap import MaximemSynapSDK
|
|
40
|
+
from synap_deepagents import SynapBackend
|
|
41
|
+
|
|
42
|
+
sdk = MaximemSynapSDK(api_key="sk-...")
|
|
43
|
+
|
|
44
|
+
backend = CompositeBackend(
|
|
45
|
+
default=FilesystemBackend(root_dir="/path/to/repo"),
|
|
46
|
+
routes={"/memories/": SynapBackend(sdk, user_id="alice")},
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
agent = create_deep_agent(
|
|
50
|
+
model="anthropic:claude-sonnet-5",
|
|
51
|
+
backend=backend,
|
|
52
|
+
memory=["/memories/AGENTS.md"],
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
agent.invoke({"messages": [{"role": "user", "content": "What do I prefer?"}]})
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
> **Mount it on a route, never as `backend=` on its own.** `SynapBackend` as the
|
|
59
|
+
> default backend would route the agent's source-code reads and writes through a
|
|
60
|
+
> memory API, and the agent would lose its working tree. The constructor cannot
|
|
61
|
+
> stop you.
|
|
62
|
+
|
|
63
|
+
## The three surfaces
|
|
64
|
+
|
|
65
|
+
| Surface | Class | Use when |
|
|
66
|
+
|---|---|---|
|
|
67
|
+
| Backend | `SynapBackend` | You want memory to reach the agent through its own `read_file` / `grep` / `write_file` tools |
|
|
68
|
+
| Middleware | `SynapMemoryMiddleware` | You want recall scoped to the user's actual question |
|
|
69
|
+
| Tools | `SynapSearchTool`, `SynapStoreTool` | You want the model to reach for memory deliberately |
|
|
70
|
+
|
|
71
|
+
They compose. A common setup is the backend for automatic recall plus the tools for deliberate lookups.
|
|
72
|
+
|
|
73
|
+
## `grep` is a semantic search
|
|
74
|
+
|
|
75
|
+
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:
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
# The agent runs this:
|
|
79
|
+
grep("what deployment process does the user follow", path="/memories/")
|
|
80
|
+
# It becomes this:
|
|
81
|
+
sdk.fetch(search_query=["what deployment process does the user follow"], mode="accurate")
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Stock deepagents `grep` can only find what is literally written in a file. This finds what the user said, however they said it. Matches are attributed to `/memories/AGENTS.md` so the agent can read the file for more.
|
|
85
|
+
|
|
86
|
+
Tell your agent about this in its system prompt if it tends to write regexes. A model assuming literal matching will write `^prefer.*` and misread the misses.
|
|
87
|
+
|
|
88
|
+
## Query-conditioned recall
|
|
89
|
+
|
|
90
|
+
`create_deep_agent(memory=[...])` installs deepagents' own `MemoryMiddleware`, which calls `backend.download_files(paths)` — paths, no query. Even with `SynapBackend` underneath, that is one unqueried digest per run.
|
|
91
|
+
|
|
92
|
+
`SynapMemoryMiddleware` reads the pending user message first and passes it as the search query:
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
from synap_deepagents import SynapMemoryMiddleware
|
|
96
|
+
|
|
97
|
+
agent = create_deep_agent(
|
|
98
|
+
model="anthropic:claude-sonnet-5",
|
|
99
|
+
middleware=[SynapMemoryMiddleware(sdk=sdk, user_id="alice")],
|
|
100
|
+
)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
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.
|
|
104
|
+
|
|
105
|
+
## Short-term context
|
|
106
|
+
|
|
107
|
+
Long-term memory and short-term context are different things. Short-term is the compacted history of the *current* conversation:
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
from synap_deepagents import synap_st_instructions
|
|
111
|
+
|
|
112
|
+
system_prompt = await synap_st_instructions(
|
|
113
|
+
sdk, "conv_abc", system="You are a helpful coding agent."
|
|
114
|
+
)
|
|
115
|
+
agent = create_deep_agent(model="...", system_prompt=system_prompt)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
That is a snapshot taken once. For a long-running agent, use `SynapShortTermMiddleware` instead, which refreshes each turn.
|
|
119
|
+
|
|
120
|
+
## Error policy
|
|
121
|
+
|
|
122
|
+
| Operation | Behaviour |
|
|
123
|
+
|---|---|
|
|
124
|
+
| Reads (`read`, `grep`, `ls`, `glob`, `download_files`) | Degrade — log at `ERROR`, return empty or `file_not_found` |
|
|
125
|
+
| Writes (`write`, `edit`, `upload_files`) | Raise `SynapIntegrationError` |
|
|
126
|
+
| Recall in middleware | Degrades to an empty block — a Synap outage must not end the run |
|
|
127
|
+
| `delete` | **Not implemented** — see below |
|
|
128
|
+
|
|
129
|
+
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.
|
|
130
|
+
|
|
131
|
+
## Why there is no `delete`
|
|
132
|
+
|
|
133
|
+
`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 — ingestion is queued. So a path written through this backend cannot be resolved back to a durable memory, and a path-addressed delete cannot be honoured.
|
|
134
|
+
|
|
135
|
+
`delete` is optional in `BackendProtocol`, so this backend inherits the default that raises `NotImplementedError`, and `CompositeBackend` reports it cleanly. Remove memories through the Synap API or dashboard with a memory id.
|
|
136
|
+
|
|
137
|
+
## Queued writes and read-after-write
|
|
138
|
+
|
|
139
|
+
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 get nothing — which reads as data loss.
|
|
140
|
+
|
|
141
|
+
`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. Set `cache_ttl_seconds=0` to disable it only if you can tolerate that.
|
|
142
|
+
|
|
143
|
+
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.
|
|
144
|
+
|
|
145
|
+
## Configuration
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
SynapBackend(
|
|
149
|
+
sdk,
|
|
150
|
+
user_id="alice", # at least one of user_id / customer_id required
|
|
151
|
+
customer_id="acme",
|
|
152
|
+
conversation_id=None,
|
|
153
|
+
recall_filename="AGENTS.md", # basename that maps to the synthesized recall doc
|
|
154
|
+
max_results=20,
|
|
155
|
+
mode="fast", # retrieval mode for reads (on the startup path)
|
|
156
|
+
grep_mode="accurate", # retrieval mode for grep (a deliberate question)
|
|
157
|
+
precision_level="high",
|
|
158
|
+
document_type="document", # writes are explicit intent, not chat transcript
|
|
159
|
+
ingest_mode="fast",
|
|
160
|
+
include_conversation_context=False,
|
|
161
|
+
cache_ttl_seconds=300,
|
|
162
|
+
)
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Tests
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
pytest integrations/synap-deepagents/tests -q
|
|
169
|
+
```
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
maximem_synap_deepagents.egg-info/PKG-INFO
|
|
4
|
+
maximem_synap_deepagents.egg-info/SOURCES.txt
|
|
5
|
+
maximem_synap_deepagents.egg-info/dependency_links.txt
|
|
6
|
+
maximem_synap_deepagents.egg-info/requires.txt
|
|
7
|
+
maximem_synap_deepagents.egg-info/top_level.txt
|
|
8
|
+
synap_deepagents/__init__.py
|
|
9
|
+
synap_deepagents/backend.py
|
|
10
|
+
synap_deepagents/middleware.py
|
|
11
|
+
synap_deepagents/short_term.py
|
|
12
|
+
synap_deepagents/tools.py
|
|
13
|
+
tests/test_backend.py
|
|
14
|
+
tests/test_middleware.py
|
|
15
|
+
tests/test_public_surface.py
|
|
16
|
+
tests/test_short_term.py
|
|
17
|
+
tests/test_tools.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
synap_deepagents
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "maximem-synap-deepagents"
|
|
7
|
+
version = "0.1.1"
|
|
8
|
+
description = "Synap memory integration for deepagents — a memory backend, retrieval middleware, and tools"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = "Apache-2.0"
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "Synap Team"}
|
|
14
|
+
]
|
|
15
|
+
keywords = ["synap", "memory", "deepagents", "langchain", "langgraph", "ai", "agents"]
|
|
16
|
+
|
|
17
|
+
dependencies = [
|
|
18
|
+
"maximem-synap>=0.2.0",
|
|
19
|
+
"maximem-synap-integrations-common>=0.1.0",
|
|
20
|
+
"deepagents>=0.7.4",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
[project.optional-dependencies]
|
|
24
|
+
dev = [
|
|
25
|
+
"pytest>=7.0",
|
|
26
|
+
"pytest-asyncio>=0.21",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[tool.setuptools.packages.find]
|
|
30
|
+
where = ["."]
|
|
31
|
+
include = ["synap_deepagents*"]
|
|
32
|
+
|
|
33
|
+
[tool.pytest.ini_options]
|
|
34
|
+
testpaths = ["tests"]
|
|
35
|
+
asyncio_mode = "auto"
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""Synap memory integration for deepagents.
|
|
2
|
+
|
|
3
|
+
deepagents is LangChain's agent harness. It reaches storage through
|
|
4
|
+
``BackendProtocol`` — a filesystem interface — and reaches memory through
|
|
5
|
+
``MemoryMiddleware``, which pastes whole ``AGENTS.md`` files into the system
|
|
6
|
+
prompt. This package plugs Synap into both, and adds a retrieval path the stock
|
|
7
|
+
middleware cannot express.
|
|
8
|
+
|
|
9
|
+
Three surfaces, useful separately or together:
|
|
10
|
+
|
|
11
|
+
**1. A backend.** :class:`SynapBackend` implements ``BackendProtocol`` so the
|
|
12
|
+
agent's own ``read_file`` / ``grep`` / ``write_file`` tools reach Synap. Mount it
|
|
13
|
+
on a route — never as the default backend, or the agent loses its working tree::
|
|
14
|
+
|
|
15
|
+
from deepagents import create_deep_agent
|
|
16
|
+
from deepagents.backends import CompositeBackend, FilesystemBackend
|
|
17
|
+
from maximem_synap import MaximemSynapSDK
|
|
18
|
+
from synap_deepagents import SynapBackend
|
|
19
|
+
|
|
20
|
+
sdk = MaximemSynapSDK(api_key="sk-...")
|
|
21
|
+
|
|
22
|
+
backend = CompositeBackend(
|
|
23
|
+
default=FilesystemBackend(root_dir="/path/to/repo"),
|
|
24
|
+
routes={"/memories/": SynapBackend(sdk, user_id="alice")},
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
agent = create_deep_agent(
|
|
28
|
+
model="anthropic:claude-sonnet-5",
|
|
29
|
+
backend=backend,
|
|
30
|
+
memory=["/memories/AGENTS.md"],
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
Note what ``grep`` does here: on a Synap route it is a **semantic search**, not a
|
|
34
|
+
regex match. The agent asks a question in plain language and gets an answer.
|
|
35
|
+
|
|
36
|
+
**2. Middleware.** :class:`SynapMemoryMiddleware` retrieves memory using the
|
|
37
|
+
user's actual question as the search query, which ``MemoryMiddleware`` cannot do
|
|
38
|
+
— it only knows file paths. Use it instead of ``memory=[...]``, not alongside::
|
|
39
|
+
|
|
40
|
+
agent = create_deep_agent(
|
|
41
|
+
model="anthropic:claude-sonnet-5",
|
|
42
|
+
middleware=[SynapMemoryMiddleware(sdk=sdk, user_id="alice")],
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
:class:`SynapShortTermMiddleware` does the same for compacted conversation
|
|
46
|
+
history, refreshed each turn.
|
|
47
|
+
|
|
48
|
+
**3. Tools.** :class:`SynapSearchTool` and :class:`SynapStoreTool` are ordinary
|
|
49
|
+
LangChain tools, for when you want the model to reach for memory deliberately
|
|
50
|
+
rather than have it arrive automatically.
|
|
51
|
+
|
|
52
|
+
Error policy: reads degrade — a Synap outage yields an empty memory block and a
|
|
53
|
+
logged error, never a failed agent run. Writes raise ``SynapIntegrationError``.
|
|
54
|
+
There is no ``delete``: ``memories.create`` returns an ingestion id rather than a
|
|
55
|
+
memory id, so a path cannot be resolved back to a durable memory. Remove
|
|
56
|
+
memories through the Synap API with a memory id.
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
from synap_deepagents.backend import (
|
|
60
|
+
DEFAULT_CACHE_TTL_SECONDS,
|
|
61
|
+
DEFAULT_RECALL_FILENAME,
|
|
62
|
+
SynapBackend,
|
|
63
|
+
)
|
|
64
|
+
from synap_deepagents.middleware import (
|
|
65
|
+
SYNAP_MEMORY_PROMPT,
|
|
66
|
+
SynapMemoryMiddleware,
|
|
67
|
+
SynapShortTermMiddleware,
|
|
68
|
+
)
|
|
69
|
+
from synap_deepagents.short_term import (
|
|
70
|
+
compose_system_prompt,
|
|
71
|
+
fetch_st_block,
|
|
72
|
+
synap_st_instructions,
|
|
73
|
+
)
|
|
74
|
+
from synap_deepagents.tools import SynapSearchTool, SynapStoreTool
|
|
75
|
+
|
|
76
|
+
__all__ = [
|
|
77
|
+
"DEFAULT_CACHE_TTL_SECONDS",
|
|
78
|
+
"DEFAULT_RECALL_FILENAME",
|
|
79
|
+
"SYNAP_MEMORY_PROMPT",
|
|
80
|
+
"SynapBackend",
|
|
81
|
+
"SynapMemoryMiddleware",
|
|
82
|
+
"SynapSearchTool",
|
|
83
|
+
"SynapShortTermMiddleware",
|
|
84
|
+
"SynapStoreTool",
|
|
85
|
+
"compose_system_prompt",
|
|
86
|
+
"fetch_st_block",
|
|
87
|
+
"synap_st_instructions",
|
|
88
|
+
]
|