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.
Files changed (52) hide show
  1. agentcache/__init__.py +29 -0
  2. agentcache/app.py +312 -0
  3. agentcache/cli.py +346 -0
  4. agentcache/connect.py +724 -0
  5. agentcache/core/__init__.py +19 -0
  6. agentcache/core/audit_log.py +104 -0
  7. agentcache/core/config.py +51 -0
  8. agentcache/core/context_builder.py +209 -0
  9. agentcache/core/graph.py +120 -0
  10. agentcache/core/image_store.py +111 -0
  11. agentcache/core/infer.py +142 -0
  12. agentcache/core/kv_scopes.py +86 -0
  13. agentcache/core/lessons.py +242 -0
  14. agentcache/core/llm.py +596 -0
  15. agentcache/core/memory_store.py +207 -0
  16. agentcache/core/observation_store.py +576 -0
  17. agentcache/core/privacy.py +34 -0
  18. agentcache/core/project_profile.py +625 -0
  19. agentcache/core/search_service.py +444 -0
  20. agentcache/core/session_store.py +382 -0
  21. agentcache/core/slots.py +504 -0
  22. agentcache/db.py +359 -0
  23. agentcache/import_data.py +86 -0
  24. agentcache/legacy.py +94 -0
  25. agentcache/mcp_stdio.py +141 -0
  26. agentcache/py.typed +0 -0
  27. agentcache/replay_import.py +680 -0
  28. agentcache/routes/__init__.py +29 -0
  29. agentcache/routes/_deps.py +46 -0
  30. agentcache/routes/auth.py +68 -0
  31. agentcache/routes/graph.py +81 -0
  32. agentcache/routes/health.py +149 -0
  33. agentcache/routes/mcp.py +614 -0
  34. agentcache/routes/memories.py +116 -0
  35. agentcache/routes/migration.py +32 -0
  36. agentcache/routes/observations.py +253 -0
  37. agentcache/routes/search.py +80 -0
  38. agentcache/search.py +935 -0
  39. agentcache/storage/__init__.py +29 -0
  40. agentcache/storage/images.py +102 -0
  41. agentcache/storage/paths.py +116 -0
  42. agentcache/storage/scopes.py +9 -0
  43. agentcache/viewer/favicon.svg +1 -0
  44. agentcache/viewer/index.html +4235 -0
  45. agentcache/viewer_helpers.py +61 -0
  46. agentcache/workers.py +192 -0
  47. agentcache_core-0.9.9.dist-info/METADATA +194 -0
  48. agentcache_core-0.9.9.dist-info/RECORD +52 -0
  49. agentcache_core-0.9.9.dist-info/WHEEL +5 -0
  50. agentcache_core-0.9.9.dist-info/entry_points.txt +2 -0
  51. agentcache_core-0.9.9.dist-info/licenses/LICENSE +190 -0
  52. 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}