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,46 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Shared service accessors for route blueprints.
|
|
3
|
+
|
|
4
|
+
Route handlers call these to reach the process-wide singletons that
|
|
5
|
+
``app.py:init_services`` populates on the Flask ``extensions`` dict.
|
|
6
|
+
Kept here so no route file has to re-derive the "extensions → app-module
|
|
7
|
+
globals → raise" fallback chain that used to live in six copies.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from flask import current_app
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _from_extensions(name: str) -> Any:
|
|
18
|
+
ext = getattr(current_app, "extensions", {}) or {}
|
|
19
|
+
value = ext.get(name)
|
|
20
|
+
if value is not None:
|
|
21
|
+
return value
|
|
22
|
+
|
|
23
|
+
from .. import app as app_module
|
|
24
|
+
|
|
25
|
+
return getattr(app_module, name, None)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def get_kv() -> Any:
|
|
29
|
+
"""Return the process StateKV. Raises RuntimeError if uninitialised."""
|
|
30
|
+
kv = _from_extensions("kv")
|
|
31
|
+
if kv is None:
|
|
32
|
+
raise RuntimeError("StateKV is not initialized")
|
|
33
|
+
return kv
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def get_search_service() -> Any:
|
|
37
|
+
"""Return the SearchService singleton (or None if search is disabled)."""
|
|
38
|
+
return _from_extensions("search_service")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def get_observation_store() -> Any:
|
|
42
|
+
"""Return the ObservationStore. Raises RuntimeError if uninitialised."""
|
|
43
|
+
store = _from_extensions("observation_store")
|
|
44
|
+
if store is None:
|
|
45
|
+
raise RuntimeError("ObservationStore is not initialized")
|
|
46
|
+
return store
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Shared authentication utilities for all route blueprints.
|
|
3
|
+
|
|
4
|
+
Public API
|
|
5
|
+
----------
|
|
6
|
+
verify_token(provided, secret) -> bool
|
|
7
|
+
Raw HMAC comparison. Used directly by the WebSocket handler in app.py.
|
|
8
|
+
|
|
9
|
+
require_auth
|
|
10
|
+
Flask route decorator. Reads AGENTCACHE_SECRET / AGENTMEMORY_SECRET from
|
|
11
|
+
the environment, validates the Authorization: Bearer <token> header, and
|
|
12
|
+
aborts with 401 JSON if the check fails. Routes with no secret configured
|
|
13
|
+
are always allowed through.
|
|
14
|
+
|
|
15
|
+
Usage in a blueprint
|
|
16
|
+
--------------------
|
|
17
|
+
from .auth import require_auth
|
|
18
|
+
|
|
19
|
+
@bp.route("/agentcache/something", methods=["GET"])
|
|
20
|
+
@require_auth
|
|
21
|
+
def api_something():
|
|
22
|
+
...
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
import hmac
|
|
26
|
+
import os
|
|
27
|
+
from functools import wraps
|
|
28
|
+
|
|
29
|
+
from flask import abort, jsonify, make_response, request
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def verify_token(provided: str, secret: str) -> bool:
|
|
33
|
+
"""Return True if *provided* matches *secret* via constant-time comparison."""
|
|
34
|
+
return hmac.compare_digest(
|
|
35
|
+
provided.encode("utf-8"),
|
|
36
|
+
secret.encode("utf-8"),
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def require_auth(f):
|
|
41
|
+
"""Decorator that enforces Bearer-token authentication on a Flask route.
|
|
42
|
+
|
|
43
|
+
If no secret is configured the request is passed through unchanged —
|
|
44
|
+
consistent with the previous per-blueprint behaviour.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
@wraps(f)
|
|
48
|
+
def _wrapped(*args, **kwargs):
|
|
49
|
+
secret = os.getenv("AGENTCACHE_SECRET") or os.getenv("AGENTMEMORY_SECRET")
|
|
50
|
+
if not secret:
|
|
51
|
+
# No secret configured → open access (matches legacy behaviour).
|
|
52
|
+
return f(*args, **kwargs)
|
|
53
|
+
|
|
54
|
+
auth_header = request.headers.get("Authorization") or request.headers.get(
|
|
55
|
+
"authorization"
|
|
56
|
+
)
|
|
57
|
+
if not auth_header or not auth_header.startswith("Bearer "):
|
|
58
|
+
resp = make_response(jsonify({"error": "unauthorized"}), 401)
|
|
59
|
+
abort(resp)
|
|
60
|
+
|
|
61
|
+
provided_token = auth_header[7:].strip()
|
|
62
|
+
if not verify_token(provided_token, secret):
|
|
63
|
+
resp = make_response(jsonify({"error": "unauthorized"}), 401)
|
|
64
|
+
abort(resp)
|
|
65
|
+
|
|
66
|
+
return f(*args, **kwargs)
|
|
67
|
+
|
|
68
|
+
return _wrapped
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Graph routes blueprint.
|
|
3
|
+
|
|
4
|
+
Handles:
|
|
5
|
+
GET /agentmemory/graph
|
|
6
|
+
GET /agentmemory/graph/stats
|
|
7
|
+
POST /agentmemory/graph/query
|
|
8
|
+
POST /agentmemory/graph/build
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from flask import Blueprint, jsonify, request
|
|
12
|
+
|
|
13
|
+
from .. import legacy as functions
|
|
14
|
+
from ._deps import get_kv
|
|
15
|
+
from .auth import require_auth
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def create_graph_bp(kv=None):
|
|
19
|
+
"""Blueprint factory — receives kv at registration time (falls back to get_kv())."""
|
|
20
|
+
bp = Blueprint("graph", __name__)
|
|
21
|
+
|
|
22
|
+
def _kv():
|
|
23
|
+
return kv if kv is not None else get_kv()
|
|
24
|
+
|
|
25
|
+
# ------------------------------------------------------------------
|
|
26
|
+
# GET /agentcache/graph
|
|
27
|
+
# ------------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
@bp.route("/agentcache/graph", methods=["GET"])
|
|
30
|
+
@bp.route("/agentmemory/graph", methods=["GET"])
|
|
31
|
+
@require_auth
|
|
32
|
+
def api_graph():
|
|
33
|
+
result = functions.folder_graph_build(_kv())
|
|
34
|
+
return jsonify(result), 200
|
|
35
|
+
|
|
36
|
+
# ------------------------------------------------------------------
|
|
37
|
+
# GET /agentcache/graph/stats
|
|
38
|
+
# ------------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
@bp.route("/agentcache/graph/stats", methods=["GET"])
|
|
41
|
+
@bp.route("/agentmemory/graph/stats", methods=["GET"])
|
|
42
|
+
@require_auth
|
|
43
|
+
def api_graph_stats():
|
|
44
|
+
g = functions.folder_graph_build(_kv())
|
|
45
|
+
node_count = len(g.get("nodes", []))
|
|
46
|
+
edge_count = len(g.get("edges", []))
|
|
47
|
+
return jsonify({"nodes": node_count, "edges": edge_count, "success": True}), 200
|
|
48
|
+
|
|
49
|
+
# ------------------------------------------------------------------
|
|
50
|
+
# POST /agentcache/graph/query
|
|
51
|
+
# ------------------------------------------------------------------
|
|
52
|
+
|
|
53
|
+
@bp.route("/agentcache/graph/query", methods=["POST"])
|
|
54
|
+
@bp.route("/agentmemory/graph/query", methods=["POST"])
|
|
55
|
+
@require_auth
|
|
56
|
+
def api_graph_query():
|
|
57
|
+
try:
|
|
58
|
+
request.get_json(force=True) or {}
|
|
59
|
+
return jsonify({"nodes": [], "edges": [], "success": True}), 200
|
|
60
|
+
except Exception as e:
|
|
61
|
+
return jsonify({"error": str(e)}), 400
|
|
62
|
+
|
|
63
|
+
# ------------------------------------------------------------------
|
|
64
|
+
# POST /agentcache/graph/build
|
|
65
|
+
# ------------------------------------------------------------------
|
|
66
|
+
|
|
67
|
+
@bp.route("/agentcache/graph/build", methods=["POST"])
|
|
68
|
+
@bp.route("/agentmemory/graph/build", methods=["POST"])
|
|
69
|
+
@require_auth
|
|
70
|
+
def api_graph_build():
|
|
71
|
+
try:
|
|
72
|
+
if functions.is_consolidation_enabled():
|
|
73
|
+
functions.consolidate(_kv())
|
|
74
|
+
return jsonify({"success": True}), 200
|
|
75
|
+
except Exception as e:
|
|
76
|
+
return jsonify({"error": str(e)}), 400
|
|
77
|
+
|
|
78
|
+
return bp
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
graph_bp = create_graph_bp(None)
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Health and audit routes blueprint.
|
|
3
|
+
|
|
4
|
+
Handles:
|
|
5
|
+
GET /agentmemory/livez
|
|
6
|
+
GET /agentmemory/health
|
|
7
|
+
GET /agentmemory/audit
|
|
8
|
+
GET /agentmemory/config/flags
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
|
|
13
|
+
from flask import Blueprint, Response, jsonify, request
|
|
14
|
+
|
|
15
|
+
from .. import legacy as functions
|
|
16
|
+
from ..legacy import query_audit
|
|
17
|
+
from ._deps import get_kv
|
|
18
|
+
from .auth import require_auth
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def create_health_bp(kv=None, embedding_provider=None):
|
|
22
|
+
"""Blueprint factory — receives kv and embedding_provider at registration time."""
|
|
23
|
+
bp = Blueprint("health", __name__)
|
|
24
|
+
|
|
25
|
+
def _kv():
|
|
26
|
+
return kv if kv is not None else get_kv()
|
|
27
|
+
|
|
28
|
+
# ------------------------------------------------------------------
|
|
29
|
+
# GET /auth.md (no auth required)
|
|
30
|
+
# ------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
@bp.route("/auth.md", methods=["GET"])
|
|
33
|
+
def auth_docs():
|
|
34
|
+
"""Serve the agent onboarding documentation in Markdown format."""
|
|
35
|
+
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
36
|
+
auth_md_path = os.path.join(base_dir, "auth.md")
|
|
37
|
+
|
|
38
|
+
if os.path.exists(auth_md_path):
|
|
39
|
+
try:
|
|
40
|
+
with open(auth_md_path, "r", encoding="utf-8") as f:
|
|
41
|
+
content = f.read()
|
|
42
|
+
return Response(
|
|
43
|
+
content,
|
|
44
|
+
mimetype="text/markdown",
|
|
45
|
+
headers={"Content-Type": "text/markdown; charset=utf-8"},
|
|
46
|
+
)
|
|
47
|
+
except Exception as e:
|
|
48
|
+
return jsonify({"error": f"failed to read auth.md: {e}"}), 500
|
|
49
|
+
|
|
50
|
+
return jsonify({"error": "auth.md not found"}), 404
|
|
51
|
+
|
|
52
|
+
# ------------------------------------------------------------------
|
|
53
|
+
# GET /agentcache/livez (no auth required)
|
|
54
|
+
# ------------------------------------------------------------------
|
|
55
|
+
|
|
56
|
+
@bp.route("/agentcache/livez", methods=["GET"])
|
|
57
|
+
@bp.route("/agentmemory/livez", methods=["GET"])
|
|
58
|
+
def livez():
|
|
59
|
+
port = int(os.getenv("III_REST_PORT", os.getenv("PORT", "3111")))
|
|
60
|
+
return jsonify(
|
|
61
|
+
{
|
|
62
|
+
"status": "ok",
|
|
63
|
+
"service": "agentcache",
|
|
64
|
+
"viewerPort": port,
|
|
65
|
+
"viewerSkipped": False,
|
|
66
|
+
}
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# ------------------------------------------------------------------
|
|
70
|
+
# GET /agentcache/health
|
|
71
|
+
# ------------------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
@bp.route("/agentcache/health", methods=["GET"])
|
|
74
|
+
@bp.route("/agentmemory/health", methods=["GET"])
|
|
75
|
+
def health():
|
|
76
|
+
return jsonify(functions.health_check(_kv()))
|
|
77
|
+
|
|
78
|
+
# ------------------------------------------------------------------
|
|
79
|
+
# GET /agentcache/audit
|
|
80
|
+
# ------------------------------------------------------------------
|
|
81
|
+
|
|
82
|
+
@bp.route("/agentcache/audit", methods=["GET"])
|
|
83
|
+
@bp.route("/agentmemory/audit", methods=["GET"])
|
|
84
|
+
@require_auth
|
|
85
|
+
def api_audit():
|
|
86
|
+
op = request.args.get("operation")
|
|
87
|
+
limit = int(request.args.get("limit", "50"))
|
|
88
|
+
res = query_audit(_kv(), {"operation": op, "limit": limit})
|
|
89
|
+
return jsonify({"entries": res, "success": True}), 200
|
|
90
|
+
|
|
91
|
+
# ------------------------------------------------------------------
|
|
92
|
+
# GET /agentcache/config/flags
|
|
93
|
+
# ------------------------------------------------------------------
|
|
94
|
+
|
|
95
|
+
@bp.route("/agentcache/config/flags", methods=["GET"])
|
|
96
|
+
@bp.route("/agentmemory/config/flags", methods=["GET"])
|
|
97
|
+
@require_auth
|
|
98
|
+
def config_flags():
|
|
99
|
+
provider_kind = "llm" if embedding_provider else "noop"
|
|
100
|
+
embedding_prov = "gemini" if embedding_provider else "none"
|
|
101
|
+
|
|
102
|
+
flags = [
|
|
103
|
+
{
|
|
104
|
+
"key": "GRAPH_EXTRACTION_ENABLED",
|
|
105
|
+
"label": "Knowledge graph extraction",
|
|
106
|
+
"enabled": functions.is_graph_extraction_enabled(),
|
|
107
|
+
"default": False,
|
|
108
|
+
"affects": ["Graph", "Dashboard"],
|
|
109
|
+
"needsLlm": True,
|
|
110
|
+
"description": "Extracts entities and relations from observations into a knowledge graph.",
|
|
111
|
+
"enableHow": "Set GRAPH_EXTRACTION_ENABLED=true and restart.",
|
|
112
|
+
"docsHref": "https://github.com/rohitg00/agentmemory#knowledge-graph",
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"key": "CONSOLIDATION_ENABLED",
|
|
116
|
+
"label": "Memory consolidation",
|
|
117
|
+
"enabled": functions.is_consolidation_enabled(),
|
|
118
|
+
"default": False,
|
|
119
|
+
"affects": ["Dashboard", "Memories", "Crystals"],
|
|
120
|
+
"needsLlm": True,
|
|
121
|
+
"description": "Periodically summarizes sessions into semantic facts + procedures.",
|
|
122
|
+
"enableHow": "Set CONSOLIDATION_ENABLED=true and restart.",
|
|
123
|
+
"docsHref": "https://github.com/rohitg00/agentmemory#consolidation",
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
"key": "AGENTCACHE_AUTO_COMPRESS",
|
|
127
|
+
"label": "LLM-powered observation compression",
|
|
128
|
+
"enabled": functions.is_auto_compress_enabled(),
|
|
129
|
+
"default": False,
|
|
130
|
+
"affects": ["Memories", "Timeline"],
|
|
131
|
+
"needsLlm": True,
|
|
132
|
+
"description": "Every observation is compressed by the LLM for richer summaries. OFF uses synthetic compression.",
|
|
133
|
+
"enableHow": "Set AGENTCACHE_AUTO_COMPRESS=true.",
|
|
134
|
+
"docsHref": "https://github.com/rohitg00/agentmemory/issues/138",
|
|
135
|
+
},
|
|
136
|
+
]
|
|
137
|
+
return jsonify(
|
|
138
|
+
{
|
|
139
|
+
"version": "0.9.9",
|
|
140
|
+
"provider": provider_kind,
|
|
141
|
+
"embeddingProvider": embedding_prov,
|
|
142
|
+
"flags": flags,
|
|
143
|
+
}
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
return bp
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
health_bp = create_health_bp(None)
|