agentmem-mcp 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.
agent_memory/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
agent_memory/server.py
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
from mcp.server.fastmcp import FastMCP
|
|
2
|
+
from .storage import MemoryStorage, CATEGORIES
|
|
3
|
+
|
|
4
|
+
mcp = FastMCP(
|
|
5
|
+
"agent-memory",
|
|
6
|
+
instructions=(
|
|
7
|
+
"Persistent graph-backed memory for AI agents. "
|
|
8
|
+
"Use `remember` to store facts, `recall` to search, "
|
|
9
|
+
"`get_related` to traverse the memory graph, "
|
|
10
|
+
"`list_memories` to browse by category, `forget` to delete."
|
|
11
|
+
),
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
_store = MemoryStorage()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@mcp.tool()
|
|
18
|
+
def remember(content: str, category: str = "fact", tags: str = "") -> str:
|
|
19
|
+
"""Store a memory and auto-link it to related existing memories.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
content: The fact, observation, or decision to remember.
|
|
23
|
+
category: One of fact, tool, preference, context, relationship, task, decision.
|
|
24
|
+
tags: Comma-separated keywords for later retrieval.
|
|
25
|
+
"""
|
|
26
|
+
if category not in CATEGORIES:
|
|
27
|
+
category = "fact"
|
|
28
|
+
mid = _store.add(content, category, tags)
|
|
29
|
+
edges = _store.edges(mid)
|
|
30
|
+
result = f"Stored [{mid}] ({category}): {content}"
|
|
31
|
+
if edges:
|
|
32
|
+
linked = [f"{r} → [{eid}]" for r, eid, _, _ in edges[:3]]
|
|
33
|
+
result += f"\nAuto-linked: {', '.join(linked)}"
|
|
34
|
+
return result
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@mcp.tool()
|
|
38
|
+
def recall(query: str, limit: int = 8) -> str:
|
|
39
|
+
"""Search memories by keyword and return matches with their graph connections.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
query: Keywords to search for.
|
|
43
|
+
limit: Maximum number of memories to return (default 8).
|
|
44
|
+
"""
|
|
45
|
+
rows = _store.search(query, min(limit, 20))
|
|
46
|
+
if not rows:
|
|
47
|
+
return f"No memories found for: {query}"
|
|
48
|
+
|
|
49
|
+
lines = [f"Found {len(rows)} memory/memories for '{query}':\n"]
|
|
50
|
+
for mid, content, category, tags, created_at in rows:
|
|
51
|
+
lines.append(f"[{mid}] ({category}) {content}")
|
|
52
|
+
if tags:
|
|
53
|
+
lines.append(f" tags: {tags}")
|
|
54
|
+
edges = _store.edges(mid)
|
|
55
|
+
if edges:
|
|
56
|
+
for rel, eid, econtent, _ in edges[:2]:
|
|
57
|
+
lines.append(f" -{rel}→ [{eid}] {econtent[:60]}...")
|
|
58
|
+
lines.append("")
|
|
59
|
+
return "\n".join(lines)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@mcp.tool()
|
|
63
|
+
def get_related(memory_id: str, depth: int = 2) -> str:
|
|
64
|
+
"""Traverse the memory graph outward from a specific memory.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
memory_id: The ID of the memory to start traversal from.
|
|
68
|
+
depth: How many hops to traverse (1–3, default 2).
|
|
69
|
+
"""
|
|
70
|
+
root = _store.get(memory_id)
|
|
71
|
+
if not root:
|
|
72
|
+
return f"Memory [{memory_id}] not found."
|
|
73
|
+
|
|
74
|
+
depth = max(1, min(depth, 3))
|
|
75
|
+
visited = {memory_id}
|
|
76
|
+
lines = [f"Graph from [{memory_id}]: {root[1]}\n"]
|
|
77
|
+
queue = [(memory_id, 0)]
|
|
78
|
+
|
|
79
|
+
while queue:
|
|
80
|
+
mid, d = queue.pop(0)
|
|
81
|
+
if d >= depth:
|
|
82
|
+
continue
|
|
83
|
+
for rel, eid, econtent, ecat in _store.edges(mid):
|
|
84
|
+
marker = " " * (d + 1)
|
|
85
|
+
lines.append(f"{marker}-{rel}→ [{eid}] ({ecat}) {econtent}")
|
|
86
|
+
if eid not in visited:
|
|
87
|
+
visited.add(eid)
|
|
88
|
+
queue.append((eid, d + 1))
|
|
89
|
+
|
|
90
|
+
if len(lines) == 2:
|
|
91
|
+
lines.append(" (no connected memories)")
|
|
92
|
+
return "\n".join(lines)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@mcp.tool()
|
|
96
|
+
def link_memories(source_id: str, target_id: str, relationship: str) -> str:
|
|
97
|
+
"""Manually create a typed edge between two memories.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
source_id: The source memory ID.
|
|
101
|
+
target_id: The target memory ID.
|
|
102
|
+
relationship: Relationship type, e.g. DEPENDS_ON, SUPPORTS, CONTRADICTS, PRECEDES.
|
|
103
|
+
"""
|
|
104
|
+
if not _store.get(source_id):
|
|
105
|
+
return f"Memory [{source_id}] not found."
|
|
106
|
+
if not _store.get(target_id):
|
|
107
|
+
return f"Memory [{target_id}] not found."
|
|
108
|
+
ok = _store.link(source_id, target_id, relationship)
|
|
109
|
+
if ok:
|
|
110
|
+
return f"Linked [{source_id}] -{relationship.upper()}→ [{target_id}]"
|
|
111
|
+
return f"Edge already exists or could not be created."
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@mcp.tool()
|
|
115
|
+
def list_memories(category: str = "", limit: int = 20) -> str:
|
|
116
|
+
"""List stored memories, optionally filtered by category.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
category: Filter to one of: fact, tool, preference, context, relationship, task, decision.
|
|
120
|
+
limit: Maximum memories to return (default 20).
|
|
121
|
+
"""
|
|
122
|
+
rows = _store.list_all(category, min(limit, 50))
|
|
123
|
+
if not rows:
|
|
124
|
+
label = f" in category '{category}'" if category else ""
|
|
125
|
+
return f"No memories{label}."
|
|
126
|
+
|
|
127
|
+
stats = _store.stats()
|
|
128
|
+
header = (
|
|
129
|
+
f"{stats['memories']} total memories · {stats['edges']} edges"
|
|
130
|
+
+ (f" · showing '{category}'" if category else "")
|
|
131
|
+
+ "\n"
|
|
132
|
+
)
|
|
133
|
+
lines = [header]
|
|
134
|
+
for mid, content, cat, tags, created_at in rows:
|
|
135
|
+
ts = created_at[:10]
|
|
136
|
+
lines.append(f"[{mid}] {ts} ({cat}) {content}")
|
|
137
|
+
return "\n".join(lines)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@mcp.tool()
|
|
141
|
+
def forget(memory_id: str) -> str:
|
|
142
|
+
"""Delete a memory and all its graph edges.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
memory_id: The ID of the memory to delete.
|
|
146
|
+
"""
|
|
147
|
+
row = _store.get(memory_id)
|
|
148
|
+
if not row:
|
|
149
|
+
return f"Memory [{memory_id}] not found."
|
|
150
|
+
_store.delete(memory_id)
|
|
151
|
+
return f"Deleted [{memory_id}]: {row[1]}"
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def main():
|
|
155
|
+
mcp.run()
|
agent_memory/storage.py
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import sqlite3
|
|
2
|
+
import uuid
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
STOP_WORDS = {
|
|
7
|
+
"a", "an", "the", "is", "in", "it", "of", "to", "and", "or", "for",
|
|
8
|
+
"with", "on", "at", "from", "by", "as", "be", "was", "are", "has",
|
|
9
|
+
"have", "had", "not", "this", "that", "i", "you", "we", "they",
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
CATEGORIES = ("fact", "tool", "preference", "context", "relationship", "task", "decision")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class MemoryStorage:
|
|
16
|
+
def __init__(self, db_path: str = None):
|
|
17
|
+
if db_path is None:
|
|
18
|
+
db_path = Path.home() / ".agent-memory" / "memories.db"
|
|
19
|
+
path = Path(db_path)
|
|
20
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
21
|
+
self.conn = sqlite3.connect(str(path), check_same_thread=False)
|
|
22
|
+
self._init()
|
|
23
|
+
|
|
24
|
+
def _init(self):
|
|
25
|
+
self.conn.executescript("""
|
|
26
|
+
CREATE TABLE IF NOT EXISTS memories (
|
|
27
|
+
id TEXT PRIMARY KEY,
|
|
28
|
+
content TEXT NOT NULL,
|
|
29
|
+
category TEXT DEFAULT 'fact',
|
|
30
|
+
tags TEXT DEFAULT '',
|
|
31
|
+
created_at TEXT NOT NULL
|
|
32
|
+
);
|
|
33
|
+
CREATE TABLE IF NOT EXISTS edges (
|
|
34
|
+
id TEXT PRIMARY KEY,
|
|
35
|
+
source_id TEXT NOT NULL,
|
|
36
|
+
target_id TEXT NOT NULL,
|
|
37
|
+
relationship TEXT NOT NULL,
|
|
38
|
+
created_at TEXT NOT NULL,
|
|
39
|
+
UNIQUE(source_id, target_id, relationship),
|
|
40
|
+
FOREIGN KEY (source_id) REFERENCES memories(id) ON DELETE CASCADE,
|
|
41
|
+
FOREIGN KEY (target_id) REFERENCES memories(id) ON DELETE CASCADE
|
|
42
|
+
);
|
|
43
|
+
PRAGMA foreign_keys = ON;
|
|
44
|
+
CREATE INDEX IF NOT EXISTS idx_memories_category ON memories(category);
|
|
45
|
+
CREATE INDEX IF NOT EXISTS idx_edges_source ON edges(source_id);
|
|
46
|
+
CREATE INDEX IF NOT EXISTS idx_edges_target ON edges(target_id);
|
|
47
|
+
""")
|
|
48
|
+
self.conn.commit()
|
|
49
|
+
|
|
50
|
+
def _new_id(self) -> str:
|
|
51
|
+
return str(uuid.uuid4())[:8]
|
|
52
|
+
|
|
53
|
+
def add(self, content: str, category: str, tags: str) -> str:
|
|
54
|
+
mid = self._new_id()
|
|
55
|
+
self.conn.execute(
|
|
56
|
+
"INSERT INTO memories VALUES (?, ?, ?, ?, ?)",
|
|
57
|
+
(mid, content, category, tags, datetime.utcnow().isoformat()),
|
|
58
|
+
)
|
|
59
|
+
self.conn.commit()
|
|
60
|
+
self._auto_link(mid, content)
|
|
61
|
+
return mid
|
|
62
|
+
|
|
63
|
+
def _auto_link(self, new_id: str, content: str):
|
|
64
|
+
words = {w for w in content.lower().split() if w not in STOP_WORDS and len(w) > 3}
|
|
65
|
+
seen = set()
|
|
66
|
+
for word in list(words)[:6]:
|
|
67
|
+
rows = self.conn.execute(
|
|
68
|
+
"SELECT id FROM memories WHERE LOWER(content) LIKE ? AND id != ? LIMIT 4",
|
|
69
|
+
(f"%{word}%", new_id),
|
|
70
|
+
).fetchall()
|
|
71
|
+
for (rid,) in rows:
|
|
72
|
+
if rid not in seen:
|
|
73
|
+
seen.add(rid)
|
|
74
|
+
eid = self._new_id()
|
|
75
|
+
self.conn.execute(
|
|
76
|
+
"INSERT OR IGNORE INTO edges VALUES (?, ?, ?, 'RELATES_TO', ?)",
|
|
77
|
+
(eid, new_id, rid, datetime.utcnow().isoformat()),
|
|
78
|
+
)
|
|
79
|
+
if seen:
|
|
80
|
+
self.conn.commit()
|
|
81
|
+
|
|
82
|
+
def link(self, source_id: str, target_id: str, relationship: str) -> bool:
|
|
83
|
+
eid = self._new_id()
|
|
84
|
+
try:
|
|
85
|
+
self.conn.execute(
|
|
86
|
+
"INSERT OR IGNORE INTO edges VALUES (?, ?, ?, ?, ?)",
|
|
87
|
+
(eid, source_id, target_id, relationship.upper(), datetime.utcnow().isoformat()),
|
|
88
|
+
)
|
|
89
|
+
self.conn.commit()
|
|
90
|
+
return True
|
|
91
|
+
except Exception:
|
|
92
|
+
return False
|
|
93
|
+
|
|
94
|
+
def search(self, query: str, limit: int = 10) -> list:
|
|
95
|
+
terms = [t for t in query.lower().split() if t not in STOP_WORDS]
|
|
96
|
+
if not terms:
|
|
97
|
+
terms = query.lower().split()
|
|
98
|
+
conditions = " OR ".join(["LOWER(content) LIKE ?" for _ in terms])
|
|
99
|
+
params = [f"%{t}%" for t in terms] + [limit]
|
|
100
|
+
return self.conn.execute(
|
|
101
|
+
f"SELECT id, content, category, tags, created_at FROM memories "
|
|
102
|
+
f"WHERE {conditions} ORDER BY created_at DESC LIMIT ?",
|
|
103
|
+
params,
|
|
104
|
+
).fetchall()
|
|
105
|
+
|
|
106
|
+
def get(self, mid: str) -> tuple | None:
|
|
107
|
+
return self.conn.execute(
|
|
108
|
+
"SELECT id, content, category, tags, created_at FROM memories WHERE id = ?",
|
|
109
|
+
(mid,),
|
|
110
|
+
).fetchone()
|
|
111
|
+
|
|
112
|
+
def edges(self, mid: str) -> list:
|
|
113
|
+
return self.conn.execute(
|
|
114
|
+
"""SELECT e.relationship, m.id, m.content, m.category
|
|
115
|
+
FROM edges e JOIN memories m ON e.target_id = m.id
|
|
116
|
+
WHERE e.source_id = ?
|
|
117
|
+
UNION
|
|
118
|
+
SELECT e.relationship, m.id, m.content, m.category
|
|
119
|
+
FROM edges e JOIN memories m ON e.source_id = m.id
|
|
120
|
+
WHERE e.target_id = ?""",
|
|
121
|
+
(mid, mid),
|
|
122
|
+
).fetchall()
|
|
123
|
+
|
|
124
|
+
def list_all(self, category: str = "", limit: int = 20) -> list:
|
|
125
|
+
if category:
|
|
126
|
+
return self.conn.execute(
|
|
127
|
+
"SELECT id, content, category, tags, created_at FROM memories "
|
|
128
|
+
"WHERE category = ? ORDER BY created_at DESC LIMIT ?",
|
|
129
|
+
(category, limit),
|
|
130
|
+
).fetchall()
|
|
131
|
+
return self.conn.execute(
|
|
132
|
+
"SELECT id, content, category, tags, created_at FROM memories "
|
|
133
|
+
"ORDER BY created_at DESC LIMIT ?",
|
|
134
|
+
(limit,),
|
|
135
|
+
).fetchall()
|
|
136
|
+
|
|
137
|
+
def delete(self, mid: str) -> bool:
|
|
138
|
+
cur = self.conn.execute("DELETE FROM memories WHERE id = ?", (mid,))
|
|
139
|
+
self.conn.commit()
|
|
140
|
+
return cur.rowcount > 0
|
|
141
|
+
|
|
142
|
+
def stats(self) -> dict:
|
|
143
|
+
m = self.conn.execute("SELECT COUNT(*) FROM memories").fetchone()[0]
|
|
144
|
+
e = self.conn.execute("SELECT COUNT(*) FROM edges").fetchone()[0]
|
|
145
|
+
cats = self.conn.execute(
|
|
146
|
+
"SELECT category, COUNT(*) FROM memories GROUP BY category"
|
|
147
|
+
).fetchall()
|
|
148
|
+
return {"memories": m, "edges": e, "by_category": dict(cats)}
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agentmem-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Persistent graph-backed memory for AI agents via MCP — store, traverse, and recall knowledge across sessions
|
|
5
|
+
Project-URL: Homepage, https://graphifymd.com
|
|
6
|
+
Project-URL: Repository, https://github.com/Yarmoluk/agent-memory-mcp
|
|
7
|
+
Project-URL: Issues, https://github.com/Yarmoluk/agent-memory-mcp/issues
|
|
8
|
+
Author-email: Daniel Yarmoluk <daniel.yarmoluk@gmail.com>
|
|
9
|
+
License: MIT
|
|
10
|
+
Keywords: agent,agentiq,ai-agents,fastmcp,knowledge-graph,llm,mcp,memory,nvidia,persistent-memory
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Requires-Dist: mcp[cli]>=1.0.0
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# agent-memory-mcp
|
|
24
|
+
|
|
25
|
+
**Give your agents memory. Give them direction. Open the context window.**
|
|
26
|
+
|
|
27
|
+
The context window is the most expensive real estate in AI development. Most teams fill it with the same documents, the same re-derived dependencies, the same facts the agent already knew from last session. The window bloats. The cost compounds. The agent still starts from scratch.
|
|
28
|
+
|
|
29
|
+
`agent-memory-mcp` solves the other half of the problem.
|
|
30
|
+
|
|
31
|
+
Not retrieval — traversal. Your agent doesn't search for what looks similar. It walks declared relationships: what came before, what depends on what, what decision caused what outcome. Persistent across sessions. Zero external services. One config entry.
|
|
32
|
+
|
|
33
|
+
Combined with a sealed knowledge appliance ([ckg-mcp](https://pypi.org/project/ckg-mcp/)), your agent has both layers of what a production system needs: memory for what it *did*, and structured knowledge for what's *true*. That's not a wider context window — that's a smarter one.
|
|
34
|
+
|
|
35
|
+
[](https://pypi.org/project/agent-memory-mcp/)
|
|
36
|
+
[](LICENSE)
|
|
37
|
+
|
|
38
|
+

|
|
39
|
+
|
|
40
|
+
> ⭐ **If this saves you tokens, star it** — it's how other developers find it.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## The problem
|
|
45
|
+
|
|
46
|
+
Most teams give their agents a document dump and call it knowledge. Most agents start every session with no sense of where they've been. The result:
|
|
47
|
+
|
|
48
|
+
- Re-explaining your stack, your preferences, your constraints — every session
|
|
49
|
+
- Re-discovering which tool worked and which didn't
|
|
50
|
+
- Re-fetching context the agent already built last time
|
|
51
|
+
- Burning tokens on what the agent already knew
|
|
52
|
+
- No memory of decisions made, no record of what was tried and failed
|
|
53
|
+
|
|
54
|
+
This isn't a model problem. It's a memory architecture problem — and it compounds with every agent call you pay for.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Install
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
# Run directly — no install needed
|
|
62
|
+
uvx agent-memory-mcp
|
|
63
|
+
|
|
64
|
+
# Or install
|
|
65
|
+
pip install agent-memory-mcp
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## MCP config
|
|
71
|
+
|
|
72
|
+
### Claude Desktop
|
|
73
|
+
|
|
74
|
+
```json
|
|
75
|
+
{
|
|
76
|
+
"mcpServers": {
|
|
77
|
+
"agent-memory": {
|
|
78
|
+
"command": "uvx",
|
|
79
|
+
"args": ["agent-memory-mcp"]
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Cursor / Windsurf / NVIDIA AgentIQ
|
|
86
|
+
|
|
87
|
+
Same config — drop it in your `mcp_servers.json`. Any MCP-compatible client works.
|
|
88
|
+
|
|
89
|
+
Memories persist to `~/.agent-memory/memories.db` — SQLite, no external services, no API keys.
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Tools
|
|
94
|
+
|
|
95
|
+
| Tool | What it does |
|
|
96
|
+
|---|---|
|
|
97
|
+
| `remember(content, category, tags)` | Store a memory. Auto-links to related existing memories. |
|
|
98
|
+
| `recall(query, limit)` | Search by keyword. Returns matches with their graph connections. |
|
|
99
|
+
| `get_related(memory_id, depth)` | Traverse the graph outward 1–3 hops from a memory. |
|
|
100
|
+
| `link_memories(source, target, relationship)` | Declare a typed edge between two memories. |
|
|
101
|
+
| `list_memories(category, limit)` | Browse by category: fact, tool, decision, preference, context… |
|
|
102
|
+
| `forget(memory_id)` | Delete a memory and all its edges. |
|
|
103
|
+
|
|
104
|
+
**Categories:** `fact` · `tool` · `preference` · `context` · `relationship` · `task` · `decision`
|
|
105
|
+
|
|
106
|
+
**Edge types:** `DEPENDS_ON` · `SUPPORTS` · `CONTRADICTS` · `PRECEDES` · `CAUSES` · `RELATES_TO`
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## Usage patterns
|
|
111
|
+
|
|
112
|
+
### 1. Cross-session continuity
|
|
113
|
+
|
|
114
|
+
The agent picks up where it left off — no re-introduction needed.
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
Session 1:
|
|
118
|
+
remember("User prefers FastAPI over Flask", "preference", ["python", "api"])
|
|
119
|
+
remember("Project uses Postgres 15 on Supabase", "fact", ["database"])
|
|
120
|
+
remember("Avoid Alembic — use raw migrations", "preference", ["database"])
|
|
121
|
+
|
|
122
|
+
Session 2:
|
|
123
|
+
recall("database preferences")
|
|
124
|
+
→ Returns Postgres fact + Alembic preference with RELATES_TO edge between them
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### 2. Dependency chain memory
|
|
128
|
+
|
|
129
|
+
The agent stores what it discovered about your stack — so it doesn't re-discover it.
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
remember("TensorRT requires CUDA 11.8+", "fact", ["tensorrt", "cuda"])
|
|
133
|
+
remember("CUDA 11.8 install requires gcc 9+", "fact", ["cuda", "build"])
|
|
134
|
+
link_memories(tensorrt_id, cuda_id, "DEPENDS_ON")
|
|
135
|
+
link_memories(cuda_id, gcc_id, "DEPENDS_ON")
|
|
136
|
+
|
|
137
|
+
→ get_related(tensorrt_id, depth=3) returns the full dependency chain
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### 3. Decision trail
|
|
141
|
+
|
|
142
|
+
The agent remembers why it made a choice — not just what it chose.
|
|
143
|
+
|
|
144
|
+
```
|
|
145
|
+
remember("Chose ChromaDB over Pinecone: latency and no API key requirement", "decision", ["vector-db"])
|
|
146
|
+
remember("Pinecone rejected: requires API key in CI environment", "context", ["vector-db", "ci"])
|
|
147
|
+
link_memories(decision_id, context_id, "CAUSES")
|
|
148
|
+
|
|
149
|
+
→ Three months later: recall("vector db decision") surfaces both with the causal link
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### 4. Tool performance learning
|
|
153
|
+
|
|
154
|
+
The agent tracks what worked — and what didn't.
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
remember("exa_search returns better results than web_search for technical docs", "tool", ["search"])
|
|
158
|
+
remember("brave_search rate-limited after 10 calls/min in production", "tool", ["search", "production"])
|
|
159
|
+
|
|
160
|
+
→ Before next search: recall("search tools") returns ranked performance memories
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### 5. Multi-step task tracking
|
|
164
|
+
|
|
165
|
+
The agent tracks where a long-running task is — even across sessions.
|
|
166
|
+
|
|
167
|
+
```
|
|
168
|
+
remember("Migration step 1/4 complete: users table done", "task", ["migration", "postgres"])
|
|
169
|
+
remember("Step 2/4 blocked: foreign key constraint on orders table", "task", ["migration", "blocker"])
|
|
170
|
+
link_memories(step1_id, step2_id, "PRECEDES")
|
|
171
|
+
|
|
172
|
+
→ Next session: recall("migration status") returns the full chain with the blocker flagged
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### 6. Pair with a sealed knowledge appliance (CKG)
|
|
176
|
+
|
|
177
|
+
Memory tells the agent what it **did**. Knowledge tells it what's **true**.
|
|
178
|
+
|
|
179
|
+
```
|
|
180
|
+
# Memory: agent remembers what it tried
|
|
181
|
+
remember("NIM deployment failed on t3.medium: insufficient VRAM", "fact", ["nim", "aws"])
|
|
182
|
+
|
|
183
|
+
# Knowledge: CKG tells it the dependency chain it should have known
|
|
184
|
+
query_ckg("NIM", "nvidia-nim", depth=3)
|
|
185
|
+
→ NIM → TensorRT-LLM → GPU Memory ≥24GB → A10G minimum
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
The agent now has both: its own experience (memory) and the declared domain rules (knowledge).
|
|
189
|
+
|
|
190
|
+
See [ckg-mcp](https://pypi.org/project/ckg-mcp/) — 97 domains of sealed knowledge appliances, or build your own at [graphifymd.com/caas](https://graphifymd.com/caas).
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## Context compression and optimization
|
|
195
|
+
|
|
196
|
+
This is the underlying problem agent-memory-mcp solves — not just "memory."
|
|
197
|
+
|
|
198
|
+
**The standard approach:** inject raw documents into the context window. 2,982 tokens to answer one question about your stack. Every call. Every session.
|
|
199
|
+
|
|
200
|
+
**The graph approach:** traverse declared relationships. 269 tokens for the same answer — the exact chain, nothing more.
|
|
201
|
+
|
|
202
|
+
That's **11× context compression**. Not by summarizing or chunking. By replacing document retrieval with graph traversal. You don't inject a manual — you walk a map.
|
|
203
|
+
|
|
204
|
+
Context optimization means the agent gets *exactly* what it needs to reason:
|
|
205
|
+
- Not "here are 40 pages about CUDA" — but "NIM → TensorRT → CUDA 11.8+ → Hopper SM90"
|
|
206
|
+
- Not "here are your last 200 conversation turns" — but the 3 decisions that led to this moment
|
|
207
|
+
- Not similarity guesses — declared edges the agent itself wrote
|
|
208
|
+
|
|
209
|
+
The result: smaller prompts, faster responses, lower cost, and an agent that reasons rather than retrieves.
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## Why graph, not vector?
|
|
214
|
+
|
|
215
|
+
Vector similarity finds *related* content. Graph traversal finds *declared* connections.
|
|
216
|
+
|
|
217
|
+
| | Vector | Graph |
|
|
218
|
+
|---|---|---|
|
|
219
|
+
| How it works | Embedding similarity | Declared typed edges |
|
|
220
|
+
| Best for | Fuzzy recall, semantic search | Reasoning chains, dependency traversal |
|
|
221
|
+
| Answer to "what came before X?" | Approximation | Exact traversal |
|
|
222
|
+
| Can be wrong? | Yes — guesses | No — only returns declared edges |
|
|
223
|
+
|
|
224
|
+
`agent-memory-mcp` auto-links new memories to related existing ones on write (via keyword overlap), then lets you declare precise typed edges when the relationship matters.
|
|
225
|
+
|
|
226
|
+
**The graph doesn't guess. It traverses.**
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## Benchmark
|
|
231
|
+
|
|
232
|
+
Memory quality benchmarked against [KRB Benchmark v0.6.2](https://github.com/Yarmoluk/ckg-benchmark/blob/main/paper/main.pdf):
|
|
233
|
+
|
|
234
|
+
| System | F1 | Tokens/query |
|
|
235
|
+
|---|---|---|
|
|
236
|
+
| **CKG graph traversal** | **0.471** | **269** |
|
|
237
|
+
| RAG (vector retrieval) | 0.123 | 2,982 |
|
|
238
|
+
| GraphRAG | 0.120 | — |
|
|
239
|
+
|
|
240
|
+
~4× F1 · 11× fewer tokens · auditable by design
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## Pair it with domain knowledge
|
|
245
|
+
|
|
246
|
+
agent-memory-mcp handles what your agent **experiences**. [ckg-mcp](https://pypi.org/project/ckg-mcp/) handles what your agent **knows**.
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
pip install ckg-mcp
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
97 domains ready to query: NVIDIA AI stack, financial regulations, healthcare standards, manufacturing safety, legal frameworks, and more. Free tier included.
|
|
253
|
+
|
|
254
|
+
**[→ Browse domains and pricing at graphifymd.com/caas](https://graphifymd.com/caas)**
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
## Partners and integrations
|
|
259
|
+
|
|
260
|
+
We're looking for:
|
|
261
|
+
|
|
262
|
+
- **MCP client integrations** — if you're building an agent runtime that supports MCP, we'd like to be in your default toolchain
|
|
263
|
+
- **Domain experts** — if you have proprietary knowledge that would be more useful as a traversable graph than a document corpus, let's talk
|
|
264
|
+
- **Enterprise pilots** — regulated industries (finance, healthcare, manufacturing, legal) where agents need auditable, declared knowledge rather than probabilistic retrieval
|
|
265
|
+
- **Researchers** — working on agent memory, knowledge representation, or MCP tooling
|
|
266
|
+
|
|
267
|
+
Reach out: [graphifymd.com](https://graphifymd.com) · [daniel.yarmoluk@gmail.com](mailto:daniel.yarmoluk@gmail.com)
|
|
268
|
+
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
## Related
|
|
272
|
+
|
|
273
|
+
- **[ckg-mcp](https://pypi.org/project/ckg-mcp/)** — 97 sealed knowledge appliances via MCP
|
|
274
|
+
- **[ckg-nvidia-ai](https://github.com/Yarmoluk/ckg-nvidia-ai)** — 20 NVIDIA AI domain graphs, 998 nodes, free
|
|
275
|
+
- **[KRB Benchmark](https://huggingface.co/datasets/danyarm/ckg-benchmark)** — open benchmark dataset
|
|
276
|
+
- **[graphifymd.com/caas](https://graphifymd.com/caas)** — Context-as-a-Service: build or subscribe to sealed knowledge appliances
|
|
277
|
+
|
|
278
|
+
---
|
|
279
|
+
|
|
280
|
+
## License
|
|
281
|
+
|
|
282
|
+
MIT — built by [Graphify.md](https://graphifymd.com). Patent pending.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
agent_memory/__init__.py,sha256=kUR5RAFc7HCeiqdlX36dZOHkUI5wI6V_43RpEcD8b-0,22
|
|
2
|
+
agent_memory/server.py,sha256=_SutIjoRWey-VA6M5WWIjyI7xrSy988FmMQChANNQNA,4965
|
|
3
|
+
agent_memory/storage.py,sha256=u3keZuwg3ru51fItsCoUGyqRkkJypQqc_Zo1o3bEgIA,5911
|
|
4
|
+
agentmem_mcp-0.1.0.dist-info/METADATA,sha256=gKZAkg_SYo1PdhaJ8ojcukgc3BdCdN4U5i7B4d669WE,10972
|
|
5
|
+
agentmem_mcp-0.1.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
6
|
+
agentmem_mcp-0.1.0.dist-info/entry_points.txt,sha256=pqvzbM_eaP4kKPWjMcXXTkhjHib7YVwJMN5sIhaywoA,58
|
|
7
|
+
agentmem_mcp-0.1.0.dist-info/RECORD,,
|