Spaces:
Configuration error
Configuration error
fix: Added prod level migrations
Browse filesThis view is limited to 50 files because it contains too many changes. Β See raw diff
- .github/workflows/automation.yml +26 -38
- Dockerfile +5 -8
- examples/antigravity_sync.py +1 -1
- plugin/scripts/notification.py +2 -2
- plugin/scripts/post_tool_failure.py +2 -2
- plugin/scripts/post_tool_use.py +2 -2
- plugin/scripts/prompt_submit.py +1 -1
- plugin/scripts/subagent_start.py +2 -2
- plugin/scripts/subagent_stop.py +2 -2
- plugin/scripts/task_completed.py +2 -2
- pyproject.toml +38 -4
- requirements.txt +1 -10
- src/__init__.py +0 -0
- src/agentcache.egg-info/PKG-INFO +520 -0
- src/agentcache.egg-info/SOURCES.txt +59 -0
- src/agentcache.egg-info/dependency_links.txt +1 -0
- src/agentcache.egg-info/entry_points.txt +2 -0
- src/agentcache.egg-info/requires.txt +15 -0
- src/agentcache.egg-info/top_level.txt +1 -0
- src/agentcache/__init__.py +32 -0
- src/{app.py β agentcache/app.py} +284 -280
- src/{auth.md β agentcache/auth.md} +0 -0
- src/{cache β agentcache/cache}/__init__.py +12 -13
- src/{cache β agentcache/cache}/context.py +2 -2
- src/{cache β agentcache/cache}/graph.py +2 -2
- src/{cache β agentcache/cache}/health.py +2 -2
- src/{cache β agentcache/cache}/observe.py +2 -3
- src/{cache β agentcache/cache}/remember.py +2 -2
- src/{cache β agentcache/cache}/timeline.py +2 -2
- src/{cli.py β agentcache/cli.py} +6 -11
- src/{connect.py β agentcache/connect.py} +6 -13
- src/{db.py β agentcache/db.py} +3 -3
- src/{functions.py β agentcache/functions.py} +107 -50
- src/{import_data.py β agentcache/import_data.py} +3 -5
- src/{mcp_stdio.py β agentcache/mcp_stdio.py} +2 -1
- src/{replay_import.py β agentcache/replay_import.py} +23 -13
- src/{routes β agentcache/routes}/__init__.py +3 -3
- src/{routes β agentcache/routes}/graph.py +5 -3
- src/{routes β agentcache/routes}/health.py +7 -5
- src/{routes β agentcache/routes}/mcp.py +11 -7
- src/{routes β agentcache/routes}/memories.py +6 -4
- src/{routes β agentcache/routes}/migration.py +5 -3
- src/{routes β agentcache/routes}/observations.py +10 -6
- src/{routes β agentcache/routes}/search.py +5 -3
- src/{search.py β agentcache/search.py} +6 -6
- src/{storage β agentcache/storage}/__init__.py +2 -2
- src/{storage β agentcache/storage}/images.py +1 -1
- src/{storage β agentcache/storage}/paths.py +1 -1
- src/{storage β agentcache/storage}/scopes.py +0 -0
- src/{viewer β agentcache/viewer}/favicon.svg +0 -0
.github/workflows/automation.yml
CHANGED
|
@@ -1,26 +1,13 @@
|
|
| 1 |
-
name: Test
|
| 2 |
|
| 3 |
on:
|
| 4 |
push:
|
| 5 |
branches: [main]
|
| 6 |
-
|
| 7 |
-
-
|
| 8 |
-
- "CHANGELOG.md"
|
| 9 |
-
- "AGENTS.md"
|
| 10 |
-
- "ROADMAP.md"
|
| 11 |
-
- "**/*.md"
|
| 12 |
pull_request:
|
| 13 |
branches: [main]
|
| 14 |
-
paths-ignore:
|
| 15 |
-
- "README.md"
|
| 16 |
-
- "CHANGELOG.md"
|
| 17 |
-
- "AGENTS.md"
|
| 18 |
-
- "ROADMAP.md"
|
| 19 |
-
- "**/*.md"
|
| 20 |
workflow_dispatch:
|
| 21 |
-
schedule:
|
| 22 |
-
# Run every Sunday at midnight to check for dependency updates and slow/regression bugs
|
| 23 |
-
- cron: '0 0 * * 0'
|
| 24 |
|
| 25 |
concurrency:
|
| 26 |
group: ${{ github.workflow }}-${{ github.ref }}
|
|
@@ -55,7 +42,7 @@ jobs:
|
|
| 55 |
strategy:
|
| 56 |
fail-fast: false
|
| 57 |
matrix:
|
| 58 |
-
os: [ubuntu-latest, windows-latest]
|
| 59 |
python-version: ["3.10", "3.11", "3.12"]
|
| 60 |
|
| 61 |
steps:
|
|
@@ -68,7 +55,7 @@ jobs:
|
|
| 68 |
python-version: ${{ matrix.python-version }}
|
| 69 |
cache: 'pip'
|
| 70 |
|
| 71 |
-
- name: Install Dependencies
|
| 72 |
run: |
|
| 73 |
python -m pip install --upgrade pip
|
| 74 |
pip install -e ".[dev]"
|
|
@@ -79,7 +66,7 @@ jobs:
|
|
| 79 |
- name: Run Hypothesis Property-Based Tests
|
| 80 |
run: pytest tests/test_properties.py -v --tb=short
|
| 81 |
|
| 82 |
-
build-
|
| 83 |
name: Build & Package Verification
|
| 84 |
needs: automated-testing
|
| 85 |
runs-on: ubuntu-latest
|
|
@@ -92,12 +79,15 @@ jobs:
|
|
| 92 |
with:
|
| 93 |
python-version: "3.11"
|
| 94 |
|
| 95 |
-
- name: Install Build
|
| 96 |
-
run: pip install build
|
| 97 |
|
| 98 |
- name: Build Package (wheel & sdist)
|
| 99 |
run: python -m build
|
| 100 |
|
|
|
|
|
|
|
|
|
|
| 101 |
- name: Upload Build Artifacts
|
| 102 |
uses: actions/upload-artifact@v4
|
| 103 |
with:
|
|
@@ -105,24 +95,22 @@ jobs:
|
|
| 105 |
path: dist/
|
| 106 |
retention-days: 5
|
| 107 |
|
| 108 |
-
|
| 109 |
-
name:
|
| 110 |
-
needs:
|
| 111 |
-
|
|
|
|
| 112 |
runs-on: ubuntu-latest
|
|
|
|
|
|
|
| 113 |
steps:
|
| 114 |
-
- name:
|
| 115 |
-
uses: actions/
|
| 116 |
with:
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
- name: Push to Hugging Face Space
|
| 121 |
-
env:
|
| 122 |
-
HF_TOKEN: ${{ secrets.HF_TOKEN }}
|
| 123 |
-
run: |
|
| 124 |
-
git config --global user.email "bot@github.com"
|
| 125 |
-
git config --global user.name "GitHub Action Bot"
|
| 126 |
-
git remote add hf https://Yash030:$HF_TOKEN@huggingface.co/spaces/Yash030/agentmemory-python
|
| 127 |
-
git push --force hf main:main
|
| 128 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Test & Publish Pipeline
|
| 2 |
|
| 3 |
on:
|
| 4 |
push:
|
| 5 |
branches: [main]
|
| 6 |
+
tags:
|
| 7 |
+
- 'v*'
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
pull_request:
|
| 9 |
branches: [main]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
workflow_dispatch:
|
|
|
|
|
|
|
|
|
|
| 11 |
|
| 12 |
concurrency:
|
| 13 |
group: ${{ github.workflow }}-${{ github.ref }}
|
|
|
|
| 42 |
strategy:
|
| 43 |
fail-fast: false
|
| 44 |
matrix:
|
| 45 |
+
os: [ubuntu-latest, windows-latest, macos-latest]
|
| 46 |
python-version: ["3.10", "3.11", "3.12"]
|
| 47 |
|
| 48 |
steps:
|
|
|
|
| 55 |
python-version: ${{ matrix.python-version }}
|
| 56 |
cache: 'pip'
|
| 57 |
|
| 58 |
+
- name: Install Package & Dev Dependencies
|
| 59 |
run: |
|
| 60 |
python -m pip install --upgrade pip
|
| 61 |
pip install -e ".[dev]"
|
|
|
|
| 66 |
- name: Run Hypothesis Property-Based Tests
|
| 67 |
run: pytest tests/test_properties.py -v --tb=short
|
| 68 |
|
| 69 |
+
build-and-verify:
|
| 70 |
name: Build & Package Verification
|
| 71 |
needs: automated-testing
|
| 72 |
runs-on: ubuntu-latest
|
|
|
|
| 79 |
with:
|
| 80 |
python-version: "3.11"
|
| 81 |
|
| 82 |
+
- name: Install Build and Twine
|
| 83 |
+
run: pip install build twine
|
| 84 |
|
| 85 |
- name: Build Package (wheel & sdist)
|
| 86 |
run: python -m build
|
| 87 |
|
| 88 |
+
- name: Verify Package Metadata
|
| 89 |
+
run: twine check dist/*
|
| 90 |
+
|
| 91 |
- name: Upload Build Artifacts
|
| 92 |
uses: actions/upload-artifact@v4
|
| 93 |
with:
|
|
|
|
| 95 |
path: dist/
|
| 96 |
retention-days: 5
|
| 97 |
|
| 98 |
+
publish-to-pypi:
|
| 99 |
+
name: Publish to PyPI
|
| 100 |
+
needs: build-and-verify
|
| 101 |
+
# Only run on pushes of version tags (e.g. v0.9.8) to the main branch
|
| 102 |
+
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
|
| 103 |
runs-on: ubuntu-latest
|
| 104 |
+
permissions:
|
| 105 |
+
id-token: write
|
| 106 |
steps:
|
| 107 |
+
- name: Download Build Artifacts
|
| 108 |
+
uses: actions/download-artifact@v4
|
| 109 |
with:
|
| 110 |
+
name: agentcache-dist
|
| 111 |
+
path: dist/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 112 |
|
| 113 |
+
- name: Publish Package to PyPI
|
| 114 |
+
uses: pypa/gh-action-pypi-publish@release/v1
|
| 115 |
+
with:
|
| 116 |
+
print-hash: true
|
Dockerfile
CHANGED
|
@@ -15,14 +15,11 @@ RUN useradd -m -u 1000 user
|
|
| 15 |
# Set up workdir
|
| 16 |
WORKDIR /app
|
| 17 |
|
| 18 |
-
# Copy
|
| 19 |
-
COPY
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
COPY --chown=user:user src /app/src
|
| 24 |
-
COPY --chown=user:user start.sh /app/start.sh
|
| 25 |
-
COPY --chown=user:user sync.py /app/sync.py
|
| 26 |
|
| 27 |
# Give permissions
|
| 28 |
RUN chmod +x /app/start.sh && chown -R user:user /app /home/user
|
|
|
|
| 15 |
# Set up workdir
|
| 16 |
WORKDIR /app
|
| 17 |
|
| 18 |
+
# Copy all application files first to support package installation
|
| 19 |
+
COPY --chown=user:user . /app
|
| 20 |
+
|
| 21 |
+
# Install the package directly in non-editable mode for production deployment
|
| 22 |
+
RUN pip install --no-cache-dir /app
|
|
|
|
|
|
|
|
|
|
| 23 |
|
| 24 |
# Give permissions
|
| 25 |
RUN chmod +x /app/start.sh && chown -R user:user /app /home/user
|
examples/antigravity_sync.py
CHANGED
|
@@ -146,7 +146,7 @@ def perform_antigravity_sync_local(args):
|
|
| 146 |
current_prompt = p_text.strip()
|
| 147 |
current_timestamp = step.get("created_at")
|
| 148 |
elif step_type == "PLANNER_RESPONSE" and current_prompt:
|
| 149 |
-
ts = current_timestamp or step.get("created_at") or datetime.datetime.
|
| 150 |
turns.append({"prompt": current_prompt, "response": step.get("content", ""), "timestamp": ts})
|
| 151 |
current_prompt = None
|
| 152 |
current_timestamp = None
|
|
|
|
| 146 |
current_prompt = p_text.strip()
|
| 147 |
current_timestamp = step.get("created_at")
|
| 148 |
elif step_type == "PLANNER_RESPONSE" and current_prompt:
|
| 149 |
+
ts = current_timestamp or step.get("created_at") or datetime.datetime.now(datetime.timezone.utc).isoformat().replace("+00:00", "Z")
|
| 150 |
turns.append({"prompt": current_prompt, "response": step.get("content", ""), "timestamp": ts})
|
| 151 |
current_prompt = None
|
| 152 |
current_timestamp = None
|
plugin/scripts/notification.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
| 2 |
import sys
|
| 3 |
import json
|
| 4 |
import time
|
| 5 |
-
from datetime import datetime
|
| 6 |
from hook_utils import resolve_project, is_sdk_child, api_call_bg
|
| 7 |
|
| 8 |
def main():
|
|
@@ -28,7 +28,7 @@ def main():
|
|
| 28 |
"sessionId": session_id,
|
| 29 |
"project": resolve_project(data.get("cwd")),
|
| 30 |
"cwd": data.get("cwd") or "",
|
| 31 |
-
"timestamp": datetime.
|
| 32 |
"data": {
|
| 33 |
"notification_type": notification_type,
|
| 34 |
"title": data.get("title"),
|
|
|
|
| 2 |
import sys
|
| 3 |
import json
|
| 4 |
import time
|
| 5 |
+
from datetime import datetime, timezone
|
| 6 |
from hook_utils import resolve_project, is_sdk_child, api_call_bg
|
| 7 |
|
| 8 |
def main():
|
|
|
|
| 28 |
"sessionId": session_id,
|
| 29 |
"project": resolve_project(data.get("cwd")),
|
| 30 |
"cwd": data.get("cwd") or "",
|
| 31 |
+
"timestamp": datetime.now(timezone.utc).isoformat().replace("+00:00", "Z"),
|
| 32 |
"data": {
|
| 33 |
"notification_type": notification_type,
|
| 34 |
"title": data.get("title"),
|
plugin/scripts/post_tool_failure.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
| 2 |
import sys
|
| 3 |
import json
|
| 4 |
import time
|
| 5 |
-
from datetime import datetime
|
| 6 |
from hook_utils import resolve_project, is_sdk_child, api_call_bg
|
| 7 |
|
| 8 |
def main():
|
|
@@ -34,7 +34,7 @@ def main():
|
|
| 34 |
"sessionId": session_id,
|
| 35 |
"project": resolve_project(data.get("cwd")),
|
| 36 |
"cwd": data.get("cwd") or "",
|
| 37 |
-
"timestamp": datetime.
|
| 38 |
"data": {
|
| 39 |
"tool_name": tool_name,
|
| 40 |
"tool_input": limit_str(tool_input),
|
|
|
|
| 2 |
import sys
|
| 3 |
import json
|
| 4 |
import time
|
| 5 |
+
from datetime import datetime, timezone
|
| 6 |
from hook_utils import resolve_project, is_sdk_child, api_call_bg
|
| 7 |
|
| 8 |
def main():
|
|
|
|
| 34 |
"sessionId": session_id,
|
| 35 |
"project": resolve_project(data.get("cwd")),
|
| 36 |
"cwd": data.get("cwd") or "",
|
| 37 |
+
"timestamp": datetime.now(timezone.utc).isoformat().replace("+00:00", "Z"),
|
| 38 |
"data": {
|
| 39 |
"tool_name": tool_name,
|
| 40 |
"tool_input": limit_str(tool_input),
|
plugin/scripts/post_tool_use.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
| 2 |
import sys
|
| 3 |
import json
|
| 4 |
import time
|
| 5 |
-
from datetime import datetime
|
| 6 |
from hook_utils import resolve_project, is_sdk_child, api_call_bg
|
| 7 |
|
| 8 |
def is_base64_image(val):
|
|
@@ -80,7 +80,7 @@ def main():
|
|
| 80 |
"sessionId": session_id,
|
| 81 |
"project": resolve_project(data.get("cwd")),
|
| 82 |
"cwd": data.get("cwd") or "",
|
| 83 |
-
"timestamp": datetime.
|
| 84 |
"data": {
|
| 85 |
"tool_name": tool_name,
|
| 86 |
"tool_input": tool_input,
|
|
|
|
| 2 |
import sys
|
| 3 |
import json
|
| 4 |
import time
|
| 5 |
+
from datetime import datetime, timezone
|
| 6 |
from hook_utils import resolve_project, is_sdk_child, api_call_bg
|
| 7 |
|
| 8 |
def is_base64_image(val):
|
|
|
|
| 80 |
"sessionId": session_id,
|
| 81 |
"project": resolve_project(data.get("cwd")),
|
| 82 |
"cwd": data.get("cwd") or "",
|
| 83 |
+
"timestamp": datetime.now(timezone.utc).isoformat().replace("+00:00", "Z"),
|
| 84 |
"data": {
|
| 85 |
"tool_name": tool_name,
|
| 86 |
"tool_input": tool_input,
|
plugin/scripts/prompt_submit.py
CHANGED
|
@@ -42,7 +42,7 @@ def main():
|
|
| 42 |
"sessionId": session_id,
|
| 43 |
"project": project,
|
| 44 |
"cwd": cwd,
|
| 45 |
-
"timestamp": datetime.
|
| 46 |
"data": {
|
| 47 |
"prompt": prompt
|
| 48 |
}
|
|
|
|
| 42 |
"sessionId": session_id,
|
| 43 |
"project": project,
|
| 44 |
"cwd": cwd,
|
| 45 |
+
"timestamp": datetime.now(timezone.utc).isoformat().replace("+00:00", "Z"),
|
| 46 |
"data": {
|
| 47 |
"prompt": prompt
|
| 48 |
}
|
plugin/scripts/subagent_start.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
| 2 |
import sys
|
| 3 |
import json
|
| 4 |
import time
|
| 5 |
-
from datetime import datetime
|
| 6 |
from hook_utils import resolve_project, is_sdk_child, api_call_bg
|
| 7 |
|
| 8 |
def main():
|
|
@@ -26,7 +26,7 @@ def main():
|
|
| 26 |
"sessionId": session_id,
|
| 27 |
"project": resolve_project(data.get("cwd")),
|
| 28 |
"cwd": data.get("cwd") or "",
|
| 29 |
-
"timestamp": datetime.
|
| 30 |
"data": {
|
| 31 |
"agent_id": agent_id,
|
| 32 |
"agent_type": agent_type
|
|
|
|
| 2 |
import sys
|
| 3 |
import json
|
| 4 |
import time
|
| 5 |
+
from datetime import datetime, timezone
|
| 6 |
from hook_utils import resolve_project, is_sdk_child, api_call_bg
|
| 7 |
|
| 8 |
def main():
|
|
|
|
| 26 |
"sessionId": session_id,
|
| 27 |
"project": resolve_project(data.get("cwd")),
|
| 28 |
"cwd": data.get("cwd") or "",
|
| 29 |
+
"timestamp": datetime.now(timezone.utc).isoformat().replace("+00:00", "Z"),
|
| 30 |
"data": {
|
| 31 |
"agent_id": agent_id,
|
| 32 |
"agent_type": agent_type
|
plugin/scripts/subagent_stop.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
| 2 |
import sys
|
| 3 |
import json
|
| 4 |
import time
|
| 5 |
-
from datetime import datetime
|
| 6 |
from hook_utils import resolve_project, is_sdk_child, api_call_bg
|
| 7 |
|
| 8 |
def main():
|
|
@@ -32,7 +32,7 @@ def main():
|
|
| 32 |
"sessionId": session_id,
|
| 33 |
"project": resolve_project(data.get("cwd")),
|
| 34 |
"cwd": data.get("cwd") or "",
|
| 35 |
-
"timestamp": datetime.
|
| 36 |
"data": {
|
| 37 |
"agent_id": agent_id,
|
| 38 |
"agent_type": agent_type,
|
|
|
|
| 2 |
import sys
|
| 3 |
import json
|
| 4 |
import time
|
| 5 |
+
from datetime import datetime, timezone
|
| 6 |
from hook_utils import resolve_project, is_sdk_child, api_call_bg
|
| 7 |
|
| 8 |
def main():
|
|
|
|
| 32 |
"sessionId": session_id,
|
| 33 |
"project": resolve_project(data.get("cwd")),
|
| 34 |
"cwd": data.get("cwd") or "",
|
| 35 |
+
"timestamp": datetime.now(timezone.utc).isoformat().replace("+00:00", "Z"),
|
| 36 |
"data": {
|
| 37 |
"agent_id": agent_id,
|
| 38 |
"agent_type": agent_type,
|
plugin/scripts/task_completed.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
| 2 |
import sys
|
| 3 |
import json
|
| 4 |
import time
|
| 5 |
-
from datetime import datetime
|
| 6 |
from hook_utils import resolve_project, is_sdk_child, api_call_bg
|
| 7 |
|
| 8 |
def main():
|
|
@@ -29,7 +29,7 @@ def main():
|
|
| 29 |
"sessionId": session_id,
|
| 30 |
"project": resolve_project(data.get("cwd")),
|
| 31 |
"cwd": data.get("cwd") or "",
|
| 32 |
-
"timestamp": datetime.
|
| 33 |
"data": {
|
| 34 |
"task_id": data.get("task_id"),
|
| 35 |
"task_subject": data.get("task_subject"),
|
|
|
|
| 2 |
import sys
|
| 3 |
import json
|
| 4 |
import time
|
| 5 |
+
from datetime import datetime, timezone
|
| 6 |
from hook_utils import resolve_project, is_sdk_child, api_call_bg
|
| 7 |
|
| 8 |
def main():
|
|
|
|
| 29 |
"sessionId": session_id,
|
| 30 |
"project": resolve_project(data.get("cwd")),
|
| 31 |
"cwd": data.get("cwd") or "",
|
| 32 |
+
"timestamp": datetime.now(timezone.utc).isoformat().replace("+00:00", "Z"),
|
| 33 |
"data": {
|
| 34 |
"task_id": data.get("task_id"),
|
| 35 |
"task_subject": data.get("task_subject"),
|
pyproject.toml
CHANGED
|
@@ -7,8 +7,26 @@ name = "agentcache"
|
|
| 7 |
version = "0.9.8"
|
| 8 |
description = "A Python REST + WebSocket + MCP cache server for AI agents, backed by SQLite"
|
| 9 |
readme = "README.md"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
license = { text = "MIT" }
|
| 11 |
requires-python = ">=3.10"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
dependencies = [
|
| 13 |
"flask>=3.0.0",
|
| 14 |
"flask-sock>=0.7.0",
|
|
@@ -22,21 +40,37 @@ dependencies = [
|
|
| 22 |
dev = [
|
| 23 |
"pytest>=8.0.0",
|
| 24 |
"hypothesis>=6.100.0",
|
|
|
|
|
|
|
| 25 |
]
|
| 26 |
local-embeddings = [
|
| 27 |
"sentence-transformers>=2.7.0",
|
| 28 |
]
|
| 29 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
[project.scripts]
|
| 31 |
-
agentcache = "
|
| 32 |
|
| 33 |
[tool.setuptools.packages.find]
|
| 34 |
-
where = ["
|
| 35 |
-
include = ["
|
| 36 |
|
| 37 |
[tool.setuptools.package-data]
|
| 38 |
-
"
|
| 39 |
|
| 40 |
[tool.pytest.ini_options]
|
| 41 |
testpaths = ["tests"]
|
| 42 |
python_files = ["test_*.py"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
version = "0.9.8"
|
| 8 |
description = "A Python REST + WebSocket + MCP cache server for AI agents, backed by SQLite"
|
| 9 |
readme = "README.md"
|
| 10 |
+
authors = [
|
| 11 |
+
{ name = "Yash Wanjarwadkar", email = "yashwanjarwadkar@gmail.com" }
|
| 12 |
+
]
|
| 13 |
+
maintainers = [
|
| 14 |
+
{ name = "Yash Wanjarwadkar", email = "yashwanjarwadkar@gmail.com" }
|
| 15 |
+
]
|
| 16 |
license = { text = "MIT" }
|
| 17 |
requires-python = ">=3.10"
|
| 18 |
+
classifiers = [
|
| 19 |
+
"Development Status :: 4 - Beta",
|
| 20 |
+
"Intended Audience :: Developers",
|
| 21 |
+
"License :: OSI Approved :: MIT License",
|
| 22 |
+
"Operating System :: OS Independent",
|
| 23 |
+
"Programming Language :: Python :: 3",
|
| 24 |
+
"Programming Language :: Python :: 3.10",
|
| 25 |
+
"Programming Language :: Python :: 3.11",
|
| 26 |
+
"Programming Language :: Python :: 3.12",
|
| 27 |
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
| 28 |
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
| 29 |
+
]
|
| 30 |
dependencies = [
|
| 31 |
"flask>=3.0.0",
|
| 32 |
"flask-sock>=0.7.0",
|
|
|
|
| 40 |
dev = [
|
| 41 |
"pytest>=8.0.0",
|
| 42 |
"hypothesis>=6.100.0",
|
| 43 |
+
"ruff>=0.3.0",
|
| 44 |
+
"twine>=5.0.0",
|
| 45 |
]
|
| 46 |
local-embeddings = [
|
| 47 |
"sentence-transformers>=2.7.0",
|
| 48 |
]
|
| 49 |
|
| 50 |
+
[project.urls]
|
| 51 |
+
Homepage = "https://github.com/Yash030/agentcache-python"
|
| 52 |
+
Repository = "https://github.com/Yash030/agentcache-python.git"
|
| 53 |
+
"Bug Tracker" = "https://github.com/Yash030/agentcache-python/issues"
|
| 54 |
+
Documentation = "https://github.com/Yash030/agentcache-python#readme"
|
| 55 |
+
|
| 56 |
[project.scripts]
|
| 57 |
+
agentcache = "agentcache.cli:main"
|
| 58 |
|
| 59 |
[tool.setuptools.packages.find]
|
| 60 |
+
where = ["src"]
|
| 61 |
+
include = ["agentcache*"]
|
| 62 |
|
| 63 |
[tool.setuptools.package-data]
|
| 64 |
+
"agentcache.viewer" = ["*.html", "*.svg", "*.js", "*.css"]
|
| 65 |
|
| 66 |
[tool.pytest.ini_options]
|
| 67 |
testpaths = ["tests"]
|
| 68 |
python_files = ["test_*.py"]
|
| 69 |
+
|
| 70 |
+
[tool.ruff]
|
| 71 |
+
line-length = 88
|
| 72 |
+
target-version = "py310"
|
| 73 |
+
|
| 74 |
+
[tool.ruff.lint]
|
| 75 |
+
select = ["E", "F", "I", "W"]
|
| 76 |
+
ignore = ["E501"]
|
requirements.txt
CHANGED
|
@@ -1,10 +1 @@
|
|
| 1 |
-
|
| 2 |
-
flask-sock>=0.7.0
|
| 3 |
-
requests>=2.31.0
|
| 4 |
-
websockets>=12.0
|
| 5 |
-
python-dateutil>=2.8.2
|
| 6 |
-
huggingface_hub>=0.20.0
|
| 7 |
-
|
| 8 |
-
# dev
|
| 9 |
-
pytest==8.3.5
|
| 10 |
-
hypothesis==6.131.15
|
|
|
|
| 1 |
+
-e .[dev,local-embeddings]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/__init__.py
DELETED
|
File without changes
|
src/agentcache.egg-info/PKG-INFO
ADDED
|
@@ -0,0 +1,520 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Metadata-Version: 2.1
|
| 2 |
+
Name: agentcache
|
| 3 |
+
Version: 0.9.8
|
| 4 |
+
Summary: A Python REST + WebSocket + MCP cache server for AI agents, backed by SQLite
|
| 5 |
+
Author-email: Yash Wanjarwadkar <yashwanjarwadkar@gmail.com>
|
| 6 |
+
Maintainer-email: Yash Wanjarwadkar <yashwanjarwadkar@gmail.com>
|
| 7 |
+
License: MIT
|
| 8 |
+
Project-URL: Homepage, https://github.com/Yash030/agentcache-python
|
| 9 |
+
Project-URL: Repository, https://github.com/Yash030/agentcache-python.git
|
| 10 |
+
Project-URL: Bug Tracker, https://github.com/Yash030/agentcache-python/issues
|
| 11 |
+
Project-URL: Documentation, https://github.com/Yash030/agentcache-python#readme
|
| 12 |
+
Classifier: Development Status :: 4 - Beta
|
| 13 |
+
Classifier: Intended Audience :: Developers
|
| 14 |
+
Classifier: License :: OSI Approved :: MIT License
|
| 15 |
+
Classifier: Operating System :: OS Independent
|
| 16 |
+
Classifier: Programming Language :: Python :: 3
|
| 17 |
+
Classifier: Programming Language :: Python :: 3.10
|
| 18 |
+
Classifier: Programming Language :: Python :: 3.11
|
| 19 |
+
Classifier: Programming Language :: Python :: 3.12
|
| 20 |
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
| 21 |
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
| 22 |
+
Requires-Python: >=3.10
|
| 23 |
+
Description-Content-Type: text/markdown
|
| 24 |
+
License-File: LICENSE
|
| 25 |
+
Requires-Dist: flask>=3.0.0
|
| 26 |
+
Requires-Dist: flask-sock>=0.7.0
|
| 27 |
+
Requires-Dist: requests>=2.31.0
|
| 28 |
+
Requires-Dist: python-dateutil>=2.8.2
|
| 29 |
+
Requires-Dist: huggingface_hub>=0.20.0
|
| 30 |
+
Requires-Dist: websockets>=12.0
|
| 31 |
+
Provides-Extra: dev
|
| 32 |
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
| 33 |
+
Requires-Dist: hypothesis>=6.100.0; extra == "dev"
|
| 34 |
+
Requires-Dist: ruff>=0.3.0; extra == "dev"
|
| 35 |
+
Requires-Dist: twine>=5.0.0; extra == "dev"
|
| 36 |
+
Provides-Extra: local-embeddings
|
| 37 |
+
Requires-Dist: sentence-transformers>=2.7.0; extra == "local-embeddings"
|
| 38 |
+
|
| 39 |
+
---
|
| 40 |
+
title: AgentCache Python
|
| 41 |
+
emoji: π§
|
| 42 |
+
colorFrom: blue
|
| 43 |
+
colorTo: indigo
|
| 44 |
+
sdk: docker
|
| 45 |
+
pinned: false
|
| 46 |
+
---
|
| 47 |
+
|
| 48 |
+
<h1 align="center">agentcache-python</h1>
|
| 49 |
+
|
| 50 |
+
<p align="center">
|
| 51 |
+
<strong>Persistent memory for AI coding agents β pure Python, zero external databases.</strong><br/>
|
| 52 |
+
Works with Claude Code, Cursor, Cline, Windsurf, Gemini CLI, and any MCP client.
|
| 53 |
+
</p>
|
| 54 |
+
|
| 55 |
+
<p align="center">
|
| 56 |
+
<img src="https://img.shields.io/badge/Python-3.10%2B-3776AB?style=for-the-badge&logo=python&logoColor=white" alt="Python 3.10+" />
|
| 57 |
+
<img src="https://img.shields.io/badge/SQLite-WAL-003B57?style=for-the-badge&logo=sqlite&logoColor=white" alt="SQLite WAL" />
|
| 58 |
+
<img src="https://img.shields.io/badge/Flask-3.0-000000?style=for-the-badge&logo=flask&logoColor=white" alt="Flask 3.0" />
|
| 59 |
+
<img src="https://img.shields.io/badge/MCP-Compatible-6B21A8?style=for-the-badge" alt="MCP Compatible" />
|
| 60 |
+
<img src="https://img.shields.io/badge/HuggingFace-Space-FF9D00?style=for-the-badge&logo=huggingface&logoColor=white" alt="HuggingFace Space" />
|
| 61 |
+
</p>
|
| 62 |
+
|
| 63 |
+
<p align="center">
|
| 64 |
+
<a href="#quick-start">Quick Start</a> •
|
| 65 |
+
<a href="#features">Features</a> •
|
| 66 |
+
<a href="#mcp-integration">MCP</a> •
|
| 67 |
+
<a href="#api-reference">API</a> •
|
| 68 |
+
<a href="#configuration">Config</a> •
|
| 69 |
+
<a href="#deploy-to-huggingface">Deploy</a> •
|
| 70 |
+
<a href="#viewer">Viewer</a> •
|
| 71 |
+
<a href="#architecture">Architecture</a>
|
| 72 |
+
</p>
|
| 73 |
+
|
| 74 |
+
---
|
| 75 |
+
|
| 76 |
+
## What Is This?
|
| 77 |
+
|
| 78 |
+
**agentcache-python** is a Python reimplementation of the [agentcache](https://github.com/rohitg00/agentcache) persistent memory server. It exposes a REST API, WebSocket stream, and MCP tools endpoint that AI coding agents use to store and retrieve session observations, long-term memories, lessons, and pinned memory slots.
|
| 79 |
+
|
| 80 |
+
Key differences from the Node.js original:
|
| 81 |
+
|
| 82 |
+
- **No Node.js or iii-engine** β runs with plain `python src/app.py`
|
| 83 |
+
- **SQLite instead of Dolt** β single file, WAL mode, instant startup
|
| 84 |
+
- **HuggingFace Space ready** β deploys in one click, data synced to an HF dataset repo
|
| 85 |
+
- **Same REST + MCP wire format** β drop-in for any agent already wired to agentcache
|
| 86 |
+
|
| 87 |
+
Your agent captures every tool call, stores them as observations, compresses them into searchable memory, and injects the right context at the start of every new session β automatically.
|
| 88 |
+
|
| 89 |
+
---
|
| 90 |
+
|
| 91 |
+
## Quick Start
|
| 92 |
+
|
| 93 |
+
### Run locally
|
| 94 |
+
|
| 95 |
+
```bash
|
| 96 |
+
# Clone
|
| 97 |
+
git clone https://github.com/Yashwant00CR7/agentcache.git
|
| 98 |
+
cd agentcache
|
| 99 |
+
|
| 100 |
+
# Install dependencies (no build step)
|
| 101 |
+
pip install -r requirements.txt
|
| 102 |
+
|
| 103 |
+
# Start the server
|
| 104 |
+
python src/app.py
|
| 105 |
+
```
|
| 106 |
+
|
| 107 |
+
Server starts on **http://localhost:3111**. Open the viewer at http://localhost:3111/viewer.
|
| 108 |
+
|
| 109 |
+
### Verify it works
|
| 110 |
+
|
| 111 |
+
```bash
|
| 112 |
+
# Health check
|
| 113 |
+
curl http://localhost:3111/agentcache/livez
|
| 114 |
+
# {"status": "ok"}
|
| 115 |
+
|
| 116 |
+
# Save a memory
|
| 117 |
+
curl -X POST http://localhost:3111/agentcache/remember \
|
| 118 |
+
-H "Content-Type: application/json" \
|
| 119 |
+
-d '{"content": "JWT auth uses jose middleware in src/middleware/auth.ts", "concepts": ["auth", "jwt"]}'
|
| 120 |
+
|
| 121 |
+
# Recall it
|
| 122 |
+
curl -X POST http://localhost:3111/agentcache/search \
|
| 123 |
+
-H "Content-Type: application/json" \
|
| 124 |
+
-d '{"query": "authentication middleware", "limit": 5}'
|
| 125 |
+
```
|
| 126 |
+
|
| 127 |
+
---
|
| 128 |
+
|
| 129 |
+
## Features
|
| 130 |
+
|
| 131 |
+
| Feature | Status | Notes |
|
| 132 |
+
|---------|--------|-------|
|
| 133 |
+
| REST API β sessions, memories, observations | β
| Full surface |
|
| 134 |
+
| WebSocket live stream | β
| `/stream/mem-live/viewer` |
|
| 135 |
+
| MCP tools endpoint | β
| 31 tools |
|
| 136 |
+
| Built-in HTML viewer | β
| Real-time dashboard at `/viewer` |
|
| 137 |
+
| BM25 keyword search | β
| Always on, no API key needed |
|
| 138 |
+
| Hybrid BM25 + vector search | β
| Requires `GEMINI_API_KEY` |
|
| 139 |
+
| 4-tier memory consolidation | βοΈ | `CONSOLIDATION_ENABLED=true` + LLM key |
|
| 140 |
+
| Knowledge graph extraction | βοΈ | `GRAPH_EXTRACTION_ENABLED=true` + LLM key |
|
| 141 |
+
| LLM observation compression | βοΈ | `AGENTCACHE_AUTO_COMPRESS=true` + LLM key |
|
| 142 |
+
| Lessons with confidence decay | β
| Fingerprinted, auto-strengthen on repeat |
|
| 143 |
+
| Memory slots (pinned context) | β
| CRUD + auto-reflect |
|
| 144 |
+
| Session replay | β
| Full timeline in viewer |
|
| 145 |
+
| Audit log | β
| Tracks every write with agent_id + timestamp |
|
| 146 |
+
| HuggingFace Space deploy | β
| One-click, data synced to dataset repo |
|
| 147 |
+
| Privacy filtering | β
| Strips API keys, tokens before storage |
|
| 148 |
+
|
| 149 |
+
### 4-Tier Memory Model
|
| 150 |
+
|
| 151 |
+
Inspired by how human memory works β raw experience β compressed episodes β extracted facts β learned patterns.
|
| 152 |
+
|
| 153 |
+
| Tier | What | When |
|
| 154 |
+
|------|------|------|
|
| 155 |
+
| **Working** | Raw observations from tool use | Every tool call |
|
| 156 |
+
| **Episodic** | Compressed session summaries | Session end |
|
| 157 |
+
| **Semantic** | Extracted facts and patterns | Consolidation |
|
| 158 |
+
| **Procedural** | Workflows and decision patterns | Consolidation |
|
| 159 |
+
|
| 160 |
+
---
|
| 161 |
+
|
| 162 |
+
## MCP Integration
|
| 163 |
+
|
| 164 |
+
Wire agentcache-python into your agent's MCP config. It speaks the same MCP protocol as the Node.js original.
|
| 165 |
+
|
| 166 |
+
### Most agents (Cursor, Claude Desktop, Cline, Windsurf)
|
| 167 |
+
|
| 168 |
+
```json
|
| 169 |
+
{
|
| 170 |
+
"mcpServers": {
|
| 171 |
+
"agentcache": {
|
| 172 |
+
"command": "npx",
|
| 173 |
+
"args": ["-y", "@agentcache/mcp"],
|
| 174 |
+
"env": {
|
| 175 |
+
"AGENTCACHE_URL": "http://localhost:3111"
|
| 176 |
+
}
|
| 177 |
+
}
|
| 178 |
+
}
|
| 179 |
+
}
|
| 180 |
+
```
|
| 181 |
+
|
| 182 |
+
### Claude Code
|
| 183 |
+
|
| 184 |
+
Paste this prompt and your agent will wire everything:
|
| 185 |
+
|
| 186 |
+
```
|
| 187 |
+
Start agentcache-python: run `python src/app.py` from the agentcache-python directory.
|
| 188 |
+
Then add this MCP server to ~/.claude.json under mcpServers:
|
| 189 |
+
{
|
| 190 |
+
"agentcache": {
|
| 191 |
+
"command": "npx",
|
| 192 |
+
"args": ["-y", "@agentcache/mcp"],
|
| 193 |
+
"env": { "AGENTCACHE_URL": "http://localhost:3111" }
|
| 194 |
+
}
|
| 195 |
+
}
|
| 196 |
+
Verify with: curl http://localhost:3111/agentcache/livez
|
| 197 |
+
Open the viewer at: http://localhost:3111/viewer
|
| 198 |
+
```
|
| 199 |
+
|
| 200 |
+
### Available MCP Tools (31)
|
| 201 |
+
|
| 202 |
+
| Tool | Description |
|
| 203 |
+
|------|-------------|
|
| 204 |
+
| `memory_save` | Save a long-term insight, decision, or pattern |
|
| 205 |
+
| `memory_recall` | Search past observations by keyword |
|
| 206 |
+
| `memory_smart_search` | Hybrid BM25 + vector semantic search |
|
| 207 |
+
| `memory_sessions` | List recent sessions |
|
| 208 |
+
| `memory_sessions_list` | Retrieve all memory sessions |
|
| 209 |
+
| `memory_timeline` | Chronological observations for a session |
|
| 210 |
+
| `memory_observations` | Observations for a session |
|
| 211 |
+
| `memory_profile` | Per-project concept + file profile |
|
| 212 |
+
| `memory_lessons` | List active lessons with confidence scores |
|
| 213 |
+
| `memory_lesson_save` | Save a lesson (duplicate saves strengthen it) |
|
| 214 |
+
| `memory_lesson_recall` | Search lessons by query |
|
| 215 |
+
| `memory_lesson_search` | Search lessons by keywords |
|
| 216 |
+
| `memory_consolidate` | Run 4-tier memory consolidation |
|
| 217 |
+
| `memory_reflect` | Reflect on session, update context |
|
| 218 |
+
| `memory_diagnose` | Health check across all subsystems |
|
| 219 |
+
| `memory_forget` | Delete memory, session, or observations |
|
| 220 |
+
| `memory_export` | Export all memory data as JSON |
|
| 221 |
+
| `agent_observe` | Log agent execution observation |
|
| 222 |
+
| `agent_remember` | Save agent cache to long-term storage |
|
| 223 |
+
| `memory_antigravity_sync` | Sync Antigravity transcripts to memory |
|
| 224 |
+
| `memory_antigravity_sync_all` | Master sync: transcript + crystallize + reflect |
|
| 225 |
+
| `memory_slot_list` | List all pinned memory slots |
|
| 226 |
+
| `memory_slot_get` | Retrieve a specific pinned memory slot |
|
| 227 |
+
| `memory_slot_create` | Create/overwrite a pinned memory slot |
|
| 228 |
+
| `memory_slot_append` | Append text content to a pinned memory slot |
|
| 229 |
+
| `memory_slot_replace` | Replace pinned memory slot content |
|
| 230 |
+
| `memory_slot_delete` | Delete a pinned memory slot |
|
| 231 |
+
| `memory_action_create` | Create a new work item / action |
|
| 232 |
+
| `memory_action_update` | Update fields of an existing action |
|
| 233 |
+
| `memory_frontier` | Get active and pending actions sorted by priority |
|
| 234 |
+
| `memory_crystallize` | Crystallize/summarize observations in a session |
|
| 235 |
+
|
| 236 |
+
---
|
| 237 |
+
|
| 238 |
+
## API Reference
|
| 239 |
+
|
| 240 |
+
Base URL: `http://localhost:3111/agentcache`
|
| 241 |
+
|
| 242 |
+
### Health
|
| 243 |
+
|
| 244 |
+
| Method | Path | Description |
|
| 245 |
+
|--------|------|-------------|
|
| 246 |
+
| `GET` | `/livez` | Liveness probe β no auth required |
|
| 247 |
+
|
| 248 |
+
### Sessions
|
| 249 |
+
|
| 250 |
+
| Method | Path | Description |
|
| 251 |
+
|--------|------|-------------|
|
| 252 |
+
| `POST` | `/session/start` | Start a new session |
|
| 253 |
+
| `POST` | `/session/end` | End a session |
|
| 254 |
+
| `POST` | `/session/commit` | Commit session with summary |
|
| 255 |
+
| `GET` | `/sessions` | List all sessions |
|
| 256 |
+
|
| 257 |
+
### Observations
|
| 258 |
+
|
| 259 |
+
| Method | Path | Description |
|
| 260 |
+
|--------|------|-------------|
|
| 261 |
+
| `POST` | `/observe` | Ingest a hook event observation |
|
| 262 |
+
| `POST` | `/agent/observe` | Simplified observe for direct agent use |
|
| 263 |
+
| `GET` | `/observations` | List observations (`?session_id=`) |
|
| 264 |
+
| `POST` | `/timeline` | Chronological observation window |
|
| 265 |
+
|
| 266 |
+
### Memories
|
| 267 |
+
|
| 268 |
+
| Method | Path | Description |
|
| 269 |
+
|--------|------|-------------|
|
| 270 |
+
| `POST` | `/remember` | Save long-term memory |
|
| 271 |
+
| `POST` | `/agent/remember` | Simplified remember |
|
| 272 |
+
| `POST` | `/forget` | Delete memory / session / observations |
|
| 273 |
+
| `POST` | `/search` | BM25 + vector search |
|
| 274 |
+
| `POST` | `/context` | Compile context for a session + project |
|
| 275 |
+
| `GET` | `/memories` | List memories (`?latest=true&limit=N`) |
|
| 276 |
+
| `POST` | `/evolve` | Create a new memory version |
|
| 277 |
+
|
| 278 |
+
### Lessons
|
| 279 |
+
|
| 280 |
+
| Method | Path | Description |
|
| 281 |
+
|--------|------|-------------|
|
| 282 |
+
| `GET` | `/lessons` | List lessons |
|
| 283 |
+
| `POST` | `/lessons` | Create lesson |
|
| 284 |
+
| `POST` | `/lessons/search` | Search lessons |
|
| 285 |
+
| `POST` | `/lessons/strengthen` | Reinforce an existing lesson |
|
| 286 |
+
|
| 287 |
+
### Slots
|
| 288 |
+
|
| 289 |
+
| Method | Path | Description |
|
| 290 |
+
|--------|------|-------------|
|
| 291 |
+
| `GET` | `/slots` | List all pinned slots |
|
| 292 |
+
| `POST` | `/slot` | Create or update a slot |
|
| 293 |
+
| `GET` | `/slot` | Get slot by name |
|
| 294 |
+
| `DELETE` | `/slot` | Delete a slot |
|
| 295 |
+
| `POST` | `/slot/reflect` | Auto-populate from session observations |
|
| 296 |
+
|
| 297 |
+
### Graph + Profile
|
| 298 |
+
|
| 299 |
+
| Method | Path | Description |
|
| 300 |
+
|--------|------|-------------|
|
| 301 |
+
| `GET` | `/relations` | Knowledge graph edges |
|
| 302 |
+
| `POST` | `/relations` | Add a relation |
|
| 303 |
+
| `GET` | `/profile` | Project profile (top concepts, files) |
|
| 304 |
+
|
| 305 |
+
### Actions
|
| 306 |
+
|
| 307 |
+
| Method | Path | Description |
|
| 308 |
+
|--------|------|-------------|
|
| 309 |
+
| `GET` | `/actions` | List actions |
|
| 310 |
+
| `POST` | `/actions` | Create an action |
|
| 311 |
+
| `PATCH` | `/actions/<id>` | Update action status / fields |
|
| 312 |
+
| `GET` | `/frontier` | Pending actions sorted by priority |
|
| 313 |
+
| `GET` | `/insights` | List insights |
|
| 314 |
+
|
| 315 |
+
### Replay
|
| 316 |
+
|
| 317 |
+
| Method | Path | Description |
|
| 318 |
+
|--------|------|-------------|
|
| 319 |
+
| `GET` | `/replay/sessions` | Sessions list for replay tab |
|
| 320 |
+
| `GET` | `/replay/load` | Full session + observations (`?sessionId=`) |
|
| 321 |
+
|
| 322 |
+
### MCP
|
| 323 |
+
|
| 324 |
+
| Method | Path | Description |
|
| 325 |
+
|--------|------|-------------|
|
| 326 |
+
| `GET` | `/mcp/tools` | MCP tool schema list |
|
| 327 |
+
| `POST` | `/mcp/tools` | MCP tool call dispatch |
|
| 328 |
+
|
| 329 |
+
---
|
| 330 |
+
|
| 331 |
+
## Configuration
|
| 332 |
+
|
| 333 |
+
Create `~/.agentcache/.env` (no `export` prefix needed):
|
| 334 |
+
|
| 335 |
+
```env
|
| 336 |
+
# Server port
|
| 337 |
+
III_REST_PORT=3111
|
| 338 |
+
|
| 339 |
+
# Vector search β enables Gemini 768-dim embeddings
|
| 340 |
+
GEMINI_API_KEY=your-gemini-key
|
| 341 |
+
|
| 342 |
+
# LLM for compression / consolidation / graph extraction
|
| 343 |
+
# Any one of these enables LLM features:
|
| 344 |
+
ANTHROPIC_API_KEY=your-anthropic-key
|
| 345 |
+
# OPENAI_API_KEY=your-openai-key
|
| 346 |
+
# GEMINI_API_KEY=your-key (same key as above works for both)
|
| 347 |
+
|
| 348 |
+
# LLM-powered features (disabled by default β spend tokens)
|
| 349 |
+
CONSOLIDATION_ENABLED=true
|
| 350 |
+
GRAPH_EXTRACTION_ENABLED=true
|
| 351 |
+
AGENTCACHE_AUTO_COMPRESS=true
|
| 352 |
+
|
| 353 |
+
# Context injection limits
|
| 354 |
+
TOKEN_BUDGET=2000
|
| 355 |
+
MAX_OBS_PER_SESSION=500
|
| 356 |
+
|
| 357 |
+
# Auth β set to require Bearer token on all endpoints
|
| 358 |
+
AGENTCACHE_SECRET=your-secret
|
| 359 |
+
|
| 360 |
+
# Agent scope isolation
|
| 361 |
+
AGENT_ID=my-agent
|
| 362 |
+
AGENTCACHE_AGENT_SCOPE=isolated # only see this agent's data
|
| 363 |
+
|
| 364 |
+
# HuggingFace sync
|
| 365 |
+
HF_TOKEN=your-hf-token
|
| 366 |
+
AGENTCACHE_DATASET_REPO=username/agentcache-data
|
| 367 |
+
```
|
| 368 |
+
|
| 369 |
+
### Full Variable Reference
|
| 370 |
+
|
| 371 |
+
| Variable | Default | Purpose |
|
| 372 |
+
|----------|---------|---------|
|
| 373 |
+
| `III_REST_PORT` / `PORT` | `3111` | API server port |
|
| 374 |
+
| `GEMINI_API_KEY` / `GOOGLE_API_KEY` | β | Enables 768-dim vector search |
|
| 375 |
+
| `AGENTCACHE_SECRET` | β | Bearer token auth on all endpoints |
|
| 376 |
+
| `AGENT_ID` | β | Default agent ID for scope isolation |
|
| 377 |
+
| `AGENTCACHE_AGENT_SCOPE=isolated` | β | Filters data to current `AGENT_ID` |
|
| 378 |
+
| `MAX_OBS_PER_SESSION` | `500` | Hard cap on observations per session |
|
| 379 |
+
| `TOKEN_BUDGET` | `2000` | Max tokens in compiled context |
|
| 380 |
+
| `GRAPH_EXTRACTION_ENABLED` | `false` | Knowledge graph (needs LLM) |
|
| 381 |
+
| `CONSOLIDATION_ENABLED` | `false` | Memory consolidation (needs LLM) |
|
| 382 |
+
| `AGENTCACHE_AUTO_COMPRESS` | `false` | LLM observation compression |
|
| 383 |
+
|
| 384 |
+
---
|
| 385 |
+
|
| 386 |
+
## Viewer
|
| 387 |
+
|
| 388 |
+
Built-in dashboard at **http://localhost:3111/viewer**.
|
| 389 |
+
|
| 390 |
+
| Tab | What You See |
|
| 391 |
+
|-----|-------------|
|
| 392 |
+
| **Dashboard** | Session stats, memory counts, recent activity |
|
| 393 |
+
| **Sessions** | Browse sessions, inspect observations |
|
| 394 |
+
| **Memories** | Search, filter, and read long-term memories |
|
| 395 |
+
| **Graph** | Project folder visualization β nodes = folders, edges = shared concepts or parent path |
|
| 396 |
+
| **Timeline** | Per-session chronological observation view |
|
| 397 |
+
| **Lessons** | Confidence-scored lessons with decay tracking |
|
| 398 |
+
| **Slots** | Pinned memory slots editor |
|
| 399 |
+
| **Replay** | Scrub through past sessions frame by frame |
|
| 400 |
+
|
| 401 |
+
---
|
| 402 |
+
|
| 403 |
+
## Deploy to HuggingFace
|
| 404 |
+
|
| 405 |
+
This project is designed to run as a HuggingFace Space. Data is stored in an HF dataset repo and restored on every boot β so no persistent disk is needed.
|
| 406 |
+
|
| 407 |
+
### Setup
|
| 408 |
+
|
| 409 |
+
1. Fork this repo as a HuggingFace Space (SDK: Docker)
|
| 410 |
+
2. Create a dataset repo (e.g. `your-username/agentcache-data`)
|
| 411 |
+
3. Add Space secrets in the HF dashboard:
|
| 412 |
+
|
| 413 |
+
| Secret | Value |
|
| 414 |
+
|--------|-------|
|
| 415 |
+
| `HF_TOKEN` | Your HF write token |
|
| 416 |
+
| `AGENTCACHE_DATASET_REPO` | `your-username/agentcache-data` |
|
| 417 |
+
| `AGENTCACHE_SECRET` | A random secret (optional but recommended) |
|
| 418 |
+
| `GEMINI_API_KEY` | Gemini key (optional, enables vector search) |
|
| 419 |
+
|
| 420 |
+
4. The Space boots, restores `agentcache.db` from the dataset repo, and starts the server
|
| 421 |
+
|
| 422 |
+
### How sync works
|
| 423 |
+
|
| 424 |
+
`sync.py` uses mtime fingerprinting β it only uploads when the database actually changed, so there are no unnecessary uploads during idle periods.
|
| 425 |
+
|
| 426 |
+
```bash
|
| 427 |
+
# Manual backup
|
| 428 |
+
python sync.py
|
| 429 |
+
|
| 430 |
+
# Environment for sync
|
| 431 |
+
HF_TOKEN=...
|
| 432 |
+
AGENTCACHE_DATASET_REPO=username/agentcache-data
|
| 433 |
+
```
|
| 434 |
+
|
| 435 |
+
---
|
| 436 |
+
|
| 437 |
+
## Architecture
|
| 438 |
+
|
| 439 |
+
```
|
| 440 |
+
agentcache-python/
|
| 441 |
+
βββ src/
|
| 442 |
+
β βββ app.py Flask server β all endpoints, WebSocket broadcaster
|
| 443 |
+
β βββ db.py SQLite StateKV β WAL mode, audit_log table
|
| 444 |
+
β βββ functions.py Core logic β observe, remember, search, context
|
| 445 |
+
β βββ search.py BM25 + Gemini vector index + HybridSearch (RRF)
|
| 446 |
+
β βββ viewer/
|
| 447 |
+
β βββ index.html Single-file HTML dashboard (no build step)
|
| 448 |
+
βββ sync.py HuggingFace dataset backup/restore
|
| 449 |
+
βββ Dockerfile HF Space container
|
| 450 |
+
βββ start.sh Boot script (restore β start server β start sync)
|
| 451 |
+
βββ requirements.txt 6 Python dependencies, no external DB required
|
| 452 |
+
```
|
| 453 |
+
|
| 454 |
+
### Database layout
|
| 455 |
+
|
| 456 |
+
Two SQLite tables in `~/.agentcache/agentcache.db`:
|
| 457 |
+
|
| 458 |
+
```sql
|
| 459 |
+
-- All data lives here, namespaced by scope
|
| 460 |
+
kv_store (
|
| 461 |
+
scope TEXT NOT NULL, -- e.g. "mem:sessions", "mem:obs:{session_id}"
|
| 462 |
+
key TEXT NOT NULL,
|
| 463 |
+
value TEXT NOT NULL, -- JSON-serialized
|
| 464 |
+
PRIMARY KEY (scope, key)
|
| 465 |
+
)
|
| 466 |
+
|
| 467 |
+
-- Audit trail replaces Dolt git versioning
|
| 468 |
+
audit_log (
|
| 469 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 470 |
+
ts INTEGER NOT NULL, -- unix millis
|
| 471 |
+
agent_id TEXT NOT NULL,
|
| 472 |
+
message TEXT NOT NULL
|
| 473 |
+
)
|
| 474 |
+
```
|
| 475 |
+
|
| 476 |
+
### Search pipeline
|
| 477 |
+
|
| 478 |
+
```
|
| 479 |
+
Query
|
| 480 |
+
β BM25 (always) β Porter-stemmed keyword matching
|
| 481 |
+
β Vector (if Gemini key) β 768-dim cosine similarity
|
| 482 |
+
β RRF fusion β Reciprocal Rank Fusion (k=60)
|
| 483 |
+
β Session diversify β max 3 results per session
|
| 484 |
+
β Return top-K
|
| 485 |
+
```
|
| 486 |
+
|
| 487 |
+
---
|
| 488 |
+
|
| 489 |
+
## vs Original agentcache
|
| 490 |
+
|
| 491 |
+
| | agentcache (Node.js) | agentcache-python |
|
| 492 |
+
|---|---|---|
|
| 493 |
+
| Runtime | Node.js 20+ | Python 3.10+ |
|
| 494 |
+
| Storage | Dolt SQL (git-versioned MySQL) | SQLite WAL (single file) |
|
| 495 |
+
| Engine dependency | iii-engine (separate binary) | None β just Flask |
|
| 496 |
+
| Embeddings | 6 providers + local `@xenova/transformers` | Gemini 768-dim |
|
| 497 |
+
| MCP tools | 53 | 31 |
|
| 498 |
+
| REST endpoints | 128 | ~50 |
|
| 499 |
+
| Deploy | npm, Docker, fly.io, Railway, Render | Docker, HuggingFace Spaces |
|
| 500 |
+
| Cold boot | ~7s (iii engine warm-up) | <2s |
|
| 501 |
+
| Database size | ~232MB (417 Dolt chunk files) | ~20MB (single `.db` file) |
|
| 502 |
+
| Setup | `npm install -g @agentcache/agentcache` | `pip install -r requirements.txt` |
|
| 503 |
+
|
| 504 |
+
Choose the Python version for: simpler setup, HF Space deployment, single-file database, no Node.js, or Python ecosystem integration.
|
| 505 |
+
|
| 506 |
+
Choose the Node.js version for: the full 53-tool MCP surface, iii-engine observability, production multi-agent deployments, or the full auto-hook suite.
|
| 507 |
+
|
| 508 |
+
---
|
| 509 |
+
|
| 510 |
+
## Contributing
|
| 511 |
+
|
| 512 |
+
See [CONTRIBUTING.md](CONTRIBUTING.md). Issues and PRs welcome.
|
| 513 |
+
|
| 514 |
+
Priority areas: test coverage, additional embedding providers, more agent hook scripts.
|
| 515 |
+
|
| 516 |
+
---
|
| 517 |
+
|
| 518 |
+
## License
|
| 519 |
+
|
| 520 |
+
Apache-2.0 β see [LICENSE](LICENSE).
|
src/agentcache.egg-info/SOURCES.txt
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
LICENSE
|
| 2 |
+
README.md
|
| 3 |
+
pyproject.toml
|
| 4 |
+
src/agentcache/__init__.py
|
| 5 |
+
src/agentcache/app.py
|
| 6 |
+
src/agentcache/cli.py
|
| 7 |
+
src/agentcache/connect.py
|
| 8 |
+
src/agentcache/db.py
|
| 9 |
+
src/agentcache/functions.py
|
| 10 |
+
src/agentcache/import_data.py
|
| 11 |
+
src/agentcache/mcp_stdio.py
|
| 12 |
+
src/agentcache/replay_import.py
|
| 13 |
+
src/agentcache/search.py
|
| 14 |
+
src/agentcache/viewer_helpers.py
|
| 15 |
+
src/agentcache/workers.py
|
| 16 |
+
src/agentcache.egg-info/PKG-INFO
|
| 17 |
+
src/agentcache.egg-info/SOURCES.txt
|
| 18 |
+
src/agentcache.egg-info/dependency_links.txt
|
| 19 |
+
src/agentcache.egg-info/entry_points.txt
|
| 20 |
+
src/agentcache.egg-info/requires.txt
|
| 21 |
+
src/agentcache.egg-info/top_level.txt
|
| 22 |
+
src/agentcache/cache/__init__.py
|
| 23 |
+
src/agentcache/cache/context.py
|
| 24 |
+
src/agentcache/cache/graph.py
|
| 25 |
+
src/agentcache/cache/health.py
|
| 26 |
+
src/agentcache/cache/observe.py
|
| 27 |
+
src/agentcache/cache/remember.py
|
| 28 |
+
src/agentcache/cache/timeline.py
|
| 29 |
+
src/agentcache/routes/__init__.py
|
| 30 |
+
src/agentcache/routes/graph.py
|
| 31 |
+
src/agentcache/routes/health.py
|
| 32 |
+
src/agentcache/routes/mcp.py
|
| 33 |
+
src/agentcache/routes/memories.py
|
| 34 |
+
src/agentcache/routes/migration.py
|
| 35 |
+
src/agentcache/routes/observations.py
|
| 36 |
+
src/agentcache/routes/search.py
|
| 37 |
+
src/agentcache/storage/__init__.py
|
| 38 |
+
src/agentcache/storage/images.py
|
| 39 |
+
src/agentcache/storage/paths.py
|
| 40 |
+
src/agentcache/storage/scopes.py
|
| 41 |
+
src/agentcache/viewer/favicon.svg
|
| 42 |
+
src/agentcache/viewer/index.html
|
| 43 |
+
tests/test_api.py
|
| 44 |
+
tests/test_auth.py
|
| 45 |
+
tests/test_context.py
|
| 46 |
+
tests/test_debounce.py
|
| 47 |
+
tests/test_folder_graph_build.py
|
| 48 |
+
tests/test_folder_observe.py
|
| 49 |
+
tests/test_forget.py
|
| 50 |
+
tests/test_graph.py
|
| 51 |
+
tests/test_migration.py
|
| 52 |
+
tests/test_normalize.py
|
| 53 |
+
tests/test_obs_lookup.py
|
| 54 |
+
tests/test_observe_core.py
|
| 55 |
+
tests/test_properties.py
|
| 56 |
+
tests/test_remember.py
|
| 57 |
+
tests/test_route_regressions.py
|
| 58 |
+
tests/test_search.py
|
| 59 |
+
tests/test_timeline.py
|
src/agentcache.egg-info/dependency_links.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
|
src/agentcache.egg-info/entry_points.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[console_scripts]
|
| 2 |
+
agentcache = agentcache.cli:main
|
src/agentcache.egg-info/requires.txt
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
flask>=3.0.0
|
| 2 |
+
flask-sock>=0.7.0
|
| 3 |
+
requests>=2.31.0
|
| 4 |
+
python-dateutil>=2.8.2
|
| 5 |
+
huggingface_hub>=0.20.0
|
| 6 |
+
websockets>=12.0
|
| 7 |
+
|
| 8 |
+
[dev]
|
| 9 |
+
pytest>=8.0.0
|
| 10 |
+
hypothesis>=6.100.0
|
| 11 |
+
ruff>=0.3.0
|
| 12 |
+
twine>=5.0.0
|
| 13 |
+
|
| 14 |
+
[local-embeddings]
|
| 15 |
+
sentence-transformers>=2.7.0
|
src/agentcache.egg-info/top_level.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
agentcache
|
src/agentcache/__init__.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
agentcache β A Python REST + WebSocket + MCP cache server for AI agents, backed by SQLite.
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
__version__ = "0.9.8"
|
| 6 |
+
|
| 7 |
+
from .app import create_app
|
| 8 |
+
from .connect import run_connect
|
| 9 |
+
from .db import StateKV
|
| 10 |
+
from .functions import (
|
| 11 |
+
folder_graph_build,
|
| 12 |
+
folder_observe,
|
| 13 |
+
folder_search,
|
| 14 |
+
folder_timeline,
|
| 15 |
+
forget,
|
| 16 |
+
health_check,
|
| 17 |
+
remember,
|
| 18 |
+
)
|
| 19 |
+
|
| 20 |
+
__all__ = [
|
| 21 |
+
"__version__",
|
| 22 |
+
"create_app",
|
| 23 |
+
"StateKV",
|
| 24 |
+
"run_connect",
|
| 25 |
+
"folder_observe",
|
| 26 |
+
"folder_search",
|
| 27 |
+
"folder_timeline",
|
| 28 |
+
"folder_graph_build",
|
| 29 |
+
"remember",
|
| 30 |
+
"forget",
|
| 31 |
+
"health_check",
|
| 32 |
+
]
|
src/{app.py β agentcache/app.py}
RENAMED
|
@@ -1,280 +1,284 @@
|
|
| 1 |
-
"""
|
| 2 |
-
agentmemory-python β Flask application factory.
|
| 3 |
-
|
| 4 |
-
Entry point: create_app() returns a fully configured Flask app.
|
| 5 |
-
Run directly: python src/app.py
|
| 6 |
-
"""
|
| 7 |
-
|
| 8 |
-
import
|
| 9 |
-
import
|
| 10 |
-
import
|
| 11 |
-
import
|
| 12 |
-
|
| 13 |
-
from
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
import functions
|
| 59 |
-
from
|
| 60 |
-
from
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
#
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
functions.
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
resp.headers["Access-Control-Allow-
|
| 259 |
-
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
|
| 276 |
-
|
| 277 |
-
|
| 278 |
-
|
| 279 |
-
|
| 280 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
agentmemory-python β Flask application factory.
|
| 3 |
+
|
| 4 |
+
Entry point: create_app() returns a fully configured Flask app.
|
| 5 |
+
Run directly: python src/app.py
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import hmac
|
| 9 |
+
import json
|
| 10 |
+
import os
|
| 11 |
+
import sys
|
| 12 |
+
|
| 13 |
+
from flask import Flask, request, send_from_directory
|
| 14 |
+
from flask_sock import Sock
|
| 15 |
+
|
| 16 |
+
# Prevent double-import of app when run directly as __main__
|
| 17 |
+
if __name__ == "__main__":
|
| 18 |
+
sys.modules["app"] = sys.modules["__main__"]
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def _load_env() -> None:
|
| 22 |
+
env_path = os.path.join(os.path.expanduser("~"), ".agentcache", ".env")
|
| 23 |
+
if not os.path.exists(env_path):
|
| 24 |
+
env_path = os.path.join(os.path.expanduser("~"), ".agentmemory", ".env")
|
| 25 |
+
if not os.path.exists(env_path):
|
| 26 |
+
return
|
| 27 |
+
try:
|
| 28 |
+
with open(env_path, "r", encoding="utf-8") as f:
|
| 29 |
+
for line in f:
|
| 30 |
+
line = line.strip()
|
| 31 |
+
if not line or line.startswith("#") or "=" not in line:
|
| 32 |
+
continue
|
| 33 |
+
k, v = line.split("=", 1)
|
| 34 |
+
os.environ[k.strip()] = v.strip().strip('"').strip("'")
|
| 35 |
+
print(f"[config] Loaded environment from {env_path}")
|
| 36 |
+
except Exception as e:
|
| 37 |
+
print(f"[config] Error reading env file: {e}")
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
_load_env()
|
| 41 |
+
|
| 42 |
+
# Module-level globals β set once by create_app(), read by blueprints via `import app`
|
| 43 |
+
kv = None
|
| 44 |
+
embedding_provider = None
|
| 45 |
+
persistence = None
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def create_app() -> Flask:
|
| 49 |
+
"""Create and return a fully configured Flask application."""
|
| 50 |
+
global kv, embedding_provider, persistence
|
| 51 |
+
|
| 52 |
+
# Check security credentials
|
| 53 |
+
if not os.getenv("AGENTCACHE_SECRET") and not os.getenv("AGENTMEMORY_SECRET"):
|
| 54 |
+
print(
|
| 55 |
+
"[security] WARNING: AGENTCACHE_SECRET/AGENTMEMORY_SECRET is not set! All API endpoints are publicly accessible without authentication."
|
| 56 |
+
)
|
| 57 |
+
|
| 58 |
+
from . import functions
|
| 59 |
+
from . import search as search_mod
|
| 60 |
+
from .db import StateKV
|
| 61 |
+
from .viewer_helpers import make_viewer_response
|
| 62 |
+
|
| 63 |
+
# 1. DB
|
| 64 |
+
kv = StateKV()
|
| 65 |
+
|
| 66 |
+
# 2. Embedding provider β auto-select by priority (D5.3):
|
| 67 |
+
# GEMINI_API_KEY β OPENAI_API_KEY β AGENTCACHE_LOCAL_EMBEDDING_MODEL β BM25-only
|
| 68 |
+
api_key = os.getenv("GEMINI_API_KEY") or os.getenv("GOOGLE_API_KEY")
|
| 69 |
+
openai_key = os.getenv("OPENAI_API_KEY")
|
| 70 |
+
local_model = os.getenv("AGENTCACHE_LOCAL_EMBEDDING_MODEL") or os.getenv(
|
| 71 |
+
"AGENTMEMORY_LOCAL_EMBEDDING_MODEL"
|
| 72 |
+
)
|
| 73 |
+
|
| 74 |
+
if api_key:
|
| 75 |
+
try:
|
| 76 |
+
embedding_provider = search_mod.GeminiEmbeddingProvider(api_key)
|
| 77 |
+
functions.set_embedding_provider(embedding_provider)
|
| 78 |
+
print(
|
| 79 |
+
f"[search] Embedding provider active: gemini ({embedding_provider.dimensions} dims)"
|
| 80 |
+
)
|
| 81 |
+
except Exception as e:
|
| 82 |
+
print(f"[search] Error initialising Gemini embedding provider: {e}")
|
| 83 |
+
elif openai_key:
|
| 84 |
+
try:
|
| 85 |
+
embedding_provider = search_mod.OpenAIEmbeddingProvider(openai_key)
|
| 86 |
+
functions.set_embedding_provider(embedding_provider)
|
| 87 |
+
print(
|
| 88 |
+
f"[search] Embedding provider active: openai ({embedding_provider.dimensions} dims)"
|
| 89 |
+
)
|
| 90 |
+
except Exception as e:
|
| 91 |
+
print(f"[search] Error initialising OpenAI embedding provider: {e}")
|
| 92 |
+
elif local_model:
|
| 93 |
+
try:
|
| 94 |
+
embedding_provider = search_mod.SentenceTransformerProvider(local_model)
|
| 95 |
+
functions.set_embedding_provider(embedding_provider)
|
| 96 |
+
print(
|
| 97 |
+
f"[search] Embedding provider active: sentence-transformers/{local_model} ({embedding_provider.dimensions} dims)"
|
| 98 |
+
)
|
| 99 |
+
except ImportError as e:
|
| 100 |
+
print(f"[search] sentence-transformers not installed: {e}")
|
| 101 |
+
except Exception as e:
|
| 102 |
+
print(f"[search] Error initialising SentenceTransformer provider: {e}")
|
| 103 |
+
else:
|
| 104 |
+
print("[search] No embedding API key found β running in BM25-only mode.")
|
| 105 |
+
|
| 106 |
+
# 3. Index persistence β use embedding_provider variable set above
|
| 107 |
+
has_vector = embedding_provider is not None
|
| 108 |
+
persistence = functions.IndexPersistence(
|
| 109 |
+
kv,
|
| 110 |
+
functions._bm25_index,
|
| 111 |
+
functions._vector_index if has_vector else None,
|
| 112 |
+
)
|
| 113 |
+
functions.set_index_persistence(persistence)
|
| 114 |
+
loaded = persistence.load()
|
| 115 |
+
print(
|
| 116 |
+
f"[persistence] Load results: BM25={loaded['bm25']}, Vector={loaded['vector']}"
|
| 117 |
+
)
|
| 118 |
+
|
| 119 |
+
# Backfill coordinate lookup index if missing/incomplete
|
| 120 |
+
try:
|
| 121 |
+
functions.backfill_obs_lookup_if_needed(kv)
|
| 122 |
+
except Exception as e:
|
| 123 |
+
print(f"[db] Warning backfilling obs_lookup: {e}")
|
| 124 |
+
|
| 125 |
+
# 4. Flask app + blueprints
|
| 126 |
+
flask_app = Flask(__name__)
|
| 127 |
+
from werkzeug.middleware.proxy_fix import ProxyFix
|
| 128 |
+
|
| 129 |
+
flask_app.wsgi_app = ProxyFix(
|
| 130 |
+
flask_app.wsgi_app, x_proto=1, x_host=1, x_port=1, x_prefix=1
|
| 131 |
+
)
|
| 132 |
+
from .routes import register_blueprints
|
| 133 |
+
|
| 134 |
+
register_blueprints(flask_app)
|
| 135 |
+
|
| 136 |
+
# 5. WebSocket broadcaster
|
| 137 |
+
sock = Sock(flask_app)
|
| 138 |
+
_ws_clients: set = set()
|
| 139 |
+
|
| 140 |
+
@sock.route("/stream/mem-live/viewer")
|
| 141 |
+
def stream_viewer(ws):
|
| 142 |
+
secret = os.getenv("AGENTCACHE_SECRET") or os.getenv("AGENTMEMORY_SECRET")
|
| 143 |
+
if secret:
|
| 144 |
+
token = request.args.get("token") or request.args.get("secret")
|
| 145 |
+
if not token or not hmac.compare_digest(
|
| 146 |
+
token.encode("utf-8"), secret.encode("utf-8")
|
| 147 |
+
):
|
| 148 |
+
ws.close(1008)
|
| 149 |
+
return
|
| 150 |
+
_ws_clients.add(ws)
|
| 151 |
+
try:
|
| 152 |
+
while ws.receive() is not None:
|
| 153 |
+
pass
|
| 154 |
+
except Exception:
|
| 155 |
+
pass
|
| 156 |
+
finally:
|
| 157 |
+
_ws_clients.discard(ws)
|
| 158 |
+
|
| 159 |
+
def _broadcast(payload: dict) -> None:
|
| 160 |
+
msg = json.dumps(payload)
|
| 161 |
+
for ws in list(_ws_clients):
|
| 162 |
+
try:
|
| 163 |
+
ws.send(msg)
|
| 164 |
+
except Exception:
|
| 165 |
+
_ws_clients.discard(ws)
|
| 166 |
+
|
| 167 |
+
functions.set_stream_broadcaster(_broadcast)
|
| 168 |
+
|
| 169 |
+
# 6. Viewer static routes
|
| 170 |
+
from importlib.resources import files
|
| 171 |
+
|
| 172 |
+
_viewer_resources = files("agentcache").joinpath("viewer")
|
| 173 |
+
_base_dir = str(_viewer_resources.parent)
|
| 174 |
+
|
| 175 |
+
@flask_app.route("/")
|
| 176 |
+
@flask_app.route("/viewer")
|
| 177 |
+
@flask_app.route("/agentcache/viewer")
|
| 178 |
+
@flask_app.route("/agentmemory/viewer")
|
| 179 |
+
def serve_viewer():
|
| 180 |
+
try:
|
| 181 |
+
return make_viewer_response(_base_dir)
|
| 182 |
+
except Exception as e:
|
| 183 |
+
return f"Viewer not found: {e}", 404
|
| 184 |
+
|
| 185 |
+
@flask_app.route("/favicon.svg")
|
| 186 |
+
def serve_favicon():
|
| 187 |
+
return send_from_directory(str(_viewer_resources), "favicon.svg")
|
| 188 |
+
|
| 189 |
+
# 7. CORS after_request β D2.1: configurable via AGENTCACHE_CORS_ORIGINS env var
|
| 190 |
+
# Default allows localhost, 127.0.0.1, HuggingFace Spaces, vscode-webview://, chrome-extension://
|
| 191 |
+
# Wildcard entries like "*.hf.space" match any subdomain via suffix check.
|
| 192 |
+
_default_cors = (
|
| 193 |
+
"http://localhost,http://127.0.0.1,"
|
| 194 |
+
"https://huggingface.co,https://*.hf.space,"
|
| 195 |
+
"vscode-webview://*,chrome-extension://*"
|
| 196 |
+
)
|
| 197 |
+
_cors_origins_raw = (
|
| 198 |
+
os.getenv("AGENTCACHE_CORS_ORIGINS")
|
| 199 |
+
or os.getenv("AGENTMEMORY_CORS_ORIGINS")
|
| 200 |
+
or _default_cors
|
| 201 |
+
)
|
| 202 |
+
|
| 203 |
+
def _parse_cors_origins(raw: str):
|
| 204 |
+
"""Return (exact_set, suffix_list) for efficient origin matching."""
|
| 205 |
+
exact, suffixes = set(), []
|
| 206 |
+
for entry in raw.split(","):
|
| 207 |
+
entry = entry.strip()
|
| 208 |
+
if not entry:
|
| 209 |
+
continue
|
| 210 |
+
if entry.startswith("*."):
|
| 211 |
+
# *.hf.space β match anything ending with .hf.space
|
| 212 |
+
suffixes.append(entry[1:].lower()) # keep the leading dot: ".hf.space"
|
| 213 |
+
elif "*" in entry:
|
| 214 |
+
# generic prefix wildcard: strip trailing * and treat as prefix
|
| 215 |
+
suffixes.append(("prefix:", entry.rstrip("*").lower()))
|
| 216 |
+
else:
|
| 217 |
+
exact.add(entry.lower())
|
| 218 |
+
return exact, suffixes
|
| 219 |
+
|
| 220 |
+
_cors_exact, _cors_suffixes = _parse_cors_origins(_cors_origins_raw)
|
| 221 |
+
|
| 222 |
+
def _origin_allowed(origin: str) -> bool:
|
| 223 |
+
lo = origin.lower()
|
| 224 |
+
if lo in _cors_exact:
|
| 225 |
+
return True
|
| 226 |
+
for s in _cors_suffixes:
|
| 227 |
+
if isinstance(s, tuple) and s[0] == "prefix:":
|
| 228 |
+
if lo.startswith(s[1]):
|
| 229 |
+
return True
|
| 230 |
+
elif lo.endswith(s):
|
| 231 |
+
return True
|
| 232 |
+
return False
|
| 233 |
+
|
| 234 |
+
@flask_app.after_request
|
| 235 |
+
def _cors(response):
|
| 236 |
+
origin = request.headers.get("Origin")
|
| 237 |
+
if origin and _origin_allowed(origin):
|
| 238 |
+
response.headers["Access-Control-Allow-Origin"] = origin
|
| 239 |
+
response.headers["Access-Control-Allow-Credentials"] = "true"
|
| 240 |
+
response.headers.add(
|
| 241 |
+
"Access-Control-Allow-Headers", "Content-Type, Authorization"
|
| 242 |
+
)
|
| 243 |
+
response.headers.add(
|
| 244 |
+
"Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"
|
| 245 |
+
)
|
| 246 |
+
return response
|
| 247 |
+
|
| 248 |
+
# Handle CORS preflight OPTIONS requests globally
|
| 249 |
+
from flask import Response as _FlaskResponse
|
| 250 |
+
|
| 251 |
+
@flask_app.before_request
|
| 252 |
+
def _handle_options():
|
| 253 |
+
if request.method == "OPTIONS":
|
| 254 |
+
origin = request.headers.get("Origin", "")
|
| 255 |
+
if origin and _origin_allowed(origin):
|
| 256 |
+
resp = _FlaskResponse("", status=204)
|
| 257 |
+
resp.headers["Access-Control-Allow-Origin"] = origin
|
| 258 |
+
resp.headers["Access-Control-Allow-Credentials"] = "true"
|
| 259 |
+
resp.headers["Access-Control-Allow-Headers"] = (
|
| 260 |
+
"Content-Type, Authorization"
|
| 261 |
+
)
|
| 262 |
+
resp.headers["Access-Control-Allow-Methods"] = (
|
| 263 |
+
"GET, POST, PUT, DELETE, OPTIONS"
|
| 264 |
+
)
|
| 265 |
+
resp.headers["Access-Control-Max-Age"] = "86400"
|
| 266 |
+
return resp
|
| 267 |
+
|
| 268 |
+
# 8. Background workers
|
| 269 |
+
from .workers import start_background_workers
|
| 270 |
+
|
| 271 |
+
start_background_workers(kv)
|
| 272 |
+
|
| 273 |
+
return flask_app
|
| 274 |
+
|
| 275 |
+
|
| 276 |
+
def main() -> None:
|
| 277 |
+
flask_app = create_app()
|
| 278 |
+
port = int(os.getenv("III_REST_PORT", os.getenv("PORT", "3111")))
|
| 279 |
+
print(f"[main] Starting Flask daemon on port {port}...")
|
| 280 |
+
flask_app.run(host="0.0.0.0", port=port, debug=False)
|
| 281 |
+
|
| 282 |
+
|
| 283 |
+
if __name__ == "__main__":
|
| 284 |
+
main()
|
src/{auth.md β agentcache/auth.md}
RENAMED
|
File without changes
|
src/{cache β agentcache/cache}/__init__.py
RENAMED
|
@@ -13,24 +13,23 @@ Compatibility shim: also re-exports everything from functions.py that
|
|
| 13 |
callers may import from this package (A2.2).
|
| 14 |
"""
|
| 15 |
|
| 16 |
-
from .observe import (
|
| 17 |
-
folder_observe,
|
| 18 |
-
observe,
|
| 19 |
-
build_synthetic_compression,
|
| 20 |
-
strip_private_data,
|
| 21 |
-
)
|
| 22 |
-
from .remember import remember, forget, jaccard_similarity
|
| 23 |
-
from .context import context, export_data, rebuild_index
|
| 24 |
-
from .graph import folder_graph_build
|
| 25 |
-
from .timeline import folder_timeline, folder_search
|
| 26 |
-
from .health import health_check, auto_forget
|
| 27 |
-
|
| 28 |
# ---------------------------------------------------------------------------
|
| 29 |
# Compatibility shim β delegate additional names to functions.py (A2.2)
|
| 30 |
# Each name is imported lazily via a try/except so missing items don't break
|
| 31 |
# the package import on partially-initialised environments.
|
| 32 |
# ---------------------------------------------------------------------------
|
| 33 |
-
import functions as _fn # noqa: E402
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
|
| 35 |
KV = _fn.KV
|
| 36 |
generate_id = _fn.generate_id
|
|
|
|
| 13 |
callers may import from this package (A2.2).
|
| 14 |
"""
|
| 15 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
# ---------------------------------------------------------------------------
|
| 17 |
# Compatibility shim β delegate additional names to functions.py (A2.2)
|
| 18 |
# Each name is imported lazily via a try/except so missing items don't break
|
| 19 |
# the package import on partially-initialised environments.
|
| 20 |
# ---------------------------------------------------------------------------
|
| 21 |
+
from .. import functions as _fn # noqa: E402
|
| 22 |
+
from .context import context, export_data, rebuild_index
|
| 23 |
+
from .graph import folder_graph_build
|
| 24 |
+
from .health import auto_forget, health_check
|
| 25 |
+
from .observe import (
|
| 26 |
+
build_synthetic_compression,
|
| 27 |
+
folder_observe,
|
| 28 |
+
observe,
|
| 29 |
+
strip_private_data,
|
| 30 |
+
)
|
| 31 |
+
from .remember import forget, jaccard_similarity, remember
|
| 32 |
+
from .timeline import folder_search, folder_timeline
|
| 33 |
|
| 34 |
KV = _fn.KV
|
| 35 |
generate_id = _fn.generate_id
|
src/{cache β agentcache/cache}/context.py
RENAMED
|
@@ -11,8 +11,8 @@ from __future__ import annotations
|
|
| 11 |
|
| 12 |
from typing import Any, Dict
|
| 13 |
|
| 14 |
-
from
|
| 15 |
-
|
| 16 |
|
| 17 |
|
| 18 |
def context(kv: StateKV, data: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
|
| 11 |
|
| 12 |
from typing import Any, Dict
|
| 13 |
|
| 14 |
+
from .. import functions as _fn
|
| 15 |
+
from ..db import StateKV
|
| 16 |
|
| 17 |
|
| 18 |
def context(kv: StateKV, data: Dict[str, Any]) -> Dict[str, Any]:
|
src/{cache β agentcache/cache}/graph.py
RENAMED
|
@@ -9,8 +9,8 @@ from __future__ import annotations
|
|
| 9 |
|
| 10 |
from typing import Any, Dict
|
| 11 |
|
| 12 |
-
from
|
| 13 |
-
|
| 14 |
|
| 15 |
|
| 16 |
def folder_graph_build(kv: StateKV) -> Dict[str, Any]:
|
|
|
|
| 9 |
|
| 10 |
from typing import Any, Dict
|
| 11 |
|
| 12 |
+
from .. import functions as _fn
|
| 13 |
+
from ..db import StateKV
|
| 14 |
|
| 15 |
|
| 16 |
def folder_graph_build(kv: StateKV) -> Dict[str, Any]:
|
src/{cache β agentcache/cache}/health.py
RENAMED
|
@@ -10,8 +10,8 @@ from __future__ import annotations
|
|
| 10 |
|
| 11 |
from typing import Any, Dict
|
| 12 |
|
| 13 |
-
from
|
| 14 |
-
|
| 15 |
|
| 16 |
|
| 17 |
def health_check(kv: StateKV) -> Dict[str, Any]:
|
|
|
|
| 10 |
|
| 11 |
from typing import Any, Dict
|
| 12 |
|
| 13 |
+
from .. import functions as _fn
|
| 14 |
+
from ..db import StateKV
|
| 15 |
|
| 16 |
|
| 17 |
def health_check(kv: StateKV) -> Dict[str, Any]:
|
src/{cache β agentcache/cache}/observe.py
RENAMED
|
@@ -12,9 +12,8 @@ from __future__ import annotations
|
|
| 12 |
|
| 13 |
from typing import Any, Dict
|
| 14 |
|
| 15 |
-
from
|
| 16 |
-
|
| 17 |
-
|
| 18 |
|
| 19 |
# Re-export for backward compatibility
|
| 20 |
strip_private_data = _fn.strip_private_data
|
|
|
|
| 12 |
|
| 13 |
from typing import Any, Dict
|
| 14 |
|
| 15 |
+
from .. import functions as _fn # access module-level globals (_bm25_index, etc.)
|
| 16 |
+
from ..db import StateKV
|
|
|
|
| 17 |
|
| 18 |
# Re-export for backward compatibility
|
| 19 |
strip_private_data = _fn.strip_private_data
|
src/{cache β agentcache/cache}/remember.py
RENAMED
|
@@ -11,8 +11,8 @@ from __future__ import annotations
|
|
| 11 |
|
| 12 |
from typing import Any, Dict
|
| 13 |
|
| 14 |
-
from
|
| 15 |
-
|
| 16 |
|
| 17 |
|
| 18 |
def remember(kv: StateKV, data: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
|
| 11 |
|
| 12 |
from typing import Any, Dict
|
| 13 |
|
| 14 |
+
from .. import functions as _fn
|
| 15 |
+
from ..db import StateKV
|
| 16 |
|
| 17 |
|
| 18 |
def remember(kv: StateKV, data: Dict[str, Any]) -> Dict[str, Any]:
|
src/{cache β agentcache/cache}/timeline.py
RENAMED
|
@@ -10,8 +10,8 @@ from __future__ import annotations
|
|
| 10 |
|
| 11 |
from typing import Any, Dict, List, Optional
|
| 12 |
|
| 13 |
-
from
|
| 14 |
-
|
| 15 |
|
| 16 |
|
| 17 |
def folder_timeline(
|
|
|
|
| 10 |
|
| 11 |
from typing import Any, Dict, List, Optional
|
| 12 |
|
| 13 |
+
from .. import functions as _fn
|
| 14 |
+
from ..db import StateKV
|
| 15 |
|
| 16 |
|
| 17 |
def folder_timeline(
|
src/{cli.py β agentcache/cli.py}
RENAMED
|
@@ -11,14 +11,12 @@ Commands:
|
|
| 11 |
import argparse
|
| 12 |
import json
|
| 13 |
import os
|
| 14 |
-
import sys
|
| 15 |
|
| 16 |
|
| 17 |
def cmd_serve(args) -> None:
|
| 18 |
"""Start the Flask server."""
|
| 19 |
os.environ.setdefault("III_REST_PORT", str(args.port))
|
| 20 |
-
|
| 21 |
-
from app import create_app
|
| 22 |
|
| 23 |
flask_app = create_app()
|
| 24 |
print(f"[cli] Starting agentcache on {args.host}:{args.port}")
|
|
@@ -27,9 +25,8 @@ def cmd_serve(args) -> None:
|
|
| 27 |
|
| 28 |
def cmd_migrate(args) -> None:
|
| 29 |
"""Run session β folder migration."""
|
| 30 |
-
|
| 31 |
-
from
|
| 32 |
-
from functions import migrate_sessions_to_folders
|
| 33 |
|
| 34 |
kv = StateKV()
|
| 35 |
result = migrate_sessions_to_folders(kv, dry_run=args.dry_run)
|
|
@@ -54,9 +51,8 @@ def cmd_migrate(args) -> None:
|
|
| 54 |
|
| 55 |
def cmd_export(args) -> None:
|
| 56 |
"""Export all data as JSON."""
|
| 57 |
-
|
| 58 |
-
from
|
| 59 |
-
from functions import export_data
|
| 60 |
|
| 61 |
kv = StateKV()
|
| 62 |
data = export_data(kv, {})
|
|
@@ -78,8 +74,7 @@ def cmd_export(args) -> None:
|
|
| 78 |
|
| 79 |
def cmd_connect(args) -> None:
|
| 80 |
"""Connect/wire MCP and hooks to client agents."""
|
| 81 |
-
|
| 82 |
-
from connect import run_connect
|
| 83 |
|
| 84 |
run_connect(args)
|
| 85 |
|
|
|
|
| 11 |
import argparse
|
| 12 |
import json
|
| 13 |
import os
|
|
|
|
| 14 |
|
| 15 |
|
| 16 |
def cmd_serve(args) -> None:
|
| 17 |
"""Start the Flask server."""
|
| 18 |
os.environ.setdefault("III_REST_PORT", str(args.port))
|
| 19 |
+
from .app import create_app
|
|
|
|
| 20 |
|
| 21 |
flask_app = create_app()
|
| 22 |
print(f"[cli] Starting agentcache on {args.host}:{args.port}")
|
|
|
|
| 25 |
|
| 26 |
def cmd_migrate(args) -> None:
|
| 27 |
"""Run session β folder migration."""
|
| 28 |
+
from .db import StateKV
|
| 29 |
+
from .functions import migrate_sessions_to_folders
|
|
|
|
| 30 |
|
| 31 |
kv = StateKV()
|
| 32 |
result = migrate_sessions_to_folders(kv, dry_run=args.dry_run)
|
|
|
|
| 51 |
|
| 52 |
def cmd_export(args) -> None:
|
| 53 |
"""Export all data as JSON."""
|
| 54 |
+
from .db import StateKV
|
| 55 |
+
from .functions import export_data
|
|
|
|
| 56 |
|
| 57 |
kv = StateKV()
|
| 58 |
data = export_data(kv, {})
|
|
|
|
| 74 |
|
| 75 |
def cmd_connect(args) -> None:
|
| 76 |
"""Connect/wire MCP and hooks to client agents."""
|
| 77 |
+
from .connect import run_connect
|
|
|
|
| 78 |
|
| 79 |
run_connect(args)
|
| 80 |
|
src/{connect.py β agentcache/connect.py}
RENAMED
|
@@ -1,9 +1,9 @@
|
|
| 1 |
#!/usr/bin/env python3
|
| 2 |
-
import
|
| 3 |
-
import sys
|
| 4 |
import json
|
|
|
|
| 5 |
import shutil
|
| 6 |
-
import
|
| 7 |
|
| 8 |
# Helper functions for connect module
|
| 9 |
|
|
@@ -45,9 +45,9 @@ def backup_file(path, prefix, ext="json"):
|
|
| 45 |
|
| 46 |
|
| 47 |
def get_plugin_root():
|
| 48 |
-
# connect.py resides in src/, plugin/ is in the parent
|
| 49 |
src_dir = os.path.dirname(os.path.abspath(__file__))
|
| 50 |
-
project_root = os.path.dirname(src_dir)
|
| 51 |
plugin_path = os.path.join(project_root, "plugin")
|
| 52 |
if os.path.exists(os.path.join(plugin_path, "scripts")):
|
| 53 |
return plugin_path
|
|
@@ -368,14 +368,7 @@ class AntigravityAdapter:
|
|
| 368 |
os.makedirs(gemini_mcp_dir, exist_ok=True)
|
| 369 |
try:
|
| 370 |
# Dynamic import to avoid circular dependency
|
| 371 |
-
|
| 372 |
-
from routes.mcp import get_mcp_tools_schemas
|
| 373 |
-
except ImportError:
|
| 374 |
-
try:
|
| 375 |
-
from src.routes.mcp import get_mcp_tools_schemas
|
| 376 |
-
except ImportError:
|
| 377 |
-
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
| 378 |
-
from routes.mcp import get_mcp_tools_schemas
|
| 379 |
|
| 380 |
tools = get_mcp_tools_schemas()
|
| 381 |
except Exception as e:
|
|
|
|
| 1 |
#!/usr/bin/env python3
|
| 2 |
+
import argparse
|
|
|
|
| 3 |
import json
|
| 4 |
+
import os
|
| 5 |
import shutil
|
| 6 |
+
import sys
|
| 7 |
|
| 8 |
# Helper functions for connect module
|
| 9 |
|
|
|
|
| 45 |
|
| 46 |
|
| 47 |
def get_plugin_root():
|
| 48 |
+
# connect.py resides in src/agentcache/, plugin/ is in the parent of src/
|
| 49 |
src_dir = os.path.dirname(os.path.abspath(__file__))
|
| 50 |
+
project_root = os.path.dirname(os.path.dirname(src_dir))
|
| 51 |
plugin_path = os.path.join(project_root, "plugin")
|
| 52 |
if os.path.exists(os.path.join(plugin_path, "scripts")):
|
| 53 |
return plugin_path
|
|
|
|
| 368 |
os.makedirs(gemini_mcp_dir, exist_ok=True)
|
| 369 |
try:
|
| 370 |
# Dynamic import to avoid circular dependency
|
| 371 |
+
from .routes.mcp import get_mcp_tools_schemas
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 372 |
|
| 373 |
tools = get_mcp_tools_schemas()
|
| 374 |
except Exception as e:
|
src/{db.py β agentcache/db.py}
RENAMED
|
@@ -1,10 +1,10 @@
|
|
| 1 |
-
import
|
| 2 |
import json
|
|
|
|
| 3 |
import sqlite3
|
| 4 |
import threading
|
| 5 |
import time
|
| 6 |
-
import
|
| 7 |
-
from typing import Dict, Any, List, Optional, TypeVar
|
| 8 |
|
| 9 |
T = TypeVar("T")
|
| 10 |
|
|
|
|
| 1 |
+
import atexit
|
| 2 |
import json
|
| 3 |
+
import os
|
| 4 |
import sqlite3
|
| 5 |
import threading
|
| 6 |
import time
|
| 7 |
+
from typing import Any, Dict, List, Optional, TypeVar
|
|
|
|
| 8 |
|
| 9 |
T = TypeVar("T")
|
| 10 |
|
src/{functions.py β agentcache/functions.py}
RENAMED
|
@@ -1,14 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import os
|
| 2 |
import re
|
|
|
|
| 3 |
import time
|
| 4 |
import uuid
|
| 5 |
-
import
|
| 6 |
-
|
| 7 |
-
import
|
| 8 |
-
import
|
| 9 |
-
from typing import Dict, Any, List, Optional, Tuple, Set
|
| 10 |
-
from db import StateKV
|
| 11 |
-
from search import SearchIndex, VectorIndex, HybridSearch
|
| 12 |
|
| 13 |
# =====================================================================
|
| 14 |
# Global Variables / Module State
|
|
@@ -253,7 +254,9 @@ def auto_complete_old_active_sessions(
|
|
| 253 |
) -> int:
|
| 254 |
sessions = kv.list(KV.sessions)
|
| 255 |
count = 0
|
| 256 |
-
now =
|
|
|
|
|
|
|
| 257 |
for s in sessions:
|
| 258 |
if s.get("id") != current_session_id and s.get("status") == "active":
|
| 259 |
if project and s.get("project") != project:
|
|
@@ -337,7 +340,9 @@ def record_audit(
|
|
| 337 |
) -> Dict[str, Any]:
|
| 338 |
entry = {
|
| 339 |
"id": generate_id("aud"),
|
| 340 |
-
"timestamp": datetime.datetime.
|
|
|
|
|
|
|
| 341 |
"operation": operation,
|
| 342 |
"userId": user_id,
|
| 343 |
"functionId": function_id,
|
|
@@ -1009,7 +1014,9 @@ def observe(kv: StateKV, payload: Dict[str, Any]) -> Dict[str, Any]:
|
|
| 1009 |
{
|
| 1010 |
"type": "set",
|
| 1011 |
"path": "updatedAt",
|
| 1012 |
-
"value": datetime.datetime.
|
|
|
|
|
|
|
| 1013 |
},
|
| 1014 |
{
|
| 1015 |
"type": "set",
|
|
@@ -1035,7 +1042,11 @@ def observe(kv: StateKV, payload: Dict[str, Any]) -> Dict[str, Any]:
|
|
| 1035 |
trimmed_prompt = None
|
| 1036 |
if isinstance(raw.get("userPrompt"), str):
|
| 1037 |
trimmed_prompt = " ".join(raw["userPrompt"].split()).strip()[:200]
|
| 1038 |
-
ts =
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1039 |
new_sess = {
|
| 1040 |
"id": session_id,
|
| 1041 |
"project": project,
|
|
@@ -1600,7 +1611,9 @@ def remember(kv: StateKV, data: Dict[str, Any]) -> Dict[str, Any]:
|
|
| 1600 |
if project:
|
| 1601 |
project = project.strip()
|
| 1602 |
|
| 1603 |
-
now =
|
|
|
|
|
|
|
| 1604 |
existing_memories = kv.list(KV.memories)
|
| 1605 |
superseded_id = None
|
| 1606 |
superseded_version = 1
|
|
@@ -1645,8 +1658,10 @@ def remember(kv: StateKV, data: Dict[str, Any]) -> Dict[str, Any]:
|
|
| 1645 |
new_mem["project"] = project
|
| 1646 |
|
| 1647 |
if ttl_days and isinstance(ttl_days, (int, float)) and ttl_days > 0:
|
| 1648 |
-
forget_time = datetime.datetime.
|
| 1649 |
-
|
|
|
|
|
|
|
| 1650 |
|
| 1651 |
if superseded_memory:
|
| 1652 |
superseded_memory["isLatest"] = False
|
|
@@ -2164,7 +2179,9 @@ DEFAULT_SLOTS = [
|
|
| 2164 |
|
| 2165 |
|
| 2166 |
def seed_defaults(kv: StateKV) -> None:
|
| 2167 |
-
now =
|
|
|
|
|
|
|
| 2168 |
for tmpl in DEFAULT_SLOTS:
|
| 2169 |
scope = tmpl["scope"]
|
| 2170 |
target = KV.globalSlots if scope == "global" else KV.slots
|
|
@@ -2265,7 +2282,9 @@ def slot_create(kv: StateKV, data: Dict[str, Any]) -> Dict[str, Any]:
|
|
| 2265 |
if existing:
|
| 2266 |
return {"success": False, "error": f"slot already exists in {scope} scope"}
|
| 2267 |
|
| 2268 |
-
now =
|
|
|
|
|
|
|
| 2269 |
slot = {
|
| 2270 |
"label": label,
|
| 2271 |
"content": content,
|
|
@@ -2327,7 +2346,9 @@ def slot_append(
|
|
| 2327 |
}
|
| 2328 |
|
| 2329 |
slot["content"] = next_content
|
| 2330 |
-
slot["updatedAt"] =
|
|
|
|
|
|
|
| 2331 |
kv.set(target_kv, label, slot)
|
| 2332 |
|
| 2333 |
safe_audit(
|
|
@@ -2375,7 +2396,9 @@ def slot_replace(
|
|
| 2375 |
|
| 2376 |
before_len = len(slot.get("content") or "")
|
| 2377 |
slot["content"] = content
|
| 2378 |
-
slot["updatedAt"] =
|
|
|
|
|
|
|
| 2379 |
kv.set(target_kv, label, slot)
|
| 2380 |
|
| 2381 |
safe_audit(
|
|
@@ -2455,7 +2478,9 @@ def slot_reflect(kv: StateKV, session_id: str, max_obs: int = 50) -> Dict[str, A
|
|
| 2455 |
files.add(f)
|
| 2456 |
|
| 2457 |
applied = 0
|
| 2458 |
-
now =
|
|
|
|
|
|
|
| 2459 |
|
| 2460 |
if pending_lines:
|
| 2461 |
res = slot_get(kv, "pending_items", project)
|
|
@@ -2558,7 +2583,9 @@ def slot_reflect(kv: StateKV, session_id: str, max_obs: int = 50) -> Dict[str, A
|
|
| 2558 |
|
| 2559 |
|
| 2560 |
def reinforce_lesson(lesson: Dict[str, Any]) -> None:
|
| 2561 |
-
now =
|
|
|
|
|
|
|
| 2562 |
lesson["reinforcements"] = lesson.get("reinforcements", 0) + 1
|
| 2563 |
conf = lesson.get("confidence", 0.5)
|
| 2564 |
lesson["confidence"] = min(1.0, conf + 0.1 * (1 - conf))
|
|
@@ -2595,7 +2622,9 @@ def lesson_save(kv: StateKV, data: Dict[str, Any]) -> Dict[str, Any]:
|
|
| 2595 |
if not isinstance(confidence, (int, float)) or confidence < 0 or confidence > 1:
|
| 2596 |
confidence = 0.5
|
| 2597 |
|
| 2598 |
-
now =
|
|
|
|
|
|
|
| 2599 |
lesson = {
|
| 2600 |
"id": fp,
|
| 2601 |
"content": content.strip(),
|
|
@@ -2676,7 +2705,7 @@ def lesson_recall(kv: StateKV, data: Dict[str, Any]) -> Dict[str, Any]:
|
|
| 2676 |
|
| 2677 |
dt = dateutil.parser.parse(baseline)
|
| 2678 |
days = (
|
| 2679 |
-
datetime.datetime.
|
| 2680 |
- dt.replace(tzinfo=datetime.timezone.utc)
|
| 2681 |
).total_seconds() / (3600 * 24)
|
| 2682 |
recency_boost = 1 / (1 + days * 0.01)
|
|
@@ -2721,8 +2750,8 @@ def lesson_decay_sweep(kv: StateKV) -> Dict[str, Any]:
|
|
| 2721 |
all_lessons = kv.list(KV.lessons)
|
| 2722 |
decayed = 0
|
| 2723 |
soft_deleted = 0
|
| 2724 |
-
now = datetime.datetime.
|
| 2725 |
-
timestamp = now.isoformat()
|
| 2726 |
|
| 2727 |
for les in all_lessons:
|
| 2728 |
if les.get("deleted"):
|
|
@@ -2733,10 +2762,9 @@ def lesson_decay_sweep(kv: StateKV) -> Dict[str, Any]:
|
|
| 2733 |
import dateutil.parser
|
| 2734 |
|
| 2735 |
dt = dateutil.parser.parse(baseline_str)
|
| 2736 |
-
weeks = (
|
| 2737 |
-
|
| 2738 |
-
|
| 2739 |
-
).total_seconds() / (3600 * 24 * 7)
|
| 2740 |
if weeks < 1.0:
|
| 2741 |
continue
|
| 2742 |
|
|
@@ -2911,7 +2939,9 @@ def create_session(kv: StateKV, session: Dict[str, Any]) -> Dict[str, Any]:
|
|
| 2911 |
|
| 2912 |
|
| 2913 |
def end_session(kv: StateKV, session_id: str) -> bool:
|
| 2914 |
-
now =
|
|
|
|
|
|
|
| 2915 |
kv.update(
|
| 2916 |
KV.sessions,
|
| 2917 |
session_id,
|
|
@@ -2972,7 +3002,9 @@ def get_project_profile(kv: StateKV, project: str) -> Dict[str, Any]:
|
|
| 2972 |
"topFiles": [],
|
| 2973 |
"conventions": [],
|
| 2974 |
"commonErrors": [],
|
| 2975 |
-
"updatedAt": datetime.datetime.
|
|
|
|
|
|
|
| 2976 |
}
|
| 2977 |
if not prof.get("topConcepts") and not prof.get("topFiles"):
|
| 2978 |
prof = build_project_profile(kv, project)
|
|
@@ -2988,14 +3020,16 @@ def build_project_profile(kv: StateKV, project: str) -> Dict[str, Any]:
|
|
| 2988 |
"topFiles": [],
|
| 2989 |
"conventions": [],
|
| 2990 |
"commonErrors": [],
|
| 2991 |
-
"updatedAt": datetime.datetime.
|
|
|
|
|
|
|
| 2992 |
}
|
| 2993 |
|
| 2994 |
# Stored profile may lack topConcepts/topFiles β compute from observations + memories if empty
|
| 2995 |
if not prof.get("topConcepts") and not prof.get("topFiles"):
|
| 2996 |
-
import re as _re
|
| 2997 |
import json as _j
|
| 2998 |
import os.path as _osp
|
|
|
|
| 2999 |
from collections import Counter
|
| 3000 |
|
| 3001 |
sessions = kv.list(KV.sessions)
|
|
@@ -3087,7 +3121,9 @@ def export_data(kv: StateKV, data: Optional[Dict[str, Any]] = None) -> Dict[str,
|
|
| 3087 |
if data is None:
|
| 3088 |
data = {}
|
| 3089 |
|
| 3090 |
-
exported_at =
|
|
|
|
|
|
|
| 3091 |
|
| 3092 |
# ---- v2 folder-based export (primary path) ----
|
| 3093 |
folder_pairs = kv.list(KV.folders)
|
|
@@ -3226,7 +3262,9 @@ def migrate_sessions_to_folders(kv: StateKV, dry_run: bool = False) -> Dict[str,
|
|
| 3226 |
def set_project_profile(
|
| 3227 |
kv: StateKV, project: str, profile: Dict[str, Any]
|
| 3228 |
) -> Dict[str, Any]:
|
| 3229 |
-
profile["updatedAt"] =
|
|
|
|
|
|
|
| 3230 |
kv.set(KV.profiles, project, profile)
|
| 3231 |
|
| 3232 |
# Commit to Dolt
|
|
@@ -3245,7 +3283,9 @@ def add_relation(kv: StateKV, data: Dict[str, Any]) -> Dict[str, Any]:
|
|
| 3245 |
"sourceId": data["sourceId"],
|
| 3246 |
"targetId": data["targetId"],
|
| 3247 |
"type": data["type"],
|
| 3248 |
-
"createdAt": datetime.datetime.
|
|
|
|
|
|
|
| 3249 |
}
|
| 3250 |
kv.set(KV.relations, rel["id"], rel)
|
| 3251 |
|
|
@@ -3273,7 +3313,9 @@ def evolve_memory(kv: StateKV, data: Dict[str, Any]) -> Dict[str, Any]:
|
|
| 3273 |
existing["isLatest"] = False
|
| 3274 |
kv.set(KV.memories, existing["id"], existing)
|
| 3275 |
|
| 3276 |
-
now =
|
|
|
|
|
|
|
| 3277 |
new_mem = dict(existing)
|
| 3278 |
new_mem["id"] = generate_id("mem")
|
| 3279 |
new_mem["content"] = new_content
|
|
@@ -3319,8 +3361,7 @@ def evolve_memory(kv: StateKV, data: Dict[str, Any]) -> Dict[str, Any]:
|
|
| 3319 |
|
| 3320 |
|
| 3321 |
def auto_forget(kv: StateKV, dry_run: bool = False) -> Dict[str, Any]:
|
| 3322 |
-
now_dt = datetime.datetime.
|
| 3323 |
-
now_dt.isoformat() + "Z"
|
| 3324 |
evicted_memories = []
|
| 3325 |
evicted_observations = []
|
| 3326 |
|
|
@@ -3601,7 +3642,7 @@ def summarize(kv: StateKV, data: Dict[str, Any]) -> Dict[str, Any]:
|
|
| 3601 |
return {"success": False, "error": "no_observations"}
|
| 3602 |
|
| 3603 |
SUMMARY_SYSTEM = """You are a session summarization assistant. Your job is to read all raw tool executions and outcomes from a coding session and produce a high-fidelity summary.
|
| 3604 |
-
|
| 3605 |
Output XML:
|
| 3606 |
<summary>
|
| 3607 |
<title>Concise title summarizing the session</title>
|
|
@@ -3658,7 +3699,9 @@ def summarize(kv: StateKV, data: Dict[str, Any]) -> Dict[str, Any]:
|
|
| 3658 |
final_summary = {
|
| 3659 |
"sessionId": session_id,
|
| 3660 |
"project": session.get("project"),
|
| 3661 |
-
"createdAt": datetime.datetime.
|
|
|
|
|
|
|
| 3662 |
"title": partial_summaries[0]["title"],
|
| 3663 |
"narrative": partial_summaries[0]["narrative"],
|
| 3664 |
"keyDecisions": partial_summaries[0]["keyDecisions"],
|
|
@@ -3668,7 +3711,7 @@ def summarize(kv: StateKV, data: Dict[str, Any]) -> Dict[str, Any]:
|
|
| 3668 |
}
|
| 3669 |
else:
|
| 3670 |
REDUCE_SYSTEM = """You are a session summarization reducer. Reduce multiple partial chunk summaries into a single final summary.
|
| 3671 |
-
|
| 3672 |
Output XML:
|
| 3673 |
<summary>
|
| 3674 |
<title>Concise final title summarizing the entire session</title>
|
|
@@ -3694,7 +3737,9 @@ def summarize(kv: StateKV, data: Dict[str, Any]) -> Dict[str, Any]:
|
|
| 3694 |
final_summary = {
|
| 3695 |
"sessionId": session_id,
|
| 3696 |
"project": session.get("project"),
|
| 3697 |
-
"createdAt": datetime.datetime.
|
|
|
|
|
|
|
| 3698 |
"title": get_xml_tag(cleaned, "title") or partial_summaries[0]["title"],
|
| 3699 |
"narrative": get_xml_tag(cleaned, "narrative") or "",
|
| 3700 |
"keyDecisions": get_xml_children(cleaned, "decisions", "decision"),
|
|
@@ -3772,7 +3817,7 @@ def consolidate(
|
|
| 3772 |
|
| 3773 |
# Prompt templates
|
| 3774 |
CONSOLIDATION_SYSTEM = """You are a memory consolidation engine. Given a set of related observations from coding sessions, synthesize them into a single long-term memory.
|
| 3775 |
-
|
| 3776 |
Output XML:
|
| 3777 |
<memory>
|
| 3778 |
<type>pattern|preference|architecture|bug|workflow|fact</type>
|
|
@@ -3829,7 +3874,11 @@ def consolidate(
|
|
| 3829 |
concepts_list = get_xml_children(cleaned, "concepts", "concept")
|
| 3830 |
files_list = get_xml_children(cleaned, "files", "file")
|
| 3831 |
|
| 3832 |
-
now =
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3833 |
|
| 3834 |
# Find existing memory with same title
|
| 3835 |
existing_match = None
|
|
@@ -3934,12 +3983,12 @@ def consolidate(
|
|
| 3934 |
)[:20]
|
| 3935 |
|
| 3936 |
SEMANTIC_MERGE_SYSTEM = """You are a memory consolidation engine. Given overlapping episodic memories (session summaries), extract stable factual knowledge.
|
| 3937 |
-
|
| 3938 |
Output format (XML):
|
| 3939 |
<facts>
|
| 3940 |
<fact confidence="0.0-1.0">Concise factual statement</fact>
|
| 3941 |
</facts>
|
| 3942 |
-
|
| 3943 |
Rules:
|
| 3944 |
- Extract only facts that appear in 2+ episodes or are highly confident
|
| 3945 |
- Confidence reflects how well-supported the fact is across episodes
|
|
@@ -3963,7 +4012,11 @@ def consolidate(
|
|
| 3963 |
)
|
| 3964 |
|
| 3965 |
existing_semantic = kv.list(KV.semantic)
|
| 3966 |
-
now =
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3967 |
|
| 3968 |
for conf_str, fact_text in fact_matches:
|
| 3969 |
fact_text = fact_text.strip()
|
|
@@ -4018,7 +4071,7 @@ def consolidate(
|
|
| 4018 |
|
| 4019 |
if len(patterns) >= 2:
|
| 4020 |
PROCEDURAL_EXTRACTION_SYSTEM = """You are a procedural memory extractor. Given repeated patterns and workflows observed across sessions, extract reusable procedures.
|
| 4021 |
-
|
| 4022 |
Output format (XML):
|
| 4023 |
<procedures>
|
| 4024 |
<procedure name="short descriptive name" trigger="when to use this procedure">
|
|
@@ -4026,7 +4079,7 @@ def consolidate(
|
|
| 4026 |
<step>Step 2 description</step>
|
| 4027 |
</procedure>
|
| 4028 |
</procedures>
|
| 4029 |
-
|
| 4030 |
Rules:
|
| 4031 |
- Only extract procedures observed 2+ times
|
| 4032 |
- Steps should be concrete and actionable
|
|
@@ -4051,7 +4104,11 @@ def consolidate(
|
|
| 4051 |
)
|
| 4052 |
|
| 4053 |
existing_procs = kv.list(KV.procedural)
|
| 4054 |
-
now =
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4055 |
|
| 4056 |
for name, trigger, steps_block in proc_matches:
|
| 4057 |
steps = [
|
|
|
|
| 1 |
+
import datetime
|
| 2 |
+
import hashlib
|
| 3 |
+
import json
|
| 4 |
import os
|
| 5 |
import re
|
| 6 |
+
import threading
|
| 7 |
import time
|
| 8 |
import uuid
|
| 9 |
+
from typing import Any, Dict, List, Optional, Set, Tuple
|
| 10 |
+
|
| 11 |
+
from .db import StateKV
|
| 12 |
+
from .search import HybridSearch, SearchIndex, VectorIndex
|
|
|
|
|
|
|
|
|
|
| 13 |
|
| 14 |
# =====================================================================
|
| 15 |
# Global Variables / Module State
|
|
|
|
| 254 |
) -> int:
|
| 255 |
sessions = kv.list(KV.sessions)
|
| 256 |
count = 0
|
| 257 |
+
now = (
|
| 258 |
+
datetime.datetime.now(datetime.timezone.utc).isoformat().replace("+00:00", "Z")
|
| 259 |
+
)
|
| 260 |
for s in sessions:
|
| 261 |
if s.get("id") != current_session_id and s.get("status") == "active":
|
| 262 |
if project and s.get("project") != project:
|
|
|
|
| 340 |
) -> Dict[str, Any]:
|
| 341 |
entry = {
|
| 342 |
"id": generate_id("aud"),
|
| 343 |
+
"timestamp": datetime.datetime.now(datetime.timezone.utc)
|
| 344 |
+
.isoformat()
|
| 345 |
+
.replace("+00:00", "Z"),
|
| 346 |
"operation": operation,
|
| 347 |
"userId": user_id,
|
| 348 |
"functionId": function_id,
|
|
|
|
| 1014 |
{
|
| 1015 |
"type": "set",
|
| 1016 |
"path": "updatedAt",
|
| 1017 |
+
"value": datetime.datetime.now(datetime.timezone.utc)
|
| 1018 |
+
.isoformat()
|
| 1019 |
+
.replace("+00:00", "Z"),
|
| 1020 |
},
|
| 1021 |
{
|
| 1022 |
"type": "set",
|
|
|
|
| 1042 |
trimmed_prompt = None
|
| 1043 |
if isinstance(raw.get("userPrompt"), str):
|
| 1044 |
trimmed_prompt = " ".join(raw["userPrompt"].split()).strip()[:200]
|
| 1045 |
+
ts = (
|
| 1046 |
+
datetime.datetime.now(datetime.timezone.utc)
|
| 1047 |
+
.isoformat()
|
| 1048 |
+
.replace("+00:00", "Z")
|
| 1049 |
+
)
|
| 1050 |
new_sess = {
|
| 1051 |
"id": session_id,
|
| 1052 |
"project": project,
|
|
|
|
| 1611 |
if project:
|
| 1612 |
project = project.strip()
|
| 1613 |
|
| 1614 |
+
now = (
|
| 1615 |
+
datetime.datetime.now(datetime.timezone.utc).isoformat().replace("+00:00", "Z")
|
| 1616 |
+
)
|
| 1617 |
existing_memories = kv.list(KV.memories)
|
| 1618 |
superseded_id = None
|
| 1619 |
superseded_version = 1
|
|
|
|
| 1658 |
new_mem["project"] = project
|
| 1659 |
|
| 1660 |
if ttl_days and isinstance(ttl_days, (int, float)) and ttl_days > 0:
|
| 1661 |
+
forget_time = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(
|
| 1662 |
+
days=ttl_days
|
| 1663 |
+
)
|
| 1664 |
+
new_mem["forgetAfter"] = forget_time.isoformat().replace("+00:00", "Z")
|
| 1665 |
|
| 1666 |
if superseded_memory:
|
| 1667 |
superseded_memory["isLatest"] = False
|
|
|
|
| 2179 |
|
| 2180 |
|
| 2181 |
def seed_defaults(kv: StateKV) -> None:
|
| 2182 |
+
now = (
|
| 2183 |
+
datetime.datetime.now(datetime.timezone.utc).isoformat().replace("+00:00", "Z")
|
| 2184 |
+
)
|
| 2185 |
for tmpl in DEFAULT_SLOTS:
|
| 2186 |
scope = tmpl["scope"]
|
| 2187 |
target = KV.globalSlots if scope == "global" else KV.slots
|
|
|
|
| 2282 |
if existing:
|
| 2283 |
return {"success": False, "error": f"slot already exists in {scope} scope"}
|
| 2284 |
|
| 2285 |
+
now = (
|
| 2286 |
+
datetime.datetime.now(datetime.timezone.utc).isoformat().replace("+00:00", "Z")
|
| 2287 |
+
)
|
| 2288 |
slot = {
|
| 2289 |
"label": label,
|
| 2290 |
"content": content,
|
|
|
|
| 2346 |
}
|
| 2347 |
|
| 2348 |
slot["content"] = next_content
|
| 2349 |
+
slot["updatedAt"] = (
|
| 2350 |
+
datetime.datetime.now(datetime.timezone.utc).isoformat().replace("+00:00", "Z")
|
| 2351 |
+
)
|
| 2352 |
kv.set(target_kv, label, slot)
|
| 2353 |
|
| 2354 |
safe_audit(
|
|
|
|
| 2396 |
|
| 2397 |
before_len = len(slot.get("content") or "")
|
| 2398 |
slot["content"] = content
|
| 2399 |
+
slot["updatedAt"] = (
|
| 2400 |
+
datetime.datetime.now(datetime.timezone.utc).isoformat().replace("+00:00", "Z")
|
| 2401 |
+
)
|
| 2402 |
kv.set(target_kv, label, slot)
|
| 2403 |
|
| 2404 |
safe_audit(
|
|
|
|
| 2478 |
files.add(f)
|
| 2479 |
|
| 2480 |
applied = 0
|
| 2481 |
+
now = (
|
| 2482 |
+
datetime.datetime.now(datetime.timezone.utc).isoformat().replace("+00:00", "Z")
|
| 2483 |
+
)
|
| 2484 |
|
| 2485 |
if pending_lines:
|
| 2486 |
res = slot_get(kv, "pending_items", project)
|
|
|
|
| 2583 |
|
| 2584 |
|
| 2585 |
def reinforce_lesson(lesson: Dict[str, Any]) -> None:
|
| 2586 |
+
now = (
|
| 2587 |
+
datetime.datetime.now(datetime.timezone.utc).isoformat().replace("+00:00", "Z")
|
| 2588 |
+
)
|
| 2589 |
lesson["reinforcements"] = lesson.get("reinforcements", 0) + 1
|
| 2590 |
conf = lesson.get("confidence", 0.5)
|
| 2591 |
lesson["confidence"] = min(1.0, conf + 0.1 * (1 - conf))
|
|
|
|
| 2622 |
if not isinstance(confidence, (int, float)) or confidence < 0 or confidence > 1:
|
| 2623 |
confidence = 0.5
|
| 2624 |
|
| 2625 |
+
now = (
|
| 2626 |
+
datetime.datetime.now(datetime.timezone.utc).isoformat().replace("+00:00", "Z")
|
| 2627 |
+
)
|
| 2628 |
lesson = {
|
| 2629 |
"id": fp,
|
| 2630 |
"content": content.strip(),
|
|
|
|
| 2705 |
|
| 2706 |
dt = dateutil.parser.parse(baseline)
|
| 2707 |
days = (
|
| 2708 |
+
datetime.datetime.now(datetime.timezone.utc)
|
| 2709 |
- dt.replace(tzinfo=datetime.timezone.utc)
|
| 2710 |
).total_seconds() / (3600 * 24)
|
| 2711 |
recency_boost = 1 / (1 + days * 0.01)
|
|
|
|
| 2750 |
all_lessons = kv.list(KV.lessons)
|
| 2751 |
decayed = 0
|
| 2752 |
soft_deleted = 0
|
| 2753 |
+
now = datetime.datetime.now(datetime.timezone.utc)
|
| 2754 |
+
timestamp = now.isoformat().replace("+00:00", "Z")
|
| 2755 |
|
| 2756 |
for les in all_lessons:
|
| 2757 |
if les.get("deleted"):
|
|
|
|
| 2762 |
import dateutil.parser
|
| 2763 |
|
| 2764 |
dt = dateutil.parser.parse(baseline_str)
|
| 2765 |
+
weeks = (now - dt.replace(tzinfo=datetime.timezone.utc)).total_seconds() / (
|
| 2766 |
+
3600 * 24 * 7
|
| 2767 |
+
)
|
|
|
|
| 2768 |
if weeks < 1.0:
|
| 2769 |
continue
|
| 2770 |
|
|
|
|
| 2939 |
|
| 2940 |
|
| 2941 |
def end_session(kv: StateKV, session_id: str) -> bool:
|
| 2942 |
+
now = (
|
| 2943 |
+
datetime.datetime.now(datetime.timezone.utc).isoformat().replace("+00:00", "Z")
|
| 2944 |
+
)
|
| 2945 |
kv.update(
|
| 2946 |
KV.sessions,
|
| 2947 |
session_id,
|
|
|
|
| 3002 |
"topFiles": [],
|
| 3003 |
"conventions": [],
|
| 3004 |
"commonErrors": [],
|
| 3005 |
+
"updatedAt": datetime.datetime.now(datetime.timezone.utc)
|
| 3006 |
+
.isoformat()
|
| 3007 |
+
.replace("+00:00", "Z"),
|
| 3008 |
}
|
| 3009 |
if not prof.get("topConcepts") and not prof.get("topFiles"):
|
| 3010 |
prof = build_project_profile(kv, project)
|
|
|
|
| 3020 |
"topFiles": [],
|
| 3021 |
"conventions": [],
|
| 3022 |
"commonErrors": [],
|
| 3023 |
+
"updatedAt": datetime.datetime.now(datetime.timezone.utc)
|
| 3024 |
+
.isoformat()
|
| 3025 |
+
.replace("+00:00", "Z"),
|
| 3026 |
}
|
| 3027 |
|
| 3028 |
# Stored profile may lack topConcepts/topFiles β compute from observations + memories if empty
|
| 3029 |
if not prof.get("topConcepts") and not prof.get("topFiles"):
|
|
|
|
| 3030 |
import json as _j
|
| 3031 |
import os.path as _osp
|
| 3032 |
+
import re as _re
|
| 3033 |
from collections import Counter
|
| 3034 |
|
| 3035 |
sessions = kv.list(KV.sessions)
|
|
|
|
| 3121 |
if data is None:
|
| 3122 |
data = {}
|
| 3123 |
|
| 3124 |
+
exported_at = (
|
| 3125 |
+
datetime.datetime.now(datetime.timezone.utc).isoformat().replace("+00:00", "Z")
|
| 3126 |
+
)
|
| 3127 |
|
| 3128 |
# ---- v2 folder-based export (primary path) ----
|
| 3129 |
folder_pairs = kv.list(KV.folders)
|
|
|
|
| 3262 |
def set_project_profile(
|
| 3263 |
kv: StateKV, project: str, profile: Dict[str, Any]
|
| 3264 |
) -> Dict[str, Any]:
|
| 3265 |
+
profile["updatedAt"] = (
|
| 3266 |
+
datetime.datetime.now(datetime.timezone.utc).isoformat().replace("+00:00", "Z")
|
| 3267 |
+
)
|
| 3268 |
kv.set(KV.profiles, project, profile)
|
| 3269 |
|
| 3270 |
# Commit to Dolt
|
|
|
|
| 3283 |
"sourceId": data["sourceId"],
|
| 3284 |
"targetId": data["targetId"],
|
| 3285 |
"type": data["type"],
|
| 3286 |
+
"createdAt": datetime.datetime.now(datetime.timezone.utc)
|
| 3287 |
+
.isoformat()
|
| 3288 |
+
.replace("+00:00", "Z"),
|
| 3289 |
}
|
| 3290 |
kv.set(KV.relations, rel["id"], rel)
|
| 3291 |
|
|
|
|
| 3313 |
existing["isLatest"] = False
|
| 3314 |
kv.set(KV.memories, existing["id"], existing)
|
| 3315 |
|
| 3316 |
+
now = (
|
| 3317 |
+
datetime.datetime.now(datetime.timezone.utc).isoformat().replace("+00:00", "Z")
|
| 3318 |
+
)
|
| 3319 |
new_mem = dict(existing)
|
| 3320 |
new_mem["id"] = generate_id("mem")
|
| 3321 |
new_mem["content"] = new_content
|
|
|
|
| 3361 |
|
| 3362 |
|
| 3363 |
def auto_forget(kv: StateKV, dry_run: bool = False) -> Dict[str, Any]:
|
| 3364 |
+
now_dt = datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None)
|
|
|
|
| 3365 |
evicted_memories = []
|
| 3366 |
evicted_observations = []
|
| 3367 |
|
|
|
|
| 3642 |
return {"success": False, "error": "no_observations"}
|
| 3643 |
|
| 3644 |
SUMMARY_SYSTEM = """You are a session summarization assistant. Your job is to read all raw tool executions and outcomes from a coding session and produce a high-fidelity summary.
|
| 3645 |
+
|
| 3646 |
Output XML:
|
| 3647 |
<summary>
|
| 3648 |
<title>Concise title summarizing the session</title>
|
|
|
|
| 3699 |
final_summary = {
|
| 3700 |
"sessionId": session_id,
|
| 3701 |
"project": session.get("project"),
|
| 3702 |
+
"createdAt": datetime.datetime.now(datetime.timezone.utc)
|
| 3703 |
+
.isoformat()
|
| 3704 |
+
.replace("+00:00", "Z"),
|
| 3705 |
"title": partial_summaries[0]["title"],
|
| 3706 |
"narrative": partial_summaries[0]["narrative"],
|
| 3707 |
"keyDecisions": partial_summaries[0]["keyDecisions"],
|
|
|
|
| 3711 |
}
|
| 3712 |
else:
|
| 3713 |
REDUCE_SYSTEM = """You are a session summarization reducer. Reduce multiple partial chunk summaries into a single final summary.
|
| 3714 |
+
|
| 3715 |
Output XML:
|
| 3716 |
<summary>
|
| 3717 |
<title>Concise final title summarizing the entire session</title>
|
|
|
|
| 3737 |
final_summary = {
|
| 3738 |
"sessionId": session_id,
|
| 3739 |
"project": session.get("project"),
|
| 3740 |
+
"createdAt": datetime.datetime.now(datetime.timezone.utc)
|
| 3741 |
+
.isoformat()
|
| 3742 |
+
.replace("+00:00", "Z"),
|
| 3743 |
"title": get_xml_tag(cleaned, "title") or partial_summaries[0]["title"],
|
| 3744 |
"narrative": get_xml_tag(cleaned, "narrative") or "",
|
| 3745 |
"keyDecisions": get_xml_children(cleaned, "decisions", "decision"),
|
|
|
|
| 3817 |
|
| 3818 |
# Prompt templates
|
| 3819 |
CONSOLIDATION_SYSTEM = """You are a memory consolidation engine. Given a set of related observations from coding sessions, synthesize them into a single long-term memory.
|
| 3820 |
+
|
| 3821 |
Output XML:
|
| 3822 |
<memory>
|
| 3823 |
<type>pattern|preference|architecture|bug|workflow|fact</type>
|
|
|
|
| 3874 |
concepts_list = get_xml_children(cleaned, "concepts", "concept")
|
| 3875 |
files_list = get_xml_children(cleaned, "files", "file")
|
| 3876 |
|
| 3877 |
+
now = (
|
| 3878 |
+
datetime.datetime.now(datetime.timezone.utc)
|
| 3879 |
+
.isoformat()
|
| 3880 |
+
.replace("+00:00", "Z")
|
| 3881 |
+
)
|
| 3882 |
|
| 3883 |
# Find existing memory with same title
|
| 3884 |
existing_match = None
|
|
|
|
| 3983 |
)[:20]
|
| 3984 |
|
| 3985 |
SEMANTIC_MERGE_SYSTEM = """You are a memory consolidation engine. Given overlapping episodic memories (session summaries), extract stable factual knowledge.
|
| 3986 |
+
|
| 3987 |
Output format (XML):
|
| 3988 |
<facts>
|
| 3989 |
<fact confidence="0.0-1.0">Concise factual statement</fact>
|
| 3990 |
</facts>
|
| 3991 |
+
|
| 3992 |
Rules:
|
| 3993 |
- Extract only facts that appear in 2+ episodes or are highly confident
|
| 3994 |
- Confidence reflects how well-supported the fact is across episodes
|
|
|
|
| 4012 |
)
|
| 4013 |
|
| 4014 |
existing_semantic = kv.list(KV.semantic)
|
| 4015 |
+
now = (
|
| 4016 |
+
datetime.datetime.now(datetime.timezone.utc)
|
| 4017 |
+
.isoformat()
|
| 4018 |
+
.replace("+00:00", "Z")
|
| 4019 |
+
)
|
| 4020 |
|
| 4021 |
for conf_str, fact_text in fact_matches:
|
| 4022 |
fact_text = fact_text.strip()
|
|
|
|
| 4071 |
|
| 4072 |
if len(patterns) >= 2:
|
| 4073 |
PROCEDURAL_EXTRACTION_SYSTEM = """You are a procedural memory extractor. Given repeated patterns and workflows observed across sessions, extract reusable procedures.
|
| 4074 |
+
|
| 4075 |
Output format (XML):
|
| 4076 |
<procedures>
|
| 4077 |
<procedure name="short descriptive name" trigger="when to use this procedure">
|
|
|
|
| 4079 |
<step>Step 2 description</step>
|
| 4080 |
</procedure>
|
| 4081 |
</procedures>
|
| 4082 |
+
|
| 4083 |
Rules:
|
| 4084 |
- Only extract procedures observed 2+ times
|
| 4085 |
- Steps should be concrete and actionable
|
|
|
|
| 4104 |
)
|
| 4105 |
|
| 4106 |
existing_procs = kv.list(KV.procedural)
|
| 4107 |
+
now = (
|
| 4108 |
+
datetime.datetime.now(datetime.timezone.utc)
|
| 4109 |
+
.isoformat()
|
| 4110 |
+
.replace("+00:00", "Z")
|
| 4111 |
+
)
|
| 4112 |
|
| 4113 |
for name, trigger, steps_block in proc_matches:
|
| 4114 |
steps = [
|
src/{import_data.py β agentcache/import_data.py}
RENAMED
|
@@ -1,11 +1,9 @@
|
|
| 1 |
-
import os
|
| 2 |
import json
|
| 3 |
-
import
|
| 4 |
import sys
|
|
|
|
| 5 |
|
| 6 |
-
|
| 7 |
-
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
| 8 |
-
from db import StateKV
|
| 9 |
|
| 10 |
|
| 11 |
def import_old_data(old_db_path: str, kv: StateKV) -> bool:
|
|
|
|
|
|
|
| 1 |
import json
|
| 2 |
+
import os
|
| 3 |
import sys
|
| 4 |
+
import urllib.parse
|
| 5 |
|
| 6 |
+
from .db import StateKV
|
|
|
|
|
|
|
| 7 |
|
| 8 |
|
| 9 |
def import_old_data(old_db_path: str, kv: StateKV) -> bool:
|
src/{mcp_stdio.py β agentcache/mcp_stdio.py}
RENAMED
|
@@ -9,9 +9,10 @@ Antigravity sync functions have been moved to examples/antigravity_sync.py (D1.3
|
|
| 9 |
All tool calls are proxied to the HTTP API.
|
| 10 |
"""
|
| 11 |
|
| 12 |
-
import sys
|
| 13 |
import json
|
| 14 |
import os
|
|
|
|
|
|
|
| 15 |
import requests
|
| 16 |
|
| 17 |
BASE = (
|
|
|
|
| 9 |
All tool calls are proxied to the HTTP API.
|
| 10 |
"""
|
| 11 |
|
|
|
|
| 12 |
import json
|
| 13 |
import os
|
| 14 |
+
import sys
|
| 15 |
+
|
| 16 |
import requests
|
| 17 |
|
| 18 |
BASE = (
|
src/{replay_import.py β agentcache/replay_import.py}
RENAMED
|
@@ -1,9 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import os
|
| 2 |
import re
|
| 3 |
-
import
|
| 4 |
-
import hashlib
|
| 5 |
-
import datetime
|
| 6 |
-
from typing import List, Dict, Any, Tuple
|
| 7 |
|
| 8 |
# Constants
|
| 9 |
MAX_FILES_DEFAULT = 200
|
|
@@ -124,7 +124,9 @@ def parse_jsonl_text(text: str, fallback_session_id: str = None) -> Dict[str, An
|
|
| 124 |
if entry.get("cwd") and not cwd:
|
| 125 |
cwd = entry["cwd"]
|
| 126 |
|
| 127 |
-
ts = entry.get("timestamp") or datetime.datetime.
|
|
|
|
|
|
|
| 128 |
if not first_ts:
|
| 129 |
first_ts = ts
|
| 130 |
last_ts = ts
|
|
@@ -196,7 +198,9 @@ def parse_jsonl_text(text: str, fallback_session_id: str = None) -> Dict[str, An
|
|
| 196 |
if obs["sessionId"] == "imported":
|
| 197 |
obs["sessionId"] = effective_session_id
|
| 198 |
|
| 199 |
-
now_iso =
|
|
|
|
|
|
|
| 200 |
return {
|
| 201 |
"sessionId": effective_session_id,
|
| 202 |
"project": derive_project(cwd),
|
|
@@ -215,11 +219,13 @@ def derive_crystal_and_lessons(
|
|
| 215 |
compressed: List[Dict[str, Any]],
|
| 216 |
first_prompt: str = None,
|
| 217 |
) -> None:
|
| 218 |
-
from functions import KV
|
| 219 |
|
| 220 |
if not raw_obs:
|
| 221 |
return
|
| 222 |
-
created_at =
|
|
|
|
|
|
|
| 223 |
|
| 224 |
files = set()
|
| 225 |
tools = set()
|
|
@@ -362,7 +368,7 @@ def find_jsonl_files(root: str, limit=200) -> Tuple[List[str], bool, int, bool]:
|
|
| 362 |
|
| 363 |
|
| 364 |
def import_jsonl_data(kv, path: str = None, max_files: int = None) -> Dict[str, Any]:
|
| 365 |
-
from functions import KV,
|
| 366 |
|
| 367 |
default_root = os.path.expanduser(os.path.join("~", ".claude", "projects"))
|
| 368 |
raw_path = path or default_root
|
|
@@ -475,7 +481,7 @@ def import_jsonl_data(kv, path: str = None, max_files: int = None) -> Dict[str,
|
|
| 475 |
}
|
| 476 |
kv.set(KV.sessions, session["id"], session)
|
| 477 |
|
| 478 |
-
from functions import vector_index_add_guarded
|
| 479 |
|
| 480 |
compressed = []
|
| 481 |
for obs in parsed["observations"]:
|
|
@@ -506,7 +512,7 @@ def import_jsonl_data(kv, path: str = None, max_files: int = None) -> Dict[str,
|
|
| 506 |
)
|
| 507 |
|
| 508 |
# Save the updated persistence state
|
| 509 |
-
import functions
|
| 510 |
|
| 511 |
if functions._index_persistence:
|
| 512 |
try:
|
|
@@ -516,7 +522,7 @@ def import_jsonl_data(kv, path: str = None, max_files: int = None) -> Dict[str,
|
|
| 516 |
|
| 517 |
# Audit trail
|
| 518 |
try:
|
| 519 |
-
from functions import log_audit
|
| 520 |
|
| 521 |
log_audit(
|
| 522 |
kv,
|
|
@@ -613,7 +619,11 @@ def estimate_duration_ms(event: Dict[str, Any]) -> int:
|
|
| 613 |
|
| 614 |
def project_timeline(observations: List[Dict[str, Any]]) -> Dict[str, Any]:
|
| 615 |
if not observations:
|
| 616 |
-
now =
|
|
|
|
|
|
|
|
|
|
|
|
|
| 617 |
return {
|
| 618 |
"sessionId": "",
|
| 619 |
"startedAt": now,
|
|
|
|
| 1 |
+
import datetime
|
| 2 |
+
import hashlib
|
| 3 |
+
import json
|
| 4 |
import os
|
| 5 |
import re
|
| 6 |
+
from typing import Any, Dict, List, Tuple
|
|
|
|
|
|
|
|
|
|
| 7 |
|
| 8 |
# Constants
|
| 9 |
MAX_FILES_DEFAULT = 200
|
|
|
|
| 124 |
if entry.get("cwd") and not cwd:
|
| 125 |
cwd = entry["cwd"]
|
| 126 |
|
| 127 |
+
ts = entry.get("timestamp") or datetime.datetime.now(
|
| 128 |
+
datetime.timezone.utc
|
| 129 |
+
).isoformat().replace("+00:00", "Z")
|
| 130 |
if not first_ts:
|
| 131 |
first_ts = ts
|
| 132 |
last_ts = ts
|
|
|
|
| 198 |
if obs["sessionId"] == "imported":
|
| 199 |
obs["sessionId"] = effective_session_id
|
| 200 |
|
| 201 |
+
now_iso = (
|
| 202 |
+
datetime.datetime.now(datetime.timezone.utc).isoformat().replace("+00:00", "Z")
|
| 203 |
+
)
|
| 204 |
return {
|
| 205 |
"sessionId": effective_session_id,
|
| 206 |
"project": derive_project(cwd),
|
|
|
|
| 219 |
compressed: List[Dict[str, Any]],
|
| 220 |
first_prompt: str = None,
|
| 221 |
) -> None:
|
| 222 |
+
from .functions import KV
|
| 223 |
|
| 224 |
if not raw_obs:
|
| 225 |
return
|
| 226 |
+
created_at = (
|
| 227 |
+
datetime.datetime.now(datetime.timezone.utc).isoformat().replace("+00:00", "Z")
|
| 228 |
+
)
|
| 229 |
|
| 230 |
files = set()
|
| 231 |
tools = set()
|
|
|
|
| 368 |
|
| 369 |
|
| 370 |
def import_jsonl_data(kv, path: str = None, max_files: int = None) -> Dict[str, Any]:
|
| 371 |
+
from .functions import KV, _bm25_index, build_synthetic_compression
|
| 372 |
|
| 373 |
default_root = os.path.expanduser(os.path.join("~", ".claude", "projects"))
|
| 374 |
raw_path = path or default_root
|
|
|
|
| 481 |
}
|
| 482 |
kv.set(KV.sessions, session["id"], session)
|
| 483 |
|
| 484 |
+
from .functions import vector_index_add_guarded
|
| 485 |
|
| 486 |
compressed = []
|
| 487 |
for obs in parsed["observations"]:
|
|
|
|
| 512 |
)
|
| 513 |
|
| 514 |
# Save the updated persistence state
|
| 515 |
+
from . import functions
|
| 516 |
|
| 517 |
if functions._index_persistence:
|
| 518 |
try:
|
|
|
|
| 522 |
|
| 523 |
# Audit trail
|
| 524 |
try:
|
| 525 |
+
from .functions import log_audit
|
| 526 |
|
| 527 |
log_audit(
|
| 528 |
kv,
|
|
|
|
| 619 |
|
| 620 |
def project_timeline(observations: List[Dict[str, Any]]) -> Dict[str, Any]:
|
| 621 |
if not observations:
|
| 622 |
+
now = (
|
| 623 |
+
datetime.datetime.now(datetime.timezone.utc)
|
| 624 |
+
.isoformat()
|
| 625 |
+
.replace("+00:00", "Z")
|
| 626 |
+
)
|
| 627 |
return {
|
| 628 |
"sessionId": "",
|
| 629 |
"startedAt": now,
|
src/{routes β agentcache/routes}/__init__.py
RENAMED
|
@@ -4,13 +4,13 @@ Flask blueprints for agentmemory-python.
|
|
| 4 |
Import and register all blueprints via register_blueprints(app).
|
| 5 |
"""
|
| 6 |
|
| 7 |
-
from .observations import observations_bp
|
| 8 |
-
from .memories import memories_bp
|
| 9 |
-
from .search import search_bp
|
| 10 |
from .graph import graph_bp
|
| 11 |
from .health import health_bp
|
| 12 |
from .mcp import mcp_bp
|
|
|
|
| 13 |
from .migration import migration_bp
|
|
|
|
|
|
|
| 14 |
|
| 15 |
|
| 16 |
def register_blueprints(app):
|
|
|
|
| 4 |
Import and register all blueprints via register_blueprints(app).
|
| 5 |
"""
|
| 6 |
|
|
|
|
|
|
|
|
|
|
| 7 |
from .graph import graph_bp
|
| 8 |
from .health import health_bp
|
| 9 |
from .mcp import mcp_bp
|
| 10 |
+
from .memories import memories_bp
|
| 11 |
from .migration import migration_bp
|
| 12 |
+
from .observations import observations_bp
|
| 13 |
+
from .search import search_bp
|
| 14 |
|
| 15 |
|
| 16 |
def register_blueprints(app):
|
src/{routes β agentcache/routes}/graph.py
RENAMED
|
@@ -9,8 +9,10 @@ Handles:
|
|
| 9 |
"""
|
| 10 |
|
| 11 |
import os
|
| 12 |
-
|
| 13 |
-
import
|
|
|
|
|
|
|
| 14 |
|
| 15 |
graph_bp = Blueprint("graph", __name__)
|
| 16 |
|
|
@@ -31,7 +33,7 @@ def _check_auth():
|
|
| 31 |
|
| 32 |
|
| 33 |
def _get_kv():
|
| 34 |
-
import app as app_module
|
| 35 |
|
| 36 |
return app_module.kv
|
| 37 |
|
|
|
|
| 9 |
"""
|
| 10 |
|
| 11 |
import os
|
| 12 |
+
|
| 13 |
+
from flask import Blueprint, jsonify, request
|
| 14 |
+
|
| 15 |
+
from .. import functions
|
| 16 |
|
| 17 |
graph_bp = Blueprint("graph", __name__)
|
| 18 |
|
|
|
|
| 33 |
|
| 34 |
|
| 35 |
def _get_kv():
|
| 36 |
+
from .. import app as app_module
|
| 37 |
|
| 38 |
return app_module.kv
|
| 39 |
|
src/{routes β agentcache/routes}/health.py
RENAMED
|
@@ -9,9 +9,11 @@ Handles:
|
|
| 9 |
"""
|
| 10 |
|
| 11 |
import os
|
| 12 |
-
|
| 13 |
-
import
|
| 14 |
-
|
|
|
|
|
|
|
| 15 |
|
| 16 |
health_bp = Blueprint("health", __name__)
|
| 17 |
|
|
@@ -32,13 +34,13 @@ def _check_auth():
|
|
| 32 |
|
| 33 |
|
| 34 |
def _get_kv():
|
| 35 |
-
import app as app_module
|
| 36 |
|
| 37 |
return app_module.kv
|
| 38 |
|
| 39 |
|
| 40 |
def _get_embedding_provider():
|
| 41 |
-
import app as app_module
|
| 42 |
|
| 43 |
return app_module.embedding_provider
|
| 44 |
|
|
|
|
| 9 |
"""
|
| 10 |
|
| 11 |
import os
|
| 12 |
+
|
| 13 |
+
from flask import Blueprint, Response, jsonify, request
|
| 14 |
+
|
| 15 |
+
from .. import functions
|
| 16 |
+
from ..functions import query_audit
|
| 17 |
|
| 18 |
health_bp = Blueprint("health", __name__)
|
| 19 |
|
|
|
|
| 34 |
|
| 35 |
|
| 36 |
def _get_kv():
|
| 37 |
+
from .. import app as app_module
|
| 38 |
|
| 39 |
return app_module.kv
|
| 40 |
|
| 41 |
|
| 42 |
def _get_embedding_provider():
|
| 43 |
+
from .. import app as app_module
|
| 44 |
|
| 45 |
return app_module.embedding_provider
|
| 46 |
|
src/{routes β agentcache/routes}/mcp.py
RENAMED
|
@@ -6,12 +6,14 @@ Handles:
|
|
| 6 |
POST /agentmemory/mcp/tools β dispatch a tool call
|
| 7 |
"""
|
| 8 |
|
| 9 |
-
import os
|
| 10 |
-
import json
|
| 11 |
import datetime
|
| 12 |
-
|
| 13 |
-
import
|
| 14 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
|
| 16 |
mcp_bp = Blueprint("mcp", __name__)
|
| 17 |
|
|
@@ -32,13 +34,15 @@ def _check_auth():
|
|
| 32 |
|
| 33 |
|
| 34 |
def _get_kv():
|
| 35 |
-
import app as app_module
|
| 36 |
|
| 37 |
return app_module.kv
|
| 38 |
|
| 39 |
|
| 40 |
def _datetime_now_iso() -> str:
|
| 41 |
-
return
|
|
|
|
|
|
|
| 42 |
|
| 43 |
|
| 44 |
def _parse_mcp_list_arg(arg_val):
|
|
|
|
| 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 functions
|
| 16 |
+
from ..functions import KV
|
| 17 |
|
| 18 |
mcp_bp = Blueprint("mcp", __name__)
|
| 19 |
|
|
|
|
| 34 |
|
| 35 |
|
| 36 |
def _get_kv():
|
| 37 |
+
from .. import app as app_module
|
| 38 |
|
| 39 |
return app_module.kv
|
| 40 |
|
| 41 |
|
| 42 |
def _datetime_now_iso() -> str:
|
| 43 |
+
return (
|
| 44 |
+
datetime.datetime.now(datetime.timezone.utc).isoformat().replace("+00:00", "Z")
|
| 45 |
+
)
|
| 46 |
|
| 47 |
|
| 48 |
def _parse_mcp_list_arg(arg_val):
|
src/{routes β agentcache/routes}/memories.py
RENAMED
|
@@ -9,9 +9,11 @@ Handles:
|
|
| 9 |
"""
|
| 10 |
|
| 11 |
import os
|
| 12 |
-
|
| 13 |
-
import
|
| 14 |
-
|
|
|
|
|
|
|
| 15 |
|
| 16 |
memories_bp = Blueprint("memories", __name__)
|
| 17 |
|
|
@@ -32,7 +34,7 @@ def _check_auth():
|
|
| 32 |
|
| 33 |
|
| 34 |
def _get_kv():
|
| 35 |
-
import app as app_module
|
| 36 |
|
| 37 |
return app_module.kv
|
| 38 |
|
|
|
|
| 9 |
"""
|
| 10 |
|
| 11 |
import os
|
| 12 |
+
|
| 13 |
+
from flask import Blueprint, jsonify, request
|
| 14 |
+
|
| 15 |
+
from .. import functions
|
| 16 |
+
from ..functions import KV
|
| 17 |
|
| 18 |
memories_bp = Blueprint("memories", __name__)
|
| 19 |
|
|
|
|
| 34 |
|
| 35 |
|
| 36 |
def _get_kv():
|
| 37 |
+
from .. import app as app_module
|
| 38 |
|
| 39 |
return app_module.kv
|
| 40 |
|
src/{routes β agentcache/routes}/migration.py
RENAMED
|
@@ -6,8 +6,10 @@ Handles:
|
|
| 6 |
"""
|
| 7 |
|
| 8 |
import os
|
| 9 |
-
|
| 10 |
-
import
|
|
|
|
|
|
|
| 11 |
|
| 12 |
migration_bp = Blueprint("migration", __name__)
|
| 13 |
|
|
@@ -28,7 +30,7 @@ def _check_auth():
|
|
| 28 |
|
| 29 |
|
| 30 |
def _get_kv():
|
| 31 |
-
import app as app_module
|
| 32 |
|
| 33 |
return app_module.kv
|
| 34 |
|
|
|
|
| 6 |
"""
|
| 7 |
|
| 8 |
import os
|
| 9 |
+
|
| 10 |
+
from flask import Blueprint, jsonify, request
|
| 11 |
+
|
| 12 |
+
from .. import functions
|
| 13 |
|
| 14 |
migration_bp = Blueprint("migration", __name__)
|
| 15 |
|
|
|
|
| 30 |
|
| 31 |
|
| 32 |
def _get_kv():
|
| 33 |
+
from .. import app as app_module
|
| 34 |
|
| 35 |
return app_module.kv
|
| 36 |
|
src/{routes β agentcache/routes}/observations.py
RENAMED
|
@@ -8,17 +8,21 @@ Handles:
|
|
| 8 |
GET /agentmemory/folders
|
| 9 |
"""
|
| 10 |
|
| 11 |
-
import os
|
| 12 |
import datetime
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
from
|
|
|
|
|
|
|
|
|
|
| 16 |
|
| 17 |
observations_bp = Blueprint("observations", __name__)
|
| 18 |
|
| 19 |
|
| 20 |
def _datetime_now_iso() -> str:
|
| 21 |
-
return
|
|
|
|
|
|
|
| 22 |
|
| 23 |
|
| 24 |
def _check_auth():
|
|
@@ -39,7 +43,7 @@ def _check_auth():
|
|
| 39 |
|
| 40 |
def _get_kv():
|
| 41 |
"""Retrieve the shared kv instance from the app module."""
|
| 42 |
-
import app as app_module
|
| 43 |
|
| 44 |
return app_module.kv
|
| 45 |
|
|
|
|
| 8 |
GET /agentmemory/folders
|
| 9 |
"""
|
| 10 |
|
|
|
|
| 11 |
import datetime
|
| 12 |
+
import os
|
| 13 |
+
|
| 14 |
+
from flask import Blueprint, jsonify, request
|
| 15 |
+
|
| 16 |
+
from .. import functions
|
| 17 |
+
from ..functions import KV
|
| 18 |
|
| 19 |
observations_bp = Blueprint("observations", __name__)
|
| 20 |
|
| 21 |
|
| 22 |
def _datetime_now_iso() -> str:
|
| 23 |
+
return (
|
| 24 |
+
datetime.datetime.now(datetime.timezone.utc).isoformat().replace("+00:00", "Z")
|
| 25 |
+
)
|
| 26 |
|
| 27 |
|
| 28 |
def _check_auth():
|
|
|
|
| 43 |
|
| 44 |
def _get_kv():
|
| 45 |
"""Retrieve the shared kv instance from the app module."""
|
| 46 |
+
from .. import app as app_module
|
| 47 |
|
| 48 |
return app_module.kv
|
| 49 |
|
src/{routes β agentcache/routes}/search.py
RENAMED
|
@@ -7,8 +7,10 @@ Handles:
|
|
| 7 |
"""
|
| 8 |
|
| 9 |
import os
|
| 10 |
-
|
| 11 |
-
import
|
|
|
|
|
|
|
| 12 |
|
| 13 |
search_bp = Blueprint("search", __name__)
|
| 14 |
|
|
@@ -29,7 +31,7 @@ def _check_auth():
|
|
| 29 |
|
| 30 |
|
| 31 |
def _get_kv():
|
| 32 |
-
import app as app_module
|
| 33 |
|
| 34 |
return app_module.kv
|
| 35 |
|
|
|
|
| 7 |
"""
|
| 8 |
|
| 9 |
import os
|
| 10 |
+
|
| 11 |
+
from flask import Blueprint, jsonify, request
|
| 12 |
+
|
| 13 |
+
from .. import functions
|
| 14 |
|
| 15 |
search_bp = Blueprint("search", __name__)
|
| 16 |
|
|
|
|
| 31 |
|
| 32 |
|
| 33 |
def _get_kv():
|
| 34 |
+
from .. import app as app_module
|
| 35 |
|
| 36 |
return app_module.kv
|
| 37 |
|
src/{search.py β agentcache/search.py}
RENAMED
|
@@ -1,11 +1,11 @@
|
|
| 1 |
-
import re
|
| 2 |
-
import math
|
| 3 |
-
import json
|
| 4 |
-
import base64
|
| 5 |
import array
|
| 6 |
-
import
|
|
|
|
|
|
|
|
|
|
| 7 |
import urllib.parse
|
| 8 |
-
|
|
|
|
| 9 |
|
| 10 |
# =====================================================================
|
| 11 |
# Custom Porter-like Stemmer (Ported from stemmer.ts)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import array
|
| 2 |
+
import base64
|
| 3 |
+
import json
|
| 4 |
+
import math
|
| 5 |
+
import re
|
| 6 |
import urllib.parse
|
| 7 |
+
import urllib.request
|
| 8 |
+
from typing import Any, Dict, List, Optional, Set, Tuple
|
| 9 |
|
| 10 |
# =====================================================================
|
| 11 |
# Custom Porter-like Stemmer (Ported from stemmer.ts)
|
src/{storage β agentcache/storage}/__init__.py
RENAMED
|
@@ -12,9 +12,9 @@ These are compatibility copies β the originals in functions.py are kept intact
|
|
| 12 |
for backward compatibility.
|
| 13 |
"""
|
| 14 |
|
|
|
|
|
|
|
| 15 |
from .scopes import KV
|
| 16 |
-
from .paths import normalize_folder_path, validate_agent_id, generate_id, fingerprint_id
|
| 17 |
-
from .images import save_image_to_disk, delete_image, touch_image, is_managed_image_path
|
| 18 |
|
| 19 |
__all__ = [
|
| 20 |
"KV",
|
|
|
|
| 12 |
for backward compatibility.
|
| 13 |
"""
|
| 14 |
|
| 15 |
+
from .images import delete_image, is_managed_image_path, save_image_to_disk, touch_image
|
| 16 |
+
from .paths import fingerprint_id, generate_id, normalize_folder_path, validate_agent_id
|
| 17 |
from .scopes import KV
|
|
|
|
|
|
|
| 18 |
|
| 19 |
__all__ = [
|
| 20 |
"KV",
|
src/{storage β agentcache/storage}/images.py
RENAMED
|
@@ -4,8 +4,8 @@ src/storage/images.py β Image-on-disk storage helpers (A2.3).
|
|
| 4 |
Copied from src/functions.py β do NOT delete the originals (backward compat).
|
| 5 |
"""
|
| 6 |
|
| 7 |
-
import os
|
| 8 |
import hashlib
|
|
|
|
| 9 |
from typing import Optional, Tuple
|
| 10 |
|
| 11 |
IMAGES_DIR = os.path.join(os.path.expanduser("~"), ".agentmemory", "images")
|
|
|
|
| 4 |
Copied from src/functions.py β do NOT delete the originals (backward compat).
|
| 5 |
"""
|
| 6 |
|
|
|
|
| 7 |
import hashlib
|
| 8 |
+
import os
|
| 9 |
from typing import Optional, Tuple
|
| 10 |
|
| 11 |
IMAGES_DIR = os.path.join(os.path.expanduser("~"), ".agentmemory", "images")
|
src/{storage β agentcache/storage}/paths.py
RENAMED
|
@@ -4,10 +4,10 @@ src/storage/paths.py β Path normalisation and ID utilities (A2.3).
|
|
| 4 |
Copied from src/functions.py β do NOT delete the originals (backward compat).
|
| 5 |
"""
|
| 6 |
|
|
|
|
| 7 |
import os
|
| 8 |
import time
|
| 9 |
import uuid
|
| 10 |
-
import hashlib
|
| 11 |
|
| 12 |
# Maximum allowed length for folder paths and agent IDs.
|
| 13 |
_MAX_PATH_LEN = 512
|
|
|
|
| 4 |
Copied from src/functions.py β do NOT delete the originals (backward compat).
|
| 5 |
"""
|
| 6 |
|
| 7 |
+
import hashlib
|
| 8 |
import os
|
| 9 |
import time
|
| 10 |
import uuid
|
|
|
|
| 11 |
|
| 12 |
# Maximum allowed length for folder paths and agent IDs.
|
| 13 |
_MAX_PATH_LEN = 512
|
src/{storage β agentcache/storage}/scopes.py
RENAMED
|
File without changes
|
src/{viewer β agentcache/viewer}/favicon.svg
RENAMED
|
File without changes
|