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
agentcache/routes/mcp.py
ADDED
|
@@ -0,0 +1,614 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MCP (Model Context Protocol) routes blueprint.
|
|
3
|
+
|
|
4
|
+
Handles:
|
|
5
|
+
GET /agentmemory/mcp/tools — list all MCP tool schemas
|
|
6
|
+
POST /agentmemory/mcp/tools — dispatch a tool call
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import datetime
|
|
10
|
+
import json
|
|
11
|
+
import os
|
|
12
|
+
|
|
13
|
+
from flask import Blueprint, jsonify, request
|
|
14
|
+
|
|
15
|
+
from .. import legacy
|
|
16
|
+
from ..core import KV
|
|
17
|
+
from ._deps import get_kv, get_observation_store, get_search_service
|
|
18
|
+
from .auth import require_auth
|
|
19
|
+
|
|
20
|
+
mcp_bp = Blueprint("mcp", __name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _datetime_now_iso() -> str:
|
|
24
|
+
return (
|
|
25
|
+
datetime.datetime.now(datetime.timezone.utc).isoformat().replace("+00:00", "Z")
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _parse_mcp_list_arg(arg_val):
|
|
30
|
+
"""Accept a list or comma-separated string and return a list of strings."""
|
|
31
|
+
if isinstance(arg_val, list):
|
|
32
|
+
return [str(item).strip() for item in arg_val if item]
|
|
33
|
+
if isinstance(arg_val, str) and arg_val:
|
|
34
|
+
return [item.strip() for item in arg_val.split(",") if item.strip()]
|
|
35
|
+
return []
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# ---------------------------------------------------------------------------
|
|
39
|
+
# GET /agentcache/mcp/tools
|
|
40
|
+
# ---------------------------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def get_mcp_tools_schemas():
|
|
44
|
+
return [
|
|
45
|
+
{
|
|
46
|
+
"name": "cache_recall",
|
|
47
|
+
"description": "Search past folder observations and global memories. Use when you need to recall what happened in a folder.",
|
|
48
|
+
"inputSchema": {
|
|
49
|
+
"type": "object",
|
|
50
|
+
"properties": {
|
|
51
|
+
"query": {"type": "string", "description": "Search query keywords"},
|
|
52
|
+
"limit": {
|
|
53
|
+
"type": "number",
|
|
54
|
+
"description": "Max results to return (default 10)",
|
|
55
|
+
},
|
|
56
|
+
"folderPath": {
|
|
57
|
+
"type": "string",
|
|
58
|
+
"description": "Filter to a specific folder path (optional)",
|
|
59
|
+
},
|
|
60
|
+
"agentId": {
|
|
61
|
+
"type": "string",
|
|
62
|
+
"description": "Filter to a specific agent ID (optional)",
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
"required": ["query"],
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"name": "cache_smart_search",
|
|
70
|
+
"description": "Hybrid semantic+keyword search across folder observations and global memories.",
|
|
71
|
+
"inputSchema": {
|
|
72
|
+
"type": "object",
|
|
73
|
+
"properties": {
|
|
74
|
+
"query": {"type": "string", "description": "Search query"},
|
|
75
|
+
"limit": {
|
|
76
|
+
"type": "number",
|
|
77
|
+
"description": "Max results (default 10)",
|
|
78
|
+
},
|
|
79
|
+
"folderPath": {
|
|
80
|
+
"type": "string",
|
|
81
|
+
"description": "Filter to a specific folder path (optional)",
|
|
82
|
+
},
|
|
83
|
+
"agentId": {
|
|
84
|
+
"type": "string",
|
|
85
|
+
"description": "Filter to a specific agent ID (optional)",
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
"required": ["query"],
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"name": "cache_save",
|
|
93
|
+
"description": "Explicitly save an important insight, decision, or pattern to long-term memory.",
|
|
94
|
+
"inputSchema": {
|
|
95
|
+
"type": "object",
|
|
96
|
+
"properties": {
|
|
97
|
+
"content": {
|
|
98
|
+
"type": "string",
|
|
99
|
+
"description": "The insight or decision to remember",
|
|
100
|
+
},
|
|
101
|
+
"type": {
|
|
102
|
+
"type": "string",
|
|
103
|
+
"description": "Memory type: pattern, preference, architecture, bug, workflow, or fact",
|
|
104
|
+
},
|
|
105
|
+
"concepts": {
|
|
106
|
+
"oneOf": [
|
|
107
|
+
{
|
|
108
|
+
"type": "string",
|
|
109
|
+
"description": "Comma-separated key concepts",
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"type": "array",
|
|
113
|
+
"items": {"type": "string"},
|
|
114
|
+
"description": "List of key concepts",
|
|
115
|
+
},
|
|
116
|
+
]
|
|
117
|
+
},
|
|
118
|
+
"files": {
|
|
119
|
+
"oneOf": [
|
|
120
|
+
{
|
|
121
|
+
"type": "string",
|
|
122
|
+
"description": "Comma-separated relevant file paths",
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
"type": "array",
|
|
126
|
+
"items": {"type": "string"},
|
|
127
|
+
"description": "List of relevant file paths",
|
|
128
|
+
},
|
|
129
|
+
]
|
|
130
|
+
},
|
|
131
|
+
"project": {
|
|
132
|
+
"type": "string",
|
|
133
|
+
"description": "Canonical project identifier",
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
"required": ["content"],
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
"name": "cache_diagnose",
|
|
141
|
+
"description": "Health check — returns folder, agent, observation and memory counts.",
|
|
142
|
+
"inputSchema": {"type": "object", "properties": {}},
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
"name": "cache_forget",
|
|
146
|
+
"description": "Delete a global memory or all observations for a (folderPath, agentId) pair.",
|
|
147
|
+
"inputSchema": {
|
|
148
|
+
"type": "object",
|
|
149
|
+
"properties": {
|
|
150
|
+
"memoryId": {
|
|
151
|
+
"type": "string",
|
|
152
|
+
"description": "Memory ID to delete",
|
|
153
|
+
},
|
|
154
|
+
"folderPath": {
|
|
155
|
+
"type": "string",
|
|
156
|
+
"description": "Folder path to delete observations from",
|
|
157
|
+
},
|
|
158
|
+
"agentId": {
|
|
159
|
+
"type": "string",
|
|
160
|
+
"description": "Agent ID to delete observations for",
|
|
161
|
+
},
|
|
162
|
+
"observationIds": {
|
|
163
|
+
"oneOf": [
|
|
164
|
+
{
|
|
165
|
+
"type": "string",
|
|
166
|
+
"description": "Comma-separated observation IDs to delete",
|
|
167
|
+
},
|
|
168
|
+
{"type": "array", "items": {"type": "string"}},
|
|
169
|
+
]
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
"name": "cache_export",
|
|
176
|
+
"description": "Export all folder observations and global memories as JSON (v2 format).",
|
|
177
|
+
"inputSchema": {"type": "object", "properties": {}},
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
"name": "agent_observe",
|
|
181
|
+
"description": "Log an observation scoped to a folder path and agent ID.",
|
|
182
|
+
"inputSchema": {
|
|
183
|
+
"type": "object",
|
|
184
|
+
"properties": {
|
|
185
|
+
"folderPath": {
|
|
186
|
+
"type": "string",
|
|
187
|
+
"description": "Absolute path of the working directory",
|
|
188
|
+
},
|
|
189
|
+
"agentId": {
|
|
190
|
+
"type": "string",
|
|
191
|
+
"description": "Identity of the agent (e.g. 'kiro', 'claude')",
|
|
192
|
+
},
|
|
193
|
+
"text": {"type": "string", "description": "Observation content"},
|
|
194
|
+
"timestamp": {
|
|
195
|
+
"type": "string",
|
|
196
|
+
"description": "ISO 8601 UTC timestamp (defaults to now)",
|
|
197
|
+
},
|
|
198
|
+
"type": {"type": "string", "description": "Observation type"},
|
|
199
|
+
"title": {"type": "string", "description": "Short title"},
|
|
200
|
+
"concepts": {"type": "array", "items": {"type": "string"}},
|
|
201
|
+
"files": {"type": "array", "items": {"type": "string"}},
|
|
202
|
+
"importance": {"type": "integer", "description": "1-10"},
|
|
203
|
+
"sessionId": {
|
|
204
|
+
"type": "string",
|
|
205
|
+
"description": "Deprecated — ignored",
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
"required": ["folderPath", "agentId", "text"],
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
"name": "agent_cache",
|
|
213
|
+
"description": "Explicitly save a key insight, fact, or architecture decision to long-term memory.",
|
|
214
|
+
"inputSchema": {
|
|
215
|
+
"type": "object",
|
|
216
|
+
"properties": {
|
|
217
|
+
"agentId": {
|
|
218
|
+
"type": "string",
|
|
219
|
+
"description": "ID/Name of the agent (optional)",
|
|
220
|
+
},
|
|
221
|
+
"content": {
|
|
222
|
+
"type": "string",
|
|
223
|
+
"description": "The memory content/insight",
|
|
224
|
+
},
|
|
225
|
+
"project": {
|
|
226
|
+
"type": "string",
|
|
227
|
+
"description": "Canonical project path/identifier",
|
|
228
|
+
},
|
|
229
|
+
"type": {
|
|
230
|
+
"type": "string",
|
|
231
|
+
"description": "Memory type: fact, preference, bug, workflow, architecture",
|
|
232
|
+
},
|
|
233
|
+
"concepts": {
|
|
234
|
+
"oneOf": [
|
|
235
|
+
{
|
|
236
|
+
"type": "string",
|
|
237
|
+
"description": "Comma-separated key concepts",
|
|
238
|
+
},
|
|
239
|
+
{"type": "array", "items": {"type": "string"}},
|
|
240
|
+
]
|
|
241
|
+
},
|
|
242
|
+
"files": {
|
|
243
|
+
"oneOf": [
|
|
244
|
+
{
|
|
245
|
+
"type": "string",
|
|
246
|
+
"description": "Comma-separated relevant file paths",
|
|
247
|
+
},
|
|
248
|
+
{"type": "array", "items": {"type": "string"}},
|
|
249
|
+
]
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
"required": ["content"],
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
"name": "cache_folders",
|
|
257
|
+
"description": "List all (folder, agent) pairs that have memory observations.",
|
|
258
|
+
"inputSchema": {"type": "object", "properties": {}},
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
"name": "cache_folder_observations",
|
|
262
|
+
"description": "Get all observations for a specific (folderPath, agentId) pair.",
|
|
263
|
+
"inputSchema": {
|
|
264
|
+
"type": "object",
|
|
265
|
+
"properties": {
|
|
266
|
+
"folderPath": {"type": "string", "description": "Folder path"},
|
|
267
|
+
"agentId": {"type": "string", "description": "Agent ID"},
|
|
268
|
+
},
|
|
269
|
+
"required": ["folderPath", "agentId"],
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
"name": "cache_timeline",
|
|
274
|
+
"description": "Get folder activity feed — observations sorted by time, filterable by folder/agent.",
|
|
275
|
+
"inputSchema": {
|
|
276
|
+
"type": "object",
|
|
277
|
+
"properties": {
|
|
278
|
+
"folderPath": {
|
|
279
|
+
"type": "string",
|
|
280
|
+
"description": "Filter by folder path (optional)",
|
|
281
|
+
},
|
|
282
|
+
"agentId": {
|
|
283
|
+
"type": "string",
|
|
284
|
+
"description": "Filter by agent ID (optional)",
|
|
285
|
+
},
|
|
286
|
+
"limit": {
|
|
287
|
+
"type": "integer",
|
|
288
|
+
"description": "Max results (default 100)",
|
|
289
|
+
},
|
|
290
|
+
"before": {
|
|
291
|
+
"type": "string",
|
|
292
|
+
"description": "ISO timestamp upper bound (optional)",
|
|
293
|
+
},
|
|
294
|
+
"after": {
|
|
295
|
+
"type": "string",
|
|
296
|
+
"description": "ISO timestamp lower bound (optional)",
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
"name": "cache_dedup",
|
|
303
|
+
"description": "Remove duplicate observations from a (folderPath, agentId) pair or all pairs. Keeps the earliest observation per unique text fingerprint.",
|
|
304
|
+
"inputSchema": {
|
|
305
|
+
"type": "object",
|
|
306
|
+
"properties": {
|
|
307
|
+
"folderPath": {
|
|
308
|
+
"type": "string",
|
|
309
|
+
"description": "Folder path to deduplicate (optional — omit for all pairs)",
|
|
310
|
+
},
|
|
311
|
+
"agentId": {
|
|
312
|
+
"type": "string",
|
|
313
|
+
"description": "Agent ID to deduplicate (optional — omit for all pairs)",
|
|
314
|
+
},
|
|
315
|
+
},
|
|
316
|
+
},
|
|
317
|
+
},
|
|
318
|
+
]
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
@mcp_bp.route("/agentcache/mcp/tools", methods=["GET"])
|
|
322
|
+
@mcp_bp.route("/agentmemory/mcp/tools", methods=["GET"])
|
|
323
|
+
@require_auth
|
|
324
|
+
def mcp_tools_list():
|
|
325
|
+
tools = get_mcp_tools_schemas()
|
|
326
|
+
return jsonify({"tools": tools}), 200
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
# ---------------------------------------------------------------------------
|
|
330
|
+
# POST /agentcache/mcp/tools
|
|
331
|
+
# ---------------------------------------------------------------------------
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
@mcp_bp.route("/agentcache/mcp/tools", methods=["POST"])
|
|
335
|
+
@mcp_bp.route("/agentmemory/mcp/tools", methods=["POST"])
|
|
336
|
+
@require_auth
|
|
337
|
+
def mcp_tools_call():
|
|
338
|
+
try:
|
|
339
|
+
kv = get_kv()
|
|
340
|
+
body = request.get_json(force=True) or {}
|
|
341
|
+
name = body.get("name")
|
|
342
|
+
args = body.get("arguments") or {}
|
|
343
|
+
if not name:
|
|
344
|
+
return jsonify({"error": "name is required"}), 400
|
|
345
|
+
|
|
346
|
+
print(f"[mcp] Calling tool {name} with args: {args}")
|
|
347
|
+
text_out = ""
|
|
348
|
+
|
|
349
|
+
if name in ("cache_recall", "memory_recall"):
|
|
350
|
+
q = args.get("query")
|
|
351
|
+
limit = int(args.get("limit") or 10)
|
|
352
|
+
folder_path = args.get("folderPath")
|
|
353
|
+
agent_id = args.get("agentId")
|
|
354
|
+
if legacy.is_agent_scope_isolated():
|
|
355
|
+
current_aid = legacy.get_agent_id()
|
|
356
|
+
if current_aid:
|
|
357
|
+
if agent_id and agent_id != current_aid:
|
|
358
|
+
return jsonify(
|
|
359
|
+
{"error": "Unauthorized: Agent scope is isolated"}
|
|
360
|
+
), 403
|
|
361
|
+
agent_id = current_aid
|
|
362
|
+
search_svc = get_search_service()
|
|
363
|
+
if search_svc is not None:
|
|
364
|
+
res = search_svc.search(
|
|
365
|
+
query=q,
|
|
366
|
+
limit=limit,
|
|
367
|
+
folder_path=folder_path,
|
|
368
|
+
agent_id=agent_id,
|
|
369
|
+
kv=kv,
|
|
370
|
+
)
|
|
371
|
+
else:
|
|
372
|
+
res = []
|
|
373
|
+
text_out = json.dumps(res, indent=2)
|
|
374
|
+
|
|
375
|
+
elif name in ("cache_save", "memory_save"):
|
|
376
|
+
content = args.get("content")
|
|
377
|
+
concepts = _parse_mcp_list_arg(args.get("concepts"))
|
|
378
|
+
files = _parse_mcp_list_arg(args.get("files"))
|
|
379
|
+
project = args.get("project")
|
|
380
|
+
res = legacy.remember(
|
|
381
|
+
kv,
|
|
382
|
+
{
|
|
383
|
+
"content": content,
|
|
384
|
+
"type": args.get("type") or "fact",
|
|
385
|
+
"concepts": concepts,
|
|
386
|
+
"files": files,
|
|
387
|
+
"project": project,
|
|
388
|
+
},
|
|
389
|
+
)
|
|
390
|
+
text_out = json.dumps(res)
|
|
391
|
+
|
|
392
|
+
elif name in ("cache_smart_search", "memory_smart_search"):
|
|
393
|
+
q = args.get("query")
|
|
394
|
+
limit = int(args.get("limit") or 10)
|
|
395
|
+
folder_path = args.get("folderPath")
|
|
396
|
+
agent_id = args.get("agentId")
|
|
397
|
+
if legacy.is_agent_scope_isolated():
|
|
398
|
+
current_aid = legacy.get_agent_id()
|
|
399
|
+
if current_aid:
|
|
400
|
+
if agent_id and agent_id != current_aid:
|
|
401
|
+
return jsonify(
|
|
402
|
+
{"error": "Unauthorized: Agent scope is isolated"}
|
|
403
|
+
), 403
|
|
404
|
+
agent_id = current_aid
|
|
405
|
+
search_svc = get_search_service()
|
|
406
|
+
if search_svc is not None:
|
|
407
|
+
res = search_svc.search(
|
|
408
|
+
query=q,
|
|
409
|
+
limit=limit,
|
|
410
|
+
folder_path=folder_path,
|
|
411
|
+
agent_id=agent_id,
|
|
412
|
+
kv=kv,
|
|
413
|
+
)
|
|
414
|
+
else:
|
|
415
|
+
res = []
|
|
416
|
+
text_out = json.dumps(res, indent=2)
|
|
417
|
+
|
|
418
|
+
elif name in ("cache_diagnose", "memory_diagnose"):
|
|
419
|
+
res = legacy.health_check(kv)
|
|
420
|
+
text_out = json.dumps(res, indent=2)
|
|
421
|
+
|
|
422
|
+
elif name in ("cache_forget", "memory_forget"):
|
|
423
|
+
obs_ids = _parse_mcp_list_arg(args.get("observationIds"))
|
|
424
|
+
request_aid = args.get("agentId")
|
|
425
|
+
if legacy.is_agent_scope_isolated():
|
|
426
|
+
current_aid = legacy.get_agent_id()
|
|
427
|
+
if current_aid:
|
|
428
|
+
if request_aid and request_aid != current_aid:
|
|
429
|
+
return jsonify(
|
|
430
|
+
{"error": "Unauthorized: Agent scope is isolated"}
|
|
431
|
+
), 403
|
|
432
|
+
request_aid = current_aid
|
|
433
|
+
obs_store = get_observation_store()
|
|
434
|
+
if obs_store is not None:
|
|
435
|
+
forget_payload = {
|
|
436
|
+
"memoryId": args.get("memoryId"),
|
|
437
|
+
"folderPath": args.get("folderPath"),
|
|
438
|
+
"agentId": request_aid,
|
|
439
|
+
}
|
|
440
|
+
if obs_ids:
|
|
441
|
+
forget_payload["observationIds"] = obs_ids
|
|
442
|
+
res = obs_store.forget(forget_payload)
|
|
443
|
+
else:
|
|
444
|
+
res = {"success": False, "deleted": 0}
|
|
445
|
+
text_out = json.dumps(res, indent=2)
|
|
446
|
+
|
|
447
|
+
elif name in ("cache_export", "memory_export"):
|
|
448
|
+
res = legacy.export_data(kv, {})
|
|
449
|
+
text_out = json.dumps(res, indent=2)
|
|
450
|
+
|
|
451
|
+
elif name == "agent_observe":
|
|
452
|
+
folder_path = args.get("folderPath")
|
|
453
|
+
agent_id = args.get("agentId")
|
|
454
|
+
text = args.get("text") or args.get("content") or ""
|
|
455
|
+
|
|
456
|
+
if not folder_path:
|
|
457
|
+
folder_path = (
|
|
458
|
+
args.get("cwd")
|
|
459
|
+
or args.get("project")
|
|
460
|
+
or os.getenv("AGENTCACHE_CWD")
|
|
461
|
+
or os.getenv("AGENTMEMORY_CWD")
|
|
462
|
+
or "/unknown"
|
|
463
|
+
)
|
|
464
|
+
if not agent_id:
|
|
465
|
+
agent_id = (
|
|
466
|
+
args.get("sessionId")
|
|
467
|
+
or legacy.get_agent_id()
|
|
468
|
+
or os.getenv("AGENT_ID")
|
|
469
|
+
or "agent"
|
|
470
|
+
)
|
|
471
|
+
|
|
472
|
+
if legacy.is_agent_scope_isolated():
|
|
473
|
+
current_aid = legacy.get_agent_id()
|
|
474
|
+
if current_aid:
|
|
475
|
+
if agent_id and agent_id != current_aid:
|
|
476
|
+
return jsonify(
|
|
477
|
+
{"error": "Unauthorized: Agent scope is isolated"}
|
|
478
|
+
), 403
|
|
479
|
+
agent_id = current_aid
|
|
480
|
+
|
|
481
|
+
if not text:
|
|
482
|
+
return jsonify({"error": "text (or content) is required"}), 400
|
|
483
|
+
|
|
484
|
+
timestamp = args.get("timestamp") or _datetime_now_iso()
|
|
485
|
+
payload = {
|
|
486
|
+
"folderPath": folder_path,
|
|
487
|
+
"agentId": agent_id,
|
|
488
|
+
"text": text,
|
|
489
|
+
"timestamp": timestamp,
|
|
490
|
+
"type": args.get("type"),
|
|
491
|
+
"title": args.get("title"),
|
|
492
|
+
"concepts": args.get("concepts"),
|
|
493
|
+
"files": args.get("files"),
|
|
494
|
+
"importance": args.get("importance"),
|
|
495
|
+
}
|
|
496
|
+
obs_store = get_observation_store()
|
|
497
|
+
if obs_store is not None:
|
|
498
|
+
res = obs_store.ingest(payload)
|
|
499
|
+
else:
|
|
500
|
+
res = {"error": "ObservationStore not initialized"}
|
|
501
|
+
text_out = json.dumps(res)
|
|
502
|
+
|
|
503
|
+
elif name in ("agent_cache", "agent_remember"):
|
|
504
|
+
agent_id = args.get("agentId") or legacy.get_agent_id() or "agent"
|
|
505
|
+
content = args.get("content")
|
|
506
|
+
project = args.get("project")
|
|
507
|
+
mem_type = args.get("type") or "fact"
|
|
508
|
+
concepts = _parse_mcp_list_arg(args.get("concepts"))
|
|
509
|
+
files = _parse_mcp_list_arg(args.get("files"))
|
|
510
|
+
|
|
511
|
+
if legacy.is_agent_scope_isolated():
|
|
512
|
+
current_aid = legacy.get_agent_id()
|
|
513
|
+
if current_aid:
|
|
514
|
+
if agent_id and agent_id != current_aid:
|
|
515
|
+
return jsonify(
|
|
516
|
+
{"error": "Unauthorized: Agent scope is isolated"}
|
|
517
|
+
), 403
|
|
518
|
+
agent_id = current_aid
|
|
519
|
+
|
|
520
|
+
if not content:
|
|
521
|
+
return jsonify({"error": "content is required"}), 400
|
|
522
|
+
payload = {
|
|
523
|
+
"content": content,
|
|
524
|
+
"type": mem_type,
|
|
525
|
+
"concepts": concepts,
|
|
526
|
+
"files": files,
|
|
527
|
+
"project": project,
|
|
528
|
+
"agentId": agent_id,
|
|
529
|
+
}
|
|
530
|
+
res = legacy.remember(kv, payload)
|
|
531
|
+
text_out = json.dumps(res)
|
|
532
|
+
|
|
533
|
+
elif name in ("cache_folders", "memory_folders"):
|
|
534
|
+
pairs = sorted(
|
|
535
|
+
kv.list(KV.folders),
|
|
536
|
+
key=lambda x: x.get("lastUpdated", ""),
|
|
537
|
+
reverse=True,
|
|
538
|
+
)
|
|
539
|
+
if legacy.is_agent_scope_isolated():
|
|
540
|
+
aid = legacy.get_agent_id()
|
|
541
|
+
if aid:
|
|
542
|
+
pairs = [p for p in pairs if p.get("agentId") == aid]
|
|
543
|
+
text_out = json.dumps(pairs, indent=2)
|
|
544
|
+
|
|
545
|
+
elif name in ("cache_folder_observations", "memory_folder_observations"):
|
|
546
|
+
fp = args.get("folderPath", "")
|
|
547
|
+
aid = args.get("agentId", "")
|
|
548
|
+
if not fp or not aid:
|
|
549
|
+
return jsonify({"error": "folderPath and agentId are required"}), 400
|
|
550
|
+
if legacy.is_agent_scope_isolated():
|
|
551
|
+
current_aid = legacy.get_agent_id()
|
|
552
|
+
if current_aid and aid != current_aid:
|
|
553
|
+
return jsonify(
|
|
554
|
+
{"error": "Unauthorized: Agent scope is isolated"}
|
|
555
|
+
), 403
|
|
556
|
+
obs = sorted(
|
|
557
|
+
kv.list(KV.folder_obs(fp, aid)),
|
|
558
|
+
key=lambda x: x.get("timestamp", ""),
|
|
559
|
+
reverse=True,
|
|
560
|
+
)
|
|
561
|
+
text_out = json.dumps(obs, indent=2)
|
|
562
|
+
|
|
563
|
+
elif name in ("cache_timeline", "memory_timeline"):
|
|
564
|
+
request_aid = args.get("agentId")
|
|
565
|
+
if legacy.is_agent_scope_isolated():
|
|
566
|
+
current_aid = legacy.get_agent_id()
|
|
567
|
+
if current_aid:
|
|
568
|
+
if request_aid and request_aid != current_aid:
|
|
569
|
+
return jsonify(
|
|
570
|
+
{"error": "Unauthorized: Agent scope is isolated"}
|
|
571
|
+
), 403
|
|
572
|
+
request_aid = current_aid
|
|
573
|
+
obs_store = get_observation_store()
|
|
574
|
+
if obs_store is not None:
|
|
575
|
+
res = obs_store.timeline(
|
|
576
|
+
limit=int(args.get("limit", 100)),
|
|
577
|
+
folder_path=args.get("folderPath"),
|
|
578
|
+
agent_id=request_aid,
|
|
579
|
+
before=args.get("before"),
|
|
580
|
+
after=args.get("after"),
|
|
581
|
+
)
|
|
582
|
+
else:
|
|
583
|
+
res = []
|
|
584
|
+
text_out = json.dumps(res, indent=2)
|
|
585
|
+
|
|
586
|
+
elif name in ("cache_dedup", "memory_dedup"):
|
|
587
|
+
request_aid = args.get("agentId")
|
|
588
|
+
if legacy.is_agent_scope_isolated():
|
|
589
|
+
current_aid = legacy.get_agent_id()
|
|
590
|
+
if current_aid:
|
|
591
|
+
if request_aid and request_aid != current_aid:
|
|
592
|
+
return jsonify(
|
|
593
|
+
{"error": "Unauthorized: Agent scope is isolated"}
|
|
594
|
+
), 403
|
|
595
|
+
request_aid = current_aid
|
|
596
|
+
obs_store = get_observation_store()
|
|
597
|
+
if obs_store is not None:
|
|
598
|
+
res = obs_store.dedup(
|
|
599
|
+
args.get("folderPath") or None,
|
|
600
|
+
request_aid or None,
|
|
601
|
+
)
|
|
602
|
+
else:
|
|
603
|
+
res = {"success": False}
|
|
604
|
+
text_out = json.dumps(res, indent=2)
|
|
605
|
+
|
|
606
|
+
text_out = json.dumps(res, indent=2)
|
|
607
|
+
|
|
608
|
+
else:
|
|
609
|
+
return jsonify({"error": f"unknown tool: {name}"}), 400
|
|
610
|
+
|
|
611
|
+
return jsonify({"content": [{"type": "text", "text": text_out}]}), 200
|
|
612
|
+
|
|
613
|
+
except Exception as e:
|
|
614
|
+
return jsonify({"error": str(e)}), 500
|