deepagents-graph-memory 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,770 @@
1
+ Metadata-Version: 2.4
2
+ Name: deepagents-graph-memory
3
+ Version: 0.1.0
4
+ Summary: Graph-backed context scratchpad for LangChain Deep Agents.
5
+ Author-email: Pranav Bedi <pranavbedi80@gmail.com>, Taha Khan <ktaha2005.net@gmail.com>
6
+ License: MIT
7
+ Project-URL: Repository, https://github.com/TahaK29/deepagents-graph-memory
8
+ Project-URL: Documentation, https://github.com/TahaK29/deepagents-graph-memory#readme
9
+ Project-URL: Issues, https://github.com/TahaK29/deepagents-graph-memory/issues
10
+ Keywords: deepagents,langchain,langgraph,context,graph,ladybug
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python :: 3.14
19
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
20
+ Requires-Python: <3.15,>=3.11
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: deepagents>=0.6.10
24
+ Requires-Dist: ladybug==0.20.3
25
+ Requires-Dist: langchain-core<2.0.0,>=1.4.0
26
+ Requires-Dist: wcmatch>=10.1
27
+ Provides-Extra: test
28
+ Requires-Dist: pytest>=8.0.0; extra == "test"
29
+ Requires-Dist: pytest-asyncio>=1.0.0; extra == "test"
30
+ Requires-Dist: ruff<0.16.0,>=0.12.2; extra == "test"
31
+ Dynamic: license-file
32
+
33
+ # deepagents-graph-memory
34
+
35
+ An experimental graph-backed workflow and trace store for LangChain Deep Agents. It records linked project context in LadybugDB, supports keyword search and bounded traversal, and exposes read-only Markdown views for inspection.
36
+
37
+ Created by **Pranav Bedi and Taha Khan**.
38
+
39
+ The [workflow evaluation](https://github.com/TahaK29/deepagents-graph-memory/blob/main/evals/README.md) runs twelve offline integration scenarios and offers an opt-in, bounded graph-versus-notes model comparison. Offline passes verify retrieval mechanics, not improved model decisions.
40
+
41
+ **Debugging a missing graph fact?** See [Inspecting the graph](#inspecting-the-graph) for a copyable Python example that needs no model or provider key.
42
+
43
+ [![Status: Experimental](https://img.shields.io/badge/Status-Experimental-F59E0B)](https://github.com/TahaK29/deepagents-graph-memory)
44
+ [![Python 3.11–3.14](https://img.shields.io/badge/Python-3.11%E2%80%933.14-3776AB?logo=python&logoColor=white)](https://python.org)
45
+ [![LadybugDB](https://img.shields.io/badge/LadybugDB-Graph_DB-FF6B35?logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZmlsbD0id2hpdGUiIGQ9Ik0xMiAyTDIgN2wxMCA1IDEwLTUtMTAtNXpNMiAxN2wxMCA1IDEwLTVNMiAxMmwxMCA1IDEwLTUiLz48L3N2Zz4=)](https://ladybugdb.com)
46
+ [![LangChain](https://img.shields.io/badge/LangChain-Framework-1C3C3C?logo=langchain&logoColor=white)](https://python.langchain.com)
47
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
48
+
49
+ <p align="center">
50
+ <img src="https://raw.githubusercontent.com/TahaK29/deepagents-graph-memory/main/assets/vgs-graph.png" alt="Virtual Graph System: connected reasoning traces" width="50%">
51
+ </p>
52
+
53
+ ## Motivation
54
+
55
+ Neo4j's [context graph article](https://neo4j.com/blog/genai/from-recall-to-reasoning-how-context-graphs-upgrade-an-agents-brain/) inspired this project. The package records supplied situations, rationales, actions, outcomes, artifacts, and links so agents can retrieve connected work context. It does not verify causal claims, infer new relationships, transfer knowledge automatically, or unlearn outdated facts. A rationale or causal label is an assertion supplied by the caller.
56
+
57
+ ## What It Does
58
+
59
+ | Structured Traces | Graph Recall | Virtual Graph Views |
60
+ |---|---|---|
61
+ | Situation &rarr; Rationale &rarr; Action &rarr; Outcome | Keyword search + bounded traversal | Read-only `/graph/...` markdown projections |
62
+ | Caller-supplied links and metadata | Anchor-based seed expansion | Schema, index, node, search |
63
+ | Workflow and project context | Approximate output sizing plus depth, node, and edge limits | Inspectable through backend methods |
64
+
65
+ This is **not** ordinary user memory. Don't use it for facts like "the user likes ice cream." Use it for connected work state like *"this failing test led to this hypothesis, this edit, this result, and this final decision."*
66
+
67
+ The namespace option scopes graph data; it is not an authorization boundary, and schema inspection remains database-wide. `GraphMemoryBackend.create()` uses in-memory LadybugDB by default. Its data remains available while the backend/store is open and disappears when it closes or the process exits; there is no automatic cleanup after each agent invocation. Supply a filesystem `path` for [persistent storage](#persistent-storage).
68
+
69
+ ### Sharing one graph between agents
70
+
71
+ Create one store and pass it to each backend. Use the same project namespace so both writers can inspect the same graph. Agent and subagent IDs are supplied by the caller; they are not inferred from the agent runtime.
72
+
73
+ ```python
74
+ from deepagents_graph_memory import GraphMemoryBackend, graph_memory_tools, make_graph_subject
75
+ from deepagents_graph_memory.ladybug_store import LadybugGraphStore
76
+
77
+ store = LadybugGraphStore.memory()
78
+ parent = GraphMemoryBackend(store, namespace=("project", "demo"))
79
+ subagent = GraphMemoryBackend(store, namespace=("project", "demo"))
80
+ subject = make_graph_subject("src/parser.py", "empty-field parsing", "linux")
81
+ # Give the same subject to each worker when creating its tools.
82
+ worker_record = next(tool for tool in graph_memory_tools(subagent, bound_subject=subject) if tool.name == "record_graph_trace")
83
+
84
+ parent.record_graph_trace(
85
+ situation="test failed at revision a1", rationale="the parser missed an empty field",
86
+ action="asked a subagent to inspect the parser", outcome="cause identified",
87
+ run_id="run-1", agent_id="parent", subject=subject,
88
+ )
89
+ worker_record.invoke({
90
+ "situation": "parser skipped an empty field at revision a1", "rationale": "split removed the field",
91
+ "action": "patched the parser", "outcome": "test passed at revision b2",
92
+ "artifacts": ["src/parser.py"], "run_id": "run-1", "agent_id": "parent", "subagent_id": "parser-debugger",
93
+ }) # The binding supplies subject even though this call omits it.
94
+ print(parent.ls("/graph/nodes/Trace/").entries)
95
+ print(parent.read("/graph/search/parser.md").file_data["content"])
96
+ ```
97
+
98
+ Writes through one store are serialized and each trace or document batch commits atomically. Existing node and edge properties survive updates that omit them; the last successful writer wins when writers set the same property. Shared Artifact and Evidence values retain links to every trace, with per-trace IDs on the links. Distinct in-memory stores do not share data. For a persistent graph, create one backend with `path=...` and pass its `.store` to other backends in the same process. Close the shared store only after all agents finish using it.
99
+
100
+ ## Quick Start
101
+
102
+ Install the package:
103
+
104
+ ```bash
105
+ pip install deepagents-graph-memory
106
+ ```
107
+
108
+ Complete the one-time [full-text search setup](#full-text-search-setup) before
109
+ running graph search or recall.
110
+
111
+ ```python
112
+ from deepagents import create_deep_agent
113
+ from deepagents.backends import CompositeBackend, StateBackend
114
+ from deepagents_graph_memory import (
115
+ GraphMemoryBackend,
116
+ graph_context_middleware,
117
+ graph_memory_tools,
118
+ )
119
+
120
+ MODEL = "google_genai:gemini-3.5-flash"
121
+
122
+ # In-memory LadybugDB graph; no database path required
123
+ graph_backend = GraphMemoryBackend.create()
124
+ graph_tools = graph_memory_tools(graph_backend)
125
+
126
+ agent = create_deep_agent(
127
+ model=MODEL,
128
+ tools=graph_tools,
129
+ middleware=[graph_context_middleware()],
130
+ memory=["/graph/index.md", "/graph/schema.md"],
131
+ backend=CompositeBackend(
132
+ default=StateBackend(), # Working files and offloaded tool results.
133
+ routes={"/graph/": graph_backend}, # Read-only graph inspection.
134
+ ),
135
+ subagents=[{
136
+ # Configure the default worker explicitly so it also gets graph guidance.
137
+ "name": "general-purpose",
138
+ "description": "Investigate a focused task and return findings with evidence.",
139
+ "system_prompt": "Complete the assigned task and report findings with source paths.",
140
+ "tools": graph_tools,
141
+ "middleware": [graph_context_middleware()],
142
+ }],
143
+ )
144
+ ```
145
+
146
+ Configure the selected model provider integration and credentials separately. The no-provider-key example below only exercises backend inspection.
147
+
148
+ ## Using VGS and VFS Together
149
+
150
+ Keep filesystem tools available for files, scratch work, and full tool output.
151
+ Use the graph to connect meaningful findings, failed attempts, decisions, and
152
+ outcomes to their evidence. A test log belongs in VFS; a trace can record what
153
+ failed, what changed, and which test run supports the result.
154
+
155
+ `graph_context_middleware()` adds this guidance to one agent without changing
156
+ model-wide profiles or removing filesystem instructions. Pass graph tools
157
+ explicitly. Add the middleware and graph tools to each subagent that needs them;
158
+ parent middleware does not automatically propagate to every subagent. The quick
159
+ start overrides the built-in general-purpose worker for this reason. Precompiled
160
+ or remote agents need their own setup and access to the evidence they cite.
161
+
162
+ The first lookup depends on the task: read a known file directly for a file edit,
163
+ or recall earlier attempts and dependencies when resuming work. Verify current
164
+ files or tool results before relying on historical findings. Record selective
165
+ outcomes rather than mirroring every read, file, or log line into the graph.
166
+
167
+ Deep Agents' existing filesystem middleware can offload large tool responses
168
+ under `/large_tool_results/`. Keep that location on a writable VFS backend so the
169
+ agent can use `read_file` and `grep` to inspect the dump. This package does not
170
+ change the offload threshold or copy the full dump into LadybugDB. When recording a
171
+ finding, use `evidence_refs` to cite the actual saved location and source identity,
172
+ including the revision or observation time when known.
173
+
174
+ Save captured evidence before recording a claim about it. File and graph writes
175
+ are separate operations: if trace recording fails after an action succeeded,
176
+ retry the recording with its operation ID rather than repeating the action.
177
+ Missing, inaccessible, or changed evidence cannot verify the original finding.
178
+ The middleware provides agent guidance; it does not fetch sources or enforce
179
+ atomic writes across VFS and LadybugDB.
180
+
181
+ You can keep your existing filesystem backend and add only the graph tools and
182
+ middleware. Mounting `/graph/` through the native `CompositeBackend` is optional;
183
+ it enables read-only file inspection of graph views. `write_file`, `edit_file`,
184
+ and uploads cannot mutate those views. Keep the physical LadybugDB database outside
185
+ the agent's writable file workspace. Ordinary preferences, instructions, and
186
+ notes remain in VFS or `/memories/`.
187
+
188
+ **Storage lifetimes are separate.** `StateBackend` uses thread state; retaining
189
+ those files across process restarts requires a durable checkpointer. Saving LadybugDB
190
+ with `path=...` does not persist VFS files. For evidence that must survive across
191
+ threads or deployments, configure a suitable persistent file/store backend and
192
+ give readers access to it. Do not assume a saved graph makes an old dump available.
193
+
194
+ ## Persistent Storage
195
+
196
+ Persistence works through the Python library API in scripts, notebooks, agent
197
+ runners, background workers, or web applications. It does not depend on a web
198
+ framework or cloud provider. The application opens one store, passes it to its
199
+ agents, and closes it after they finish.
200
+
201
+ Pass a `str` or `pathlib.Path` to create or reopen a LadybugDB database file. The parent
202
+ directory must already exist; the package does not create directories or mount
203
+ storage. Examples use `.lbdb`; the path accepts any extension. Omitting `path`,
204
+ or passing `None`, keeps the default in-memory behavior.
205
+
206
+ This example writes a trace, closes the database, and reopens it without an LLM:
207
+
208
+ ```python
209
+ from pathlib import Path
210
+ from tempfile import TemporaryDirectory
211
+
212
+ from deepagents_graph_memory import GraphMemoryBackend
213
+
214
+ with TemporaryDirectory() as directory: # Use a durable directory in your app.
215
+ path = Path(directory) / "project.lbdb"
216
+ graph = GraphMemoryBackend.create(path=path, namespace=("project", "demo"))
217
+ try:
218
+ trace_id = graph.record_graph_trace(
219
+ situation="parser test failed", rationale="empty field was dropped",
220
+ action="fixed the parser", outcome="test passed",
221
+ )
222
+ finally:
223
+ graph.close()
224
+
225
+ reopened = GraphMemoryBackend.create(path=path, namespace=("project", "demo"))
226
+ try:
227
+ print(reopened.read(f"/graph/nodes/Trace/{trace_id}.md").file_data["content"])
228
+ finally:
229
+ reopened.close()
230
+ ```
231
+
232
+ Successful writes commit during execution. `close()` releases the connection and
233
+ database lock; it does not delete a disk database. Calling it again is safe.
234
+ Backends sharing one store share its lifetime, so closing any of them closes that
235
+ store for all of them. Keep the database open across requests and close it during
236
+ application shutdown. Do not close it inside an active transaction.
237
+
238
+ Invalid paths and open failures raise errors instead of falling back to memory.
239
+ The path names a database file, not a directory or a URL such as `s3://...`.
240
+ Keep its containing directory on durable storage, including LadybugDB's associated
241
+ files. Reuse the same namespace when resuming the same project.
242
+
243
+ ### FastAPI and containers
244
+
245
+ FastAPI is one lifecycle example; other applications use the same `create(path=...)`
246
+ and `close()` calls in their own startup and shutdown code.
247
+
248
+ Configure your deployment to mount durable storage at `/data`, then open the
249
+ database once in FastAPI's lifespan. FastAPI is an application dependency; this
250
+ package does not install it.
251
+
252
+ ```python
253
+ from contextlib import asynccontextmanager
254
+
255
+ from fastapi import FastAPI
256
+ from deepagents_graph_memory import GraphMemoryBackend
257
+
258
+
259
+ @asynccontextmanager
260
+ async def lifespan(app: FastAPI):
261
+ graph = GraphMemoryBackend.create(
262
+ path="/data/project.lbdb", namespace=("project", "demo"),
263
+ )
264
+ app.state.graph = graph
265
+ try:
266
+ yield
267
+ finally:
268
+ graph.close()
269
+
270
+
271
+ app = FastAPI(lifespan=lifespan)
272
+ # Use app.state.graph when constructing agents in your request handlers.
273
+ ```
274
+
275
+ Use one worker process per writable database, for example
276
+ `uvicorn app:app --workers 1`. Parent and subagents in that process reuse the
277
+ store. A replacement container must open the same mounted database after the old
278
+ process releases it; rolling deployments must not overlap database owners. A
279
+ single replica setting alone does not prevent overlap during replacement.
280
+
281
+ The developer configures the volume, permissions, and retention across container
282
+ replacement. An ordinary container filesystem is not durable. The package accepts
283
+ a filesystem path and has no cloud SDKs, blob uploads, or automatic LangSmith
284
+ storage integration. It cannot detect whether your mount survives redeployment.
285
+
286
+ [Azure Container Apps storage mounts](https://learn.microsoft.com/en-us/azure/container-apps/storage-mounts)
287
+ and [AWS ECS EFS volumes](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/efs-volumes.html)
288
+ describe provider setup. These links are not a claim of tested LadybugDB compatibility:
289
+ network filesystems must support LadybugDB's locking and file operations, and need
290
+ deployment-specific recovery tests. Local filesystem persistence is covered by
291
+ this package's tests; Azure Files and EFS have not been integration-tested here.
292
+
293
+ ## How It Works
294
+
295
+ ### Graph Traces
296
+
297
+ `record_graph_trace` records the core reasoning shape:
298
+
299
+ ```
300
+ Situation -> Rationale -> Action -> Outcome
301
+ ```
302
+
303
+ For example:
304
+
305
+ ```
306
+ Situation: "sheep saw lion"
307
+ Rationale: "lion is dangerous"
308
+ Action: "sheep ran away"
309
+ Outcome: "sheep survived"
310
+ ```
311
+
312
+ The graph stores edges like:
313
+
314
+ ```
315
+ sheep saw lion --LED_TO--> lion is dangerous
316
+ lion is dangerous --JUSTIFIED--> sheep ran away
317
+ sheep ran away --PRODUCED--> sheep survived
318
+ ```
319
+
320
+ Related findings can share a narrow `subject` within a namespace. Create it once
321
+ with `make_graph_subject(entity, aspect, environment)` and pass it to workers with
322
+ `graph_memory_tools(backend, bound_subject=subject)`. A worker may omit `subject`
323
+ when recording a trace; a different explicit subject is rejected. Keep changing
324
+ revisions in the trace context or evidence, and use separate subjects for environments
325
+ whose states should not be compared as one question. `observed_at` is
326
+ the time of the observation, while server-generated `recorded_at` is when the trace
327
+ was saved. A caller may explicitly supersede an earlier **state** finding only
328
+ with evidence and a strictly newer observation. The old trace and parallel newer
329
+ findings remain in the graph; explanations are never superseded by this rule.
330
+ For a reviewed disagreement between two or more same-subject traces, an evidenced
331
+ resolution trace can use `resolves=[...]`. Recall places that resolution ahead of
332
+ the reviewed originals and links them; this records a caller judgment, not a
333
+ database check of its truth.
334
+
335
+ ```python
336
+ from deepagents_graph_memory import GraphMemoryBackend
337
+ from deepagents_graph_memory.errors import GraphMemoryValidationError
338
+
339
+ graph = GraphMemoryBackend.create()
340
+ common = dict(subject="tests/test_parser.py::test_empty@linux", finding_type="state")
341
+ graph.record_graph_trace(
342
+ trace_id="failed", situation="parser test at revision a1", rationale="pytest exit 1",
343
+ action="ran pytest", outcome="failed", evidence=["pytest output at a1: failed"],
344
+ observed_at="2026-09-19T10:00:00Z", **common,
345
+ )
346
+ graph.record_graph_trace(
347
+ trace_id="passed", situation="parser test at revision b2", rationale="pytest exit 0",
348
+ action="reran pytest", outcome="passed", evidence=["pytest output at b2: passed"],
349
+ observed_at="2026-09-19T11:00:00Z", supersedes=["failed"], **common,
350
+ )
351
+ try:
352
+ graph.record_graph_trace(
353
+ trace_id="delayed", situation="late report from revision a1", rationale="old output",
354
+ action="reported result", outcome="failed", evidence=["pytest output at a1: failed"],
355
+ observed_at="2026-09-19T09:30:00Z", supersedes=["passed"], **common,
356
+ )
357
+ except GraphMemoryValidationError:
358
+ pass # An older observation cannot supersede the newer one.
359
+ print(graph.recall_graph_memory("failed", anchors=["/graph/nodes/Trace/failed.md"], max_nodes=2))
360
+ ```
361
+
362
+ The caller supplies the subject and supersession assertion. Graph storage does not
363
+ detect contradictions, verify the evidence, or gate external actions. Recall brings
364
+ same-subject findings together when budgets permit and flags incomplete context;
365
+ the reading agent must compare and verify material disagreements before deciding.
366
+ It ranks a single subject's terminal findings and resolutions before applying the
367
+ node limit, using observation time for order when present. Competing branches stay
368
+ visible when the budget fits them. A partial history is labeled incomplete, and
369
+ an omitted anchor gets a direct path to inspect. This currently scans one subject's
370
+ traces during recall, so very large subjects may cost more to read.
371
+
372
+ Structured `evidence_refs` identify captured sources across reports. The caller
373
+ assigns a distinct `source_id` to each actual execution or snapshot at collection;
374
+ `locator` points to its log, file, or document. Reusing that ID cites the same
375
+ source, while changing its locator, revision, or observation time raises an error.
376
+ Optional `summary` describes a reporter's reading and belongs to that citation.
377
+ Plain `evidence` strings still work, but are unstructured caller reports. Neither
378
+ kind of evidence is fetched or verified by the graph. Distinct cited sources are
379
+ not necessarily independent experiments, and partial recall cannot establish a
380
+ complete source count.
381
+
382
+ ```python
383
+ from deepagents_graph_memory import GraphMemoryBackend
384
+
385
+ graph = GraphMemoryBackend.create()
386
+ ref = {
387
+ "source_id": "pytest-run-17", "locator": "logs/pytest-run-17.txt",
388
+ "revision": "commit-a1", "observed_at": "2026-09-19T10:00:00Z",
389
+ }
390
+ for agent, summary in [("worker-a", "empty input failed"), ("worker-b", "parser failed")]:
391
+ graph.record_graph_trace(
392
+ situation="parser check", rationale="read pytest output", action="reported result",
393
+ outcome="failed", subject="parser@linux", agent_id=agent,
394
+ evidence_refs=[{**ref, "summary": summary}],
395
+ )
396
+ print(graph.recall_graph_memory("parser@linux", max_nodes=3))
397
+ ```
398
+
399
+ Conclusions can cite prior Trace IDs with `depends_on`. The targets must already
400
+ exist in the same namespace. Recall follows these links within its node, edge,
401
+ and depth budgets. If a cited finding is explicitly superseded or reviewed in a
402
+ resolution, recall marks the dependent conclusion `needs recheck` and shows the
403
+ changed premise's update path. This also applies through a chain of dependencies.
404
+ It flags review; it does not prove the decision false or change the original trace.
405
+ Missing evidence, cycles, or an incomplete dependency traversal produce an
406
+ unknown-status warning. A fresh conclusion should cite the current findings it
407
+ actually used.
408
+
409
+ ```python
410
+ from deepagents_graph_memory import GraphMemoryBackend
411
+
412
+ graph = GraphMemoryBackend.create()
413
+ graph.record_graph_trace(
414
+ trace_id="probe-failed", situation="checkout probe", rationale="run 1",
415
+ action="tested checkout", outcome="failed", subject="checkout@staging",
416
+ finding_type="state", observed_at="2026-09-19T10:00:00Z", evidence=["run 1 log"],
417
+ )
418
+ graph.record_graph_trace(
419
+ trace_id="mitigation", situation="checkout failed", rationale="probe-failed",
420
+ action="chose temporary mitigation", outcome="disable checkout",
421
+ depends_on=["probe-failed"],
422
+ )
423
+ graph.record_graph_trace(
424
+ trace_id="probe-passed", situation="checkout probe", rationale="run 2",
425
+ action="tested checkout", outcome="passed", subject="checkout@staging",
426
+ finding_type="state", observed_at="2026-09-19T11:00:00Z", evidence=["run 2 log"],
427
+ supersedes=["probe-failed"],
428
+ )
429
+ context = graph.recall_graph_memory("mitigation", anchors=["/graph/nodes/Trace/mitigation.md"])
430
+ assert "needs recheck" in context
431
+ ```
432
+
433
+ Use `operation_id` when retrying the same trace write. Replaying the same request
434
+ returns its original trace ID without changing the graph; changing the normalized
435
+ request under that ID raises `GraphMemoryValidationError`. A new execution needs a
436
+ new ID, even if its text is identical. The identity is scoped to the backend
437
+ namespace, and `operation_id` cannot be combined with `trace_id`.
438
+
439
+ ```python
440
+ from deepagents_graph_memory import GraphMemoryBackend
441
+
442
+ graph = GraphMemoryBackend.create()
443
+ payload = dict(situation="network timeout", rationale="probe failed", action="checked network", outcome="unavailable")
444
+ first = graph.record_graph_trace(operation_id="network-probe-7", **payload)
445
+ assert graph.record_graph_trace(operation_id="network-probe-7", **payload) == first
446
+ assert graph.record_graph_trace(operation_id="network-probe-8", **payload) != first
447
+ ```
448
+
449
+ The `record_graph_trace` tool uses its injected tool-call ID and runtime thread ID
450
+ for retries when no explicit `operation_id` is supplied. Direct calls without
451
+ either ID continue to append traces.
452
+
453
+ Writes are issued as LadybugDB Cypher `MERGE` statements (no raw Cypher is exposed to the agent).
454
+
455
+ ### Graph Recall
456
+
457
+ `recall_graph_memory` searches for seed facts, expands through useful edges, and returns compact markdown with source `/graph/...` paths. Pass anchors (file paths, run IDs, task IDs) to give recall a concrete starting point.
458
+
459
+ Matching generated trace components and paths are summarized once in recall. Explicit component anchors, custom nodes with different content, and direct `/graph/...` debug views remain available.
460
+
461
+ Recall uses LadybugDB keyword search to find seed nodes, then bounded Cypher `MATCH` traversal to expand connected context. The token budget estimates output size; it is not an exact token cap. No vector/embedding search is used.
462
+
463
+ ```python
464
+ graph_backend.recall_graph_memory("what services did incident 123 affect and what do they depend on?")
465
+ ```
466
+
467
+ ## Graph Tools
468
+
469
+ ```python
470
+ graph_memory_tools(graph_backend)
471
+ ```
472
+
473
+ | Tool | Purpose |
474
+ |---|---|
475
+ | `recall_graph_memory` | Primary read path -- search, expand, return context |
476
+ | `record_graph_trace` | High-level write -- Situation &rarr; Rationale &rarr; Action &rarr; Outcome |
477
+
478
+ Low-level write tools are available for application builders but not exposed by default, since unconstrained agents can create drifting labels and relationship types:
479
+
480
+ ```python
481
+ graph_memory_tools(graph_backend, include_low_level_writes=True)
482
+ ```
483
+
484
+ | Tool | Purpose |
485
+ |---|---|
486
+ | `add_graph_node` | Create or update entities |
487
+ | `add_graph_edge` | Create or update relationships |
488
+ | `add_graph_documents` | Ingest LangChain documents |
489
+
490
+ For production use, prefer domain-specific tools that call `graph_backend.add_graph_node(...)` and `graph_backend.add_graph_edge(...)` with your application's approved labels and relationships.
491
+
492
+ ## Virtual Graph Views
493
+
494
+ The backend projects graph state into read-only markdown paths:
495
+
496
+ | Path | Description |
497
+ |---|---|
498
+ | `/graph/schema.md` | Current graph schema |
499
+ | `/graph/index.md` | Graph memory landing page |
500
+ | `/graph/nodes/{label}/{id}.md` | Single node with properties and relationships |
501
+ | `/graph/search/{query}.md` | Search results with preview text |
502
+
503
+ These are generated virtual paths, not files on disk or browser links. Node pages include stored properties, provenance when present, and bounded one-hop relationships. Writes go through graph tools, not file operations.
504
+
505
+ ### Inspecting the graph
506
+
507
+ Use backend methods to check a recorded fact directly, even when recall does not select it. This example runs locally without an LLM or provider key:
508
+
509
+ ```python
510
+ from deepagents_graph_memory import GraphMemoryBackend
511
+
512
+ backend = GraphMemoryBackend.create()
513
+ trace_id = backend.record_graph_trace(
514
+ trace_id="debug-trace-1",
515
+ situation="scope test failed",
516
+ rationale="a graph read might use the wrong scope",
517
+ action="checked the read path",
518
+ outcome="scope test passed",
519
+ source="tests/test_scope.py",
520
+ )
521
+
522
+ print(trace_id)
523
+ print([entry["path"] for entry in backend.ls("/graph/nodes/Trace/").entries])
524
+ node_path = f"/graph/nodes/Trace/{trace_id}.md"
525
+ node = backend.read(node_path)
526
+ if node.error:
527
+ raise RuntimeError(node.error)
528
+ print(node.file_data["content"])
529
+ print(backend.read("/graph/schema.md").file_data["content"])
530
+ print(backend.read("/graph/search/scope.md").file_data["content"])
531
+ ```
532
+
533
+ For a missing answer, read the exact known node path first. A "not found" error means that node is absent from the active scope; a returned page lets you inspect its text, provenance, and relationships. Then try `backend.recall_graph_memory("scope test", anchors=[node_path])` to start recall at that node. `ls()` helps discover paths. If a directory exceeds `max_nodes`, listing and recursive file discovery return an error instead of silently omitting files; increase `max_nodes` or use a known node path. Inspection bypasses recall's relevance selection, while node relationships still obey `max_nodes` and `max_edges`. `read()` also accepts line `offset` and `limit`.
534
+
535
+ `grep()` searches the rendered Markdown for case-sensitive literal text and returns
536
+ matching lines with their line numbers. Use `/graph/search/{query}.md` or
537
+ `recall_graph_memory()` for ranked keyword search. `glob()` matches relative to its
538
+ `path`: `*.md` matches filenames at any depth, `/*.md` matches only the root, and
539
+ `nodes/**/*.md` matches paths under `nodes/`. Directory paths work with or without
540
+ a trailing slash. Reads preserve newlines and provide pagination metadata when
541
+ the installed Deep Agents version supports it. On current Deep Agents,
542
+ `grep(..., max_count=10)` reports `truncated=True` when more matching lines exist;
543
+ older versions without truncation metadata return an error if the cap is exceeded.
544
+ Async variants follow the installed Deep Agents protocol, including its supported
545
+ arguments.
546
+
547
+ ## Optional Graph-Only Mode
548
+
549
+ For applications that deliberately omit filesystem tools, the existing
550
+ `register_vgs_harness_profile(model)` helper enables graph-only behavior:
551
+
552
+ - Deep Agents default VFS tools are hidden: `ls`, `read_file`, `write_file`, `edit_file`, `delete`, `glob`, `grep` (where available)
553
+ - VGS prompt guidance is added
554
+ - The caller passes `graph_memory_tools(graph_backend)` explicitly; the profile does not install them
555
+
556
+ With file tools hidden, agents use `recall_graph_memory`, which queries the graph store directly. `memory=["/graph/index.md", "/graph/schema.md"]` loads those two views into agent context; it does not enable interactive graph browsing. Developers can still inspect through `GraphMemoryBackend.ls()`, `read()`, `glob()`, and `grep()`.
557
+
558
+ Use the combined setup above when the agent needs files or offloaded tool output.
559
+ The graph-only helper changes the profile for a model key throughout the process;
560
+ do not register it for a model used by combined-mode agents. Adding
561
+ `graph_context_middleware()` does not undo an existing tool exclusion. Graph tools
562
+ remain opt-in for ordinary Deep Agents applications.
563
+ Application-supplied instructions are preserved. Older Deep Agents releases'
564
+ automatically added filesystem guidance is removed in graph-only mode. Tool
565
+ exclusion controls model exposure; it is not an authorization boundary.
566
+
567
+ ## Architecture
568
+
569
+ ### System Overview
570
+
571
+ ```mermaid
572
+ graph TB
573
+ Agent["LangChain Deep Agent"]
574
+
575
+ subgraph VGS ["Virtual Graph System"]
576
+ Tools["Graph Tools<br/><i>tools.py</i>"]
577
+ Backend["GraphMemoryBackend<br/><i>backend.py</i>"]
578
+ Recall["Recall Engine<br/><i>recall.py</i>"]
579
+ Renderers["Markdown Renderers<br/><i>renderers.py</i>"]
580
+ Paths["Path Parser<br/><i>paths.py</i>"]
581
+ VGSProfile["Graph Context Guidance<br/><i>vgs.py</i>"]
582
+ end
583
+
584
+ subgraph Store ["Storage"]
585
+ LadybugDB["LadybugDB Graph: Memory or Disk<br/><i>ladybug_store.py</i>"]
586
+ end
587
+
588
+ Agent -->|"record_graph_trace<br/>recall_graph_memory"| Tools
589
+ Agent -->|"file tools"| VFS["VFS: Files and Tool Dumps"]
590
+ Tools --> Backend
591
+ Backend --> Recall
592
+ Backend --> Renderers
593
+ Backend --> Paths
594
+ Recall --> LadybugDB
595
+ Renderers --> LadybugDB
596
+ VGSProfile -.->|"guides graph and file use"| Agent
597
+
598
+ style VGS fill:#1e293b,stroke:#334155,color:#e2e8f0
599
+ style Store fill:#1e293b,stroke:#334155,color:#e2e8f0
600
+ ```
601
+
602
+ ### Combined and Graph-Only Setups
603
+
604
+ ```mermaid
605
+ graph LR
606
+ subgraph Combined ["Combined Setup"]
607
+ VFS["VFS Tools<br/>ls, read_file, write_file,<br/>edit_file, glob, grep"]
608
+ GraphTools["Graph Tools<br/>recall_graph_memory<br/>record_graph_trace"]
609
+ GraphTools -->|"evidence references"| VFS
610
+ end
611
+
612
+ subgraph VGSMode ["Optional Graph-Only Profile"]
613
+ VFSHidden["VFS Tools<br/><s>hidden</s>"]
614
+ OnlyGraph["Graph Tools"]
615
+ end
616
+
617
+ VFSHidden ~~~ OnlyGraph
618
+
619
+ style VFSHidden fill:#991b1b,stroke:#7f1d1d,color:#fecaca
620
+ style GraphTools fill:#065f46,stroke:#064e3b,color:#a7f3d0
621
+ ```
622
+
623
+ ### Recall Pipeline
624
+
625
+ ```mermaid
626
+ graph LR
627
+ Q["Query or Anchors"] --> Seed["Seed Search<br/><i>full-text + relationship labels</i>"]
628
+ Seed --> Expand["Graph Expansion<br/><i>bounded traversal</i>"]
629
+ Expand --> Budget["Budget Enforcement<br/><i>tokens, depth, nodes, edges</i>"]
630
+ Budget --> MD["Markdown Output<br/><i>with /graph/... source paths</i>"]
631
+ ```
632
+
633
+ ### Trace Data Model
634
+
635
+ ```mermaid
636
+ graph LR
637
+ S["Situation"] -->|LED_TO| R["Rationale"]
638
+ R -->|JUSTIFIED| A["Action"]
639
+ A -->|PRODUCED| O["Outcome"]
640
+
641
+ style S fill:#7c3aed,stroke:#6d28d9,color:#ede9fe
642
+ style R fill:#2563eb,stroke:#1d4ed8,color:#dbeafe
643
+ style A fill:#d97706,stroke:#b45309,color:#fef3c7
644
+ style O fill:#059669,stroke:#047857,color:#d1fae5
645
+ ```
646
+
647
+ ### Module Reference
648
+
649
+ | Component | Module | Description |
650
+ |---|---|---|
651
+ | **Backend** | `backend.py` | `BackendProtocol` implementation for Deep Agents |
652
+ | **Graph Store** | `ladybug_store.py` | LadybugDB adapter with FTS indexing and scoped queries |
653
+ | **Recall Engine** | `recall.py` | Seed search &rarr; expansion &rarr; budget enforcement &rarr; markdown output |
654
+ | **Tools** | `tools.py` | LangChain tools with error boundaries |
655
+ | **Renderers** | `renderers.py` | Graph data &rarr; markdown view projections |
656
+ | **Paths** | `paths.py` | Virtual path parsing and validation |
657
+ | **Graph Guidance** | `vgs.py` | Agent-local combined guidance and optional graph-only profiles |
658
+ | **Errors** | `errors.py` | `GraphMemoryError`, `GraphMemoryConfigurationError`, `GraphMemoryPathError`, `GraphMemoryValidationError` |
659
+
660
+ ## Example Domain: SRE
661
+
662
+ VGS is domain-agnostic; here is one example of how a project might shape its labels and relationships:
663
+
664
+ ```
665
+ Langfuse DEPENDS_ON Redis
666
+ Langfuse DEPENDS_ON Postgres
667
+ SRE Team OWNS Langfuse
668
+ Incident 123 AFFECTED Langfuse
669
+ Incident 123 RESOLVED_BY "Restart Ingestion Workers" Runbook
670
+ ```
671
+
672
+ Recall query:
673
+ ```python
674
+ graph_backend.recall_graph_memory("what services did incident 123 affect and what do they depend on?")
675
+ ```
676
+
677
+ ## Installation
678
+
679
+ ```bash
680
+ pip install deepagents-graph-memory
681
+ # Optional test tools:
682
+ pip install "deepagents-graph-memory[test]"
683
+ ```
684
+
685
+ ### Full-text search setup
686
+
687
+ LadybugDB 0.20.3 requires a separately installed `fts` extension for graph search
688
+ and recall. A fresh `pip install` does not provide it. Satisfy the native runtime
689
+ [requirements](#requirements) first, then run this once during
690
+ development setup or your image build with network access, following the
691
+ [official extension installation](https://docs.ladybugdb.com/extensions/#install-an-extension):
692
+
693
+ ```python
694
+ import ladybug
695
+
696
+ with ladybug.Database(":memory:", buffer_pool_size=64 * 1024 * 1024) as database:
697
+ with ladybug.Connection(database) as connection:
698
+ connection.execute("INSTALL fts;").close()
699
+ connection.execute("LOAD fts;").close()
700
+ ```
701
+
702
+ Provision for the same LadybugDB version, OS, architecture, and runtime user.
703
+ Keep the installed extension cache available in the runtime image or volume;
704
+ building as a different user does not make that user's cache available to your
705
+ application. After provisioning, runtime search loads the local extension.
706
+ The adapter does not download extensions or fall back to another search method;
707
+ a missing extension raises a configuration error.
708
+
709
+ ### Requirements
710
+
711
+ - Python 3.11–3.14 (`>=3.11,<3.15`)
712
+ - Deep Agents 0.6.10 or later (`>=0.6.10`)
713
+ - LadybugDB via `ladybug==0.20.3`
714
+ - An OpenSSL 3 runtime available to LadybugDB's native library; `pip` does not install it.
715
+
716
+ Fresh installations can resolve the newest Deep Agents release; applications can
717
+ also lock an older supported version. CI tests the latest release across the
718
+ platform matrix and runs separate compatibility checks with 0.6.10, 0.6.12, and
719
+ 0.7.1. The current tested latest release is 0.7.15. Future releases still need
720
+ those checks to pass; an open dependency range is not a compatibility guarantee.
721
+ Versions before 0.6.10 are outside the supported range; the former 0.5.2 minimum
722
+ lacks the harness-profile API this package uses.
723
+
724
+ Use your platform's maintained OpenSSL 3 runtime. On Windows, install the matching
725
+ architecture from a maintained distribution such as
726
+ [Shining Light Productions](https://slproweb.com/products/Win32OpenSSL.html).
727
+ The native library needs `libssl-3-x64.dll` and `libcrypto-3-x64.dll` on x64;
728
+ select the installer's option to copy DLLs to the Windows system directory.
729
+ If your application keeps them elsewhere, register that directory with
730
+ [`os.add_dll_directory`](https://docs.python.org/3/library/os.html#os.add_dll_directory)
731
+ before importing Ladybug and keep the returned handle alive; each worker process
732
+ needs the same setup. PATH alone and importing Python's `ssl` module do not satisfy
733
+ this requirement. See [Ladybug's Windows dependency guidance](https://github.com/LadybugDB/ladybug/issues/775).
734
+
735
+ The exact pin excludes the Windows FTS ABI regression in 0.20.4 reported in
736
+ [upstream issue #971](https://github.com/LadybugDB/ladybug/issues/971).
737
+
738
+ The [Ladybug 0.20.3 wheels](https://pypi.org/project/ladybug/0.20.3/#files)
739
+ include macOS 15+ (Intel and Apple Silicon), Linux, and Windows builds for
740
+ x86-64 and ARM64. Check the available wheel for your Python version, architecture,
741
+ and OS; wheel availability does not establish that this package passed tests on
742
+ every combination.
743
+
744
+ ## Development
745
+
746
+ ```bash
747
+ git clone https://github.com/TahaK29/deepagents-graph-memory.git
748
+ cd deepagents-graph-memory
749
+ pip install -e ".[test]"
750
+ python3 -m pytest -q # Run all tests
751
+ python3 -m ruff check . # Lint
752
+ ```
753
+
754
+ ## Design
755
+
756
+ `GraphMemoryBackend.create()` creates a LadybugDB in-memory graph via `ladybug.Database(":memory:")`. Supply `path=...` to create or reopen a disk database. Persistence depends on retaining that filesystem across restarts; each writable database has one owning process. Both modes use the same graph tools and namespace rules.
757
+
758
+ Recall uses full-text search to find seed nodes, relationship-label search for relationship-oriented questions, and bounded graph traversal to recover connected context. Vector search and graph algorithms are intentionally not part of the default recall path.
759
+
760
+ Raw Cypher is not exposed as an agent-facing read or write path. Generated graph views are read-only projections.
761
+
762
+ For the full design rationale, see [DESIGN.md](https://github.com/TahaK29/deepagents-graph-memory/blob/main/DESIGN.md).
763
+
764
+ ## License
765
+
766
+ MIT
767
+
768
+ ---
769
+
770
+ **deepagents-graph-memory** is experimental. [GitHub Issues](https://github.com/TahaK29/deepagents-graph-memory/issues)