agentcache-core 0.9.9__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.
- agentcache/__init__.py +29 -0
- agentcache/app.py +312 -0
- agentcache/cli.py +346 -0
- agentcache/connect.py +724 -0
- agentcache/core/__init__.py +19 -0
- agentcache/core/audit_log.py +104 -0
- agentcache/core/config.py +51 -0
- agentcache/core/context_builder.py +209 -0
- agentcache/core/graph.py +120 -0
- agentcache/core/image_store.py +111 -0
- agentcache/core/infer.py +142 -0
- agentcache/core/kv_scopes.py +86 -0
- agentcache/core/lessons.py +242 -0
- agentcache/core/llm.py +596 -0
- agentcache/core/memory_store.py +207 -0
- agentcache/core/observation_store.py +576 -0
- agentcache/core/privacy.py +34 -0
- agentcache/core/project_profile.py +625 -0
- agentcache/core/search_service.py +444 -0
- agentcache/core/session_store.py +382 -0
- agentcache/core/slots.py +504 -0
- agentcache/db.py +359 -0
- agentcache/import_data.py +86 -0
- agentcache/legacy.py +94 -0
- agentcache/mcp_stdio.py +141 -0
- agentcache/py.typed +0 -0
- agentcache/replay_import.py +680 -0
- agentcache/routes/__init__.py +29 -0
- agentcache/routes/_deps.py +46 -0
- agentcache/routes/auth.py +68 -0
- agentcache/routes/graph.py +81 -0
- agentcache/routes/health.py +149 -0
- agentcache/routes/mcp.py +614 -0
- agentcache/routes/memories.py +116 -0
- agentcache/routes/migration.py +32 -0
- agentcache/routes/observations.py +253 -0
- agentcache/routes/search.py +80 -0
- agentcache/search.py +935 -0
- agentcache/storage/__init__.py +29 -0
- agentcache/storage/images.py +102 -0
- agentcache/storage/paths.py +116 -0
- agentcache/storage/scopes.py +9 -0
- agentcache/viewer/favicon.svg +1 -0
- agentcache/viewer/index.html +4235 -0
- agentcache/viewer_helpers.py +61 -0
- agentcache/workers.py +192 -0
- agentcache_core-0.9.9.dist-info/METADATA +194 -0
- agentcache_core-0.9.9.dist-info/RECORD +52 -0
- agentcache_core-0.9.9.dist-info/WHEEL +5 -0
- agentcache_core-0.9.9.dist-info/entry_points.txt +2 -0
- agentcache_core-0.9.9.dist-info/licenses/LICENSE +190 -0
- agentcache_core-0.9.9.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
"""Memory store — remember, evolve, forget global memories."""
|
|
2
|
+
|
|
3
|
+
import datetime
|
|
4
|
+
from typing import Any, Dict
|
|
5
|
+
|
|
6
|
+
from ..db import StateKV
|
|
7
|
+
from ..storage.paths import generate_id
|
|
8
|
+
from .config import commit_if_enabled, get_agent_id
|
|
9
|
+
from .infer import vector_index_add_guarded
|
|
10
|
+
from .kv_scopes import KV
|
|
11
|
+
from .privacy import strip_private_data
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def jaccard_similarity(a: str, b: str) -> float:
|
|
15
|
+
tokens_a = [t for t in a.split() if len(t) > 2]
|
|
16
|
+
tokens_b = [t for t in b.split() if len(t) > 2]
|
|
17
|
+
set_a = set(tokens_a)
|
|
18
|
+
set_b = set(tokens_b)
|
|
19
|
+
if not set_a and not set_b:
|
|
20
|
+
return 1.0
|
|
21
|
+
if not set_a or not set_b:
|
|
22
|
+
return 0.0
|
|
23
|
+
intersection = len(set_a.intersection(set_b))
|
|
24
|
+
union = len(set_a.union(set_b))
|
|
25
|
+
return intersection / union
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def memory_to_observation(memory: Dict[str, Any]) -> Dict[str, Any]:
|
|
29
|
+
return {
|
|
30
|
+
"id": memory["id"],
|
|
31
|
+
"sessionId": memory.get("sessionIds", ["memory"])[0]
|
|
32
|
+
if memory.get("sessionIds")
|
|
33
|
+
else "memory",
|
|
34
|
+
"timestamp": memory["createdAt"],
|
|
35
|
+
"type": "decision",
|
|
36
|
+
"title": memory["title"],
|
|
37
|
+
"facts": [memory["content"]],
|
|
38
|
+
"narrative": memory["content"],
|
|
39
|
+
"concepts": memory.get("concepts", []),
|
|
40
|
+
"files": memory.get("files", []),
|
|
41
|
+
"importance": memory.get("strength", 7),
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def remember(kv: StateKV, data: Dict[str, Any]) -> Dict[str, Any]:
|
|
46
|
+
from .. import legacy as _legacy
|
|
47
|
+
|
|
48
|
+
content = data.get("content")
|
|
49
|
+
if not content or not content.strip():
|
|
50
|
+
raise ValueError("content is required")
|
|
51
|
+
content = strip_private_data(content)
|
|
52
|
+
|
|
53
|
+
concepts = data.get("concepts") or []
|
|
54
|
+
files = data.get("files") or []
|
|
55
|
+
source_obs = data.get("sourceObservationIds") or []
|
|
56
|
+
ttl_days = data.get("ttlDays")
|
|
57
|
+
mem_type = data.get("type") or "fact"
|
|
58
|
+
project = data.get("project")
|
|
59
|
+
if project:
|
|
60
|
+
project = project.strip()
|
|
61
|
+
|
|
62
|
+
now = (
|
|
63
|
+
datetime.datetime.now(datetime.timezone.utc).isoformat().replace("+00:00", "Z")
|
|
64
|
+
)
|
|
65
|
+
existing_memories = kv.list(KV.memories)
|
|
66
|
+
superseded_id = None
|
|
67
|
+
superseded_version = 1
|
|
68
|
+
superseded_memory = None
|
|
69
|
+
lower_content = content.lower()
|
|
70
|
+
|
|
71
|
+
for existing in existing_memories:
|
|
72
|
+
if existing.get("isLatest") is False:
|
|
73
|
+
continue
|
|
74
|
+
if project and existing.get("project") and existing["project"] != project:
|
|
75
|
+
continue
|
|
76
|
+
similarity = jaccard_similarity(
|
|
77
|
+
lower_content, existing.get("content", "").lower()
|
|
78
|
+
)
|
|
79
|
+
if similarity > 0.7:
|
|
80
|
+
superseded_id = existing["id"]
|
|
81
|
+
superseded_version = existing.get("version") or 1
|
|
82
|
+
superseded_memory = existing
|
|
83
|
+
break
|
|
84
|
+
|
|
85
|
+
call_agent_id = data.get("agentId") or get_agent_id()
|
|
86
|
+
new_mem = {
|
|
87
|
+
"id": generate_id("mem"),
|
|
88
|
+
"createdAt": now,
|
|
89
|
+
"updatedAt": now,
|
|
90
|
+
"type": mem_type,
|
|
91
|
+
"title": content[:80],
|
|
92
|
+
"content": content,
|
|
93
|
+
"concepts": concepts,
|
|
94
|
+
"files": files,
|
|
95
|
+
"sessionIds": [],
|
|
96
|
+
"strength": 7,
|
|
97
|
+
"version": superseded_version + 1 if superseded_id else 1,
|
|
98
|
+
"parentId": superseded_id,
|
|
99
|
+
"supersedes": [superseded_id] if superseded_id else [],
|
|
100
|
+
"sourceObservationIds": [i for i in source_obs if i],
|
|
101
|
+
"isLatest": True,
|
|
102
|
+
}
|
|
103
|
+
if call_agent_id:
|
|
104
|
+
new_mem["agentId"] = call_agent_id
|
|
105
|
+
if project:
|
|
106
|
+
new_mem["project"] = project
|
|
107
|
+
|
|
108
|
+
if ttl_days and isinstance(ttl_days, (int, float)) and ttl_days > 0:
|
|
109
|
+
forget_time = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(
|
|
110
|
+
days=ttl_days
|
|
111
|
+
)
|
|
112
|
+
new_mem["forgetAfter"] = forget_time.isoformat().replace("+00:00", "Z")
|
|
113
|
+
elif "forgetAfter" in data:
|
|
114
|
+
new_mem["forgetAfter"] = data["forgetAfter"]
|
|
115
|
+
|
|
116
|
+
if superseded_memory:
|
|
117
|
+
superseded_memory["isLatest"] = False
|
|
118
|
+
kv.set(KV.memories, superseded_memory["id"], superseded_memory)
|
|
119
|
+
|
|
120
|
+
kv.set(KV.memories, new_mem["id"], new_mem)
|
|
121
|
+
|
|
122
|
+
if _legacy._search_service:
|
|
123
|
+
try:
|
|
124
|
+
_legacy._search_service.bm25.add(memory_to_observation(new_mem))
|
|
125
|
+
except Exception as ex:
|
|
126
|
+
print(f"[bm25] memory add failed: {ex}")
|
|
127
|
+
|
|
128
|
+
comb_text = new_mem["title"] + " " + new_mem["content"]
|
|
129
|
+
vector_index_add_guarded(
|
|
130
|
+
new_mem["id"], "memory", comb_text, {"kind": "memory", "logId": new_mem["id"]}
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
if _legacy._search_service:
|
|
134
|
+
_legacy._search_service.schedule_persist()
|
|
135
|
+
|
|
136
|
+
commit_if_enabled(
|
|
137
|
+
kv, f"Remember: {new_mem.get('title', '')}", new_mem.get("agentId")
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
_legacy.broadcast_stream(
|
|
141
|
+
{
|
|
142
|
+
"type": "memory_created",
|
|
143
|
+
"data": new_mem,
|
|
144
|
+
}
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
return {"success": True, "memory": new_mem}
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def evolve_memory(kv: StateKV, data: Dict[str, Any]) -> Dict[str, Any]:
|
|
151
|
+
from .. import legacy as _legacy
|
|
152
|
+
|
|
153
|
+
mem_id = data["memoryId"]
|
|
154
|
+
new_content = data["newContent"]
|
|
155
|
+
new_title = data.get("newTitle")
|
|
156
|
+
|
|
157
|
+
existing = kv.get(KV.memories, mem_id)
|
|
158
|
+
if not existing:
|
|
159
|
+
raise ValueError("Memory not found")
|
|
160
|
+
|
|
161
|
+
existing["isLatest"] = False
|
|
162
|
+
kv.set(KV.memories, existing["id"], existing)
|
|
163
|
+
|
|
164
|
+
now = (
|
|
165
|
+
datetime.datetime.now(datetime.timezone.utc).isoformat().replace("+00:00", "Z")
|
|
166
|
+
)
|
|
167
|
+
new_mem = dict(existing)
|
|
168
|
+
new_mem["id"] = generate_id("mem")
|
|
169
|
+
new_mem["content"] = new_content
|
|
170
|
+
if new_title:
|
|
171
|
+
new_mem["title"] = new_title
|
|
172
|
+
else:
|
|
173
|
+
new_mem["title"] = new_content[:80]
|
|
174
|
+
new_mem["version"] = existing.get("version", 1) + 1
|
|
175
|
+
new_mem["parentId"] = existing["id"]
|
|
176
|
+
new_mem["supersedes"] = [existing["id"]]
|
|
177
|
+
new_mem["createdAt"] = now
|
|
178
|
+
new_mem["updatedAt"] = now
|
|
179
|
+
new_mem["isLatest"] = True
|
|
180
|
+
|
|
181
|
+
kv.set(KV.memories, new_mem["id"], new_mem)
|
|
182
|
+
|
|
183
|
+
if _legacy._search_service:
|
|
184
|
+
try:
|
|
185
|
+
_legacy._search_service.bm25.add(memory_to_observation(new_mem))
|
|
186
|
+
_legacy._search_service.bm25.remove(existing["id"])
|
|
187
|
+
except Exception:
|
|
188
|
+
pass
|
|
189
|
+
|
|
190
|
+
comb_text = new_mem["title"] + " " + new_mem["content"]
|
|
191
|
+
vector_index_add_guarded(
|
|
192
|
+
new_mem["id"], "memory", comb_text, {"kind": "memory", "logId": new_mem["id"]}
|
|
193
|
+
)
|
|
194
|
+
if _legacy._search_service and _legacy._search_service.vector:
|
|
195
|
+
_legacy._search_service.vector.remove(existing["id"])
|
|
196
|
+
|
|
197
|
+
if _legacy._search_service:
|
|
198
|
+
_legacy._search_service.schedule_persist()
|
|
199
|
+
|
|
200
|
+
agent_id = data.get("agentId") or get_agent_id() or new_mem.get("agentId")
|
|
201
|
+
commit_if_enabled(
|
|
202
|
+
kv,
|
|
203
|
+
f"Evolve memory {new_mem['id']} (v{new_mem['version']}): {new_mem['title']}",
|
|
204
|
+
agent_id,
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
return {"success": True, "memory": new_mem}
|