kore-memory 3.0.2__tar.gz → 3.0.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- kore_memory-3.0.4/.dockerignore +20 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/.github/workflows/ci.yml +3 -1
- {kore_memory-3.0.2 → kore_memory-3.0.4}/.gitignore +6 -4
- {kore_memory-3.0.2 → kore_memory-3.0.4}/CHANGELOG.md +51 -0
- kore_memory-3.0.4/Dockerfile +33 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/PKG-INFO +35 -5
- {kore_memory-3.0.2 → kore_memory-3.0.4}/README.md +34 -4
- kore_memory-3.0.4/docker-compose.yml +27 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/auth.py +1 -3
- kore_memory-3.0.4/kore_memory/cache.py +290 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/compressor.py +8 -1
- {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/config.py +39 -1
- {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/conflict_detector.py +1 -1
- {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/context_assembler.py +2 -4
- {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/database.py +12 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/main.py +240 -39
- {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/mcp_server.py +32 -1
- {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/models.py +55 -12
- {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/plugins.py +20 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/ranking.py +107 -2
- {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/repository/__init__.py +2 -1
- {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/repository/graph.py +142 -3
- {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/repository/memory.py +47 -31
- {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/repository/search.py +1 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/templates/dashboard.html +522 -23
- kore_memory-3.0.4/kore_memory/utils.py +24 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/pyproject.toml +1 -1
- {kore_memory-3.0.2 → kore_memory-3.0.4}/sdk/js/package.json +1 -1
- kore_memory-3.0.4/tests/test_acl.py +158 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/test_api.py +150 -12
- kore_memory-3.0.4/tests/test_api_endpoints.py +375 -0
- kore_memory-3.0.4/tests/test_cache.py +721 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/test_client.py +9 -4
- {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/test_client_sync.py +10 -6
- kore_memory-3.0.4/tests/test_compressor.py +171 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/test_dashboard.py +39 -0
- kore_memory-3.0.4/tests/test_integrations_init.py +66 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/test_mcp.py +107 -0
- kore_memory-3.0.4/tests/test_plugins.py +216 -0
- kore_memory-3.0.4/tests/test_ranking_profiles.py +121 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/test_wave3_graph.py +28 -0
- kore_memory-3.0.2/MIGRATION-v0.6.md +0 -107
- kore_memory-3.0.2/article-devto.md +0 -452
- kore_memory-3.0.2/docs/coding-memory-mode.md +0 -235
- kore_memory-3.0.2/docs/competitive-analysis-2025.md +0 -555
- kore_memory-3.0.2/docs/quickstart-v2.1.md +0 -310
- kore_memory-3.0.2/docs/v1.0-roadmap.md +0 -285
- {kore_memory-3.0.2 → kore_memory-3.0.4}/.coveragerc +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/.github/workflows/benchmark.yml +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/.github/workflows/build-sdk.yml +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/.github/workflows/publish.yml +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/CONTRIBUTING.md +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/LICENSE +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/assets/favicon.svg +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/assets/logo.svg +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/examples/async_usage.py +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/examples/basic_usage.py +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/examples/langchain_example.py +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/hatch_build.py +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/kore-daemon.sh +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/__init__.py +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/acl.py +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/analytics.py +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/audit.py +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/auto_tuner.py +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/cli.py +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/client.py +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/consolidation.py +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/dashboard.py +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/decay.py +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/embedder.py +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/events.py +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/filesystem_overlay.py +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/filesystem_watcher.py +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/integrations/__init__.py +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/integrations/crewai.py +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/integrations/entities.py +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/integrations/langchain.py +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/integrations/openai_agents.py +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/integrations/pydantic_ai.py +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/policy_engine.py +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/privacy.py +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/py.typed +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/repository/entity.py +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/repository/lifecycle.py +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/repository/sessions.py +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/scorer.py +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/structured.py +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/summarizer.py +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/vector_index.py +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/welcome.py +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/presets/claude-code/README.md +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/presets/claude-code/mcp.json +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/presets/claude-code-coding.md +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/presets/cursor/README.md +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/presets/cursor/mcp.json +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/requirements.txt +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/scripts/assert_benchmarks.py +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/scripts/import_memory.py +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/sdk/js/.gitignore +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/sdk/js/README.md +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/sdk/js/package-lock.json +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/sdk/js/src/client.ts +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/sdk/js/src/errors.ts +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/sdk/js/src/index.ts +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/sdk/js/src/types.ts +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/sdk/js/tests/client.test.ts +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/sdk/js/tests/errors.test.ts +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/sdk/js/tests/helpers.ts +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/sdk/js/tsconfig.json +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/sdk/js/tsup.config.ts +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/sdk/js/vitest.config.ts +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/start.sh +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/__init__.py +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/benchmarks/__init__.py +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/benchmarks/conftest_bench.py +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/benchmarks/datasets/dataset_a_temporal.json +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/benchmarks/datasets/dataset_b_conflicts.json +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/benchmarks/datasets/dataset_c_coding.json +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/benchmarks/datasets/dataset_d_graph.json +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/benchmarks/datasets/dataset_e_context.json +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/benchmarks/datasets/loaders.py +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/benchmarks/test_benchmarks.py +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/conftest.py +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/test_audit.py +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/test_auth_events.py +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/test_auto_tuner.py +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/test_cli.py +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/test_conflict_detection.py +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/test_coverage_gaps.py +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/test_crewai.py +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/test_entities.py +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/test_langchain.py +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/test_m1_graph_entity.py +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/test_m2_structured_memory.py +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/test_m3a_session_consolidation.py +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/test_policy_engine.py +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/test_ranking_engine.py +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/test_sessions.py +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/test_v11_fixes.py +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/test_v12_features.py +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/test_v21_temporal.py +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/test_v2_features.py +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/test_wave3_overlay.py +0 -0
- {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/test_wave3_watcher.py +0 -0
|
@@ -144,7 +144,9 @@ jobs:
|
|
|
144
144
|
python-version: "3.12"
|
|
145
145
|
|
|
146
146
|
- name: Install tools
|
|
147
|
-
run:
|
|
147
|
+
run: |
|
|
148
|
+
pip install --upgrade pip
|
|
149
|
+
pip install bandit pip-audit -e ".[dev]"
|
|
148
150
|
|
|
149
151
|
- name: Run bandit (SAST)
|
|
150
152
|
run: bandit -r kore_memory/ -c pyproject.toml
|
|
@@ -37,11 +37,13 @@ logs/
|
|
|
37
37
|
.coverage
|
|
38
38
|
htmlcov/
|
|
39
39
|
|
|
40
|
-
#
|
|
41
|
-
PLAN-*.md
|
|
42
|
-
|
|
43
|
-
# Claude Code (local only)
|
|
40
|
+
# Internal docs (local only)
|
|
44
41
|
CLAUDE.md
|
|
42
|
+
MIGRATION-*.md
|
|
43
|
+
article-*.md
|
|
44
|
+
docs/
|
|
45
|
+
PLAN-*.md
|
|
46
|
+
.openclaude-profile.json
|
|
45
47
|
|
|
46
48
|
# SDK JS
|
|
47
49
|
sdk/js/node_modules/
|
|
@@ -11,6 +11,57 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
11
11
|
|
|
12
12
|
---
|
|
13
13
|
|
|
14
|
+
## [3.0.4] - 2026-05-15
|
|
15
|
+
|
|
16
|
+
### Theme: "CI Fixes"
|
|
17
|
+
|
|
18
|
+
Bug-fix release for CI pipeline: lint errors and test failures on GitHub Actions.
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- **Lint errors**: Fixed import sorting in `main.py`, `repository/__init__.py`, `repository/memory.py`
|
|
23
|
+
- **Unused imports**: Removed `get_index` unused import in `repository/graph.py`
|
|
24
|
+
- **Format errors**: Applied `ruff format` to 5 files (`cache.py`, `compressor.py`, `main.py`, `ranking.py`, `repository/memory.py`)
|
|
25
|
+
- **Test failures**: Fixed `test_compressor.py` to use `pytest.importorskip("numpy")` for optional dependency
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
|
|
29
|
+
- Version bumped to 3.0.4 across `pyproject.toml`, `config.py`, and `sdk/js/package.json`
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## [3.0.3] - 2026-05-15
|
|
34
|
+
|
|
35
|
+
### Theme: "Redis Caching Layer"
|
|
36
|
+
|
|
37
|
+
Optional Redis caching for improved performance on repeated queries. Fully backward compatible — disabled by default.
|
|
38
|
+
|
|
39
|
+
### Added — Caching
|
|
40
|
+
|
|
41
|
+
- **Optional Redis cache layer** (`kore_memory/cache.py`): Lazy-loaded client, graceful degradation if Redis unavailable
|
|
42
|
+
- **SearchCache**: Caches `/search` results (queries without cursor pagination)
|
|
43
|
+
- **GraphCache**: Caches `/graph/traverse`, `/graph/subgraph`, `/graph/hubs` responses
|
|
44
|
+
- **AnalyticsCache**: Caches `/analytics` endpoint
|
|
45
|
+
- **Automatic invalidation**: Cache invalidated on save, delete, archive, restore, compress
|
|
46
|
+
- **Configuration**: `KORE_REDIS_ENABLED`, `KORE_REDIS_HOST`, `KORE_REDIS_PORT`, `KORE_REDIS_PREFIX`, `KORE_REDIS_TTL`, `KORE_REDIS_SEARCH_TTL`, `KORE_REDIS_GRAPH_TTL`, `KORE_REDIS_ANALYTICS_TTL`
|
|
47
|
+
|
|
48
|
+
### Changed
|
|
49
|
+
|
|
50
|
+
- Search endpoint skips cache when cursor provided (preserves pagination correctness)
|
|
51
|
+
- Archive/restore endpoints invalidate cache appropriately
|
|
52
|
+
|
|
53
|
+
### Tests
|
|
54
|
+
|
|
55
|
+
- 53 new tests in `tests/test_cache.py` (with and without Redis)
|
|
56
|
+
- All 925 tests pass with Redis enabled
|
|
57
|
+
|
|
58
|
+
### Technical
|
|
59
|
+
|
|
60
|
+
- Pydantic model serialization in cache (handles nested models)
|
|
61
|
+
- No hard dependency on redis-py (imported lazily)
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
14
65
|
## [3.0.2] - 2026-04-28
|
|
15
66
|
|
|
16
67
|
### Theme: "Hardening & Bug Sweep"
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
FROM python:3.12-slim AS base
|
|
2
|
+
|
|
3
|
+
LABEL maintainer="Auriti Labs <info@auritidesign.it>"
|
|
4
|
+
LABEL org.opencontainers.image.source="https://github.com/Auriti-Labs/kore-memory"
|
|
5
|
+
LABEL org.opencontainers.image.description="Kore Memory — the memory layer that thinks like a human"
|
|
6
|
+
|
|
7
|
+
# Prevent Python from writing .pyc and enable unbuffered output
|
|
8
|
+
ENV PYTHONDONTWRITEBYTECODE=1 \
|
|
9
|
+
PYTHONUNBUFFERED=1
|
|
10
|
+
|
|
11
|
+
WORKDIR /app
|
|
12
|
+
|
|
13
|
+
# Install only production deps first (cache layer)
|
|
14
|
+
COPY pyproject.toml hatch_build.py README.md LICENSE ./
|
|
15
|
+
COPY kore_memory/ kore_memory/
|
|
16
|
+
COPY assets/ assets/
|
|
17
|
+
|
|
18
|
+
RUN pip install --no-cache-dir ".[semantic,mcp,watcher]"
|
|
19
|
+
|
|
20
|
+
# Runtime config
|
|
21
|
+
ENV KORE_HOST=0.0.0.0 \
|
|
22
|
+
KORE_PORT=8765 \
|
|
23
|
+
KORE_LOCAL_ONLY=0 \
|
|
24
|
+
KORE_DB_PATH=/data/memory.db
|
|
25
|
+
|
|
26
|
+
VOLUME /data
|
|
27
|
+
EXPOSE 8765 8766
|
|
28
|
+
|
|
29
|
+
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
|
|
30
|
+
CMD python -c "import httpx; r=httpx.get('http://127.0.0.1:8765/health'); r.raise_for_status()"
|
|
31
|
+
|
|
32
|
+
# Default: run REST API server
|
|
33
|
+
CMD ["kore", "--host", "0.0.0.0", "--port", "8765"]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kore-memory
|
|
3
|
-
Version: 3.0.
|
|
3
|
+
Version: 3.0.4
|
|
4
4
|
Summary: The memory layer that thinks like a human: remembers what matters, forgets what doesn't, and never calls home.
|
|
5
5
|
Project-URL: Homepage, https://github.com/auriti-labs/kore-memory
|
|
6
6
|
Project-URL: Documentation, https://auritidesign.it/docs
|
|
@@ -65,7 +65,7 @@ Remembers what matters. Forgets what doesn't. Never calls home.
|
|
|
65
65
|
[](LICENSE)
|
|
66
66
|
[]()
|
|
67
67
|
[]()
|
|
68
|
-
[]()
|
|
69
69
|
[]()
|
|
70
70
|
|
|
71
71
|
<br/>
|
|
@@ -114,6 +114,8 @@ Every AI agent memory tool has the same problem: they remember everything foreve
|
|
|
114
114
|
|
|
115
115
|
## ✨ What's New in v3.0 — Cognitive Runtime
|
|
116
116
|
|
|
117
|
+
> **v3.0.2** (2026-04-28) — Hardening release: 24 bug fixes including critical data integrity fix in compressor, auth bypass prevention behind reverse proxy, full pipeline parity for batch save, and 14 dashboard fixes. See [CHANGELOG.md](CHANGELOG.md).
|
|
118
|
+
|
|
117
119
|
Wave 3 is complete. Kore Memory is now a full **Cognitive Runtime** for AI agents.
|
|
118
120
|
|
|
119
121
|
| Feature | Since |
|
|
@@ -262,7 +264,7 @@ pip install 'kore-memory[semantic,mcp,watcher,nlp]'
|
|
|
262
264
|
|
|
263
265
|
```bash
|
|
264
266
|
kore
|
|
265
|
-
# → Kore Memory v3.0.
|
|
267
|
+
# → Kore Memory v3.0.2 running on http://localhost:8765
|
|
266
268
|
# → Dashboard: http://localhost:8765/dashboard
|
|
267
269
|
# → API docs: http://localhost:8765/docs
|
|
268
270
|
```
|
|
@@ -702,6 +704,7 @@ All configuration via environment variables. No config file needed.
|
|
|
702
704
|
## 🔐 Security
|
|
703
705
|
|
|
704
706
|
- **API key** — auto-generated on first run, stored in `data/.api_key` (chmod 600). Override via `KORE_API_KEY`
|
|
707
|
+
- **Reverse proxy detection** — when `X-Forwarded-For` or `X-Real-IP` headers are present, `LOCAL_ONLY` mode requires API key authentication (prevents auth bypass behind nginx/Caddy)
|
|
705
708
|
- **Agent isolation** — all queries are scoped to `agent_id`. Agents cannot read each other's memories without explicit ACL grant
|
|
706
709
|
- **Parameterized queries** — no SQL injection possible; all DB queries use placeholders
|
|
707
710
|
- **Timing-safe comparison** — `secrets.compare_digest` for API key validation
|
|
@@ -737,7 +740,7 @@ All configuration via environment variables. No config file needed.
|
|
|
737
740
|
|
|
738
741
|
**Wave 4 — In Planning**
|
|
739
742
|
|
|
740
|
-
- [
|
|
743
|
+
- [x] Lifecycle Policy Engine (auto-archive rules, importance decay overrides)
|
|
741
744
|
- [ ] Ranking Profiles per-Agent (persistent custom weights)
|
|
742
745
|
- [ ] Temporal Graph (relations with valid_from/to)
|
|
743
746
|
- [ ] Explainable Graph Retrieval (graph_path in context package)
|
|
@@ -747,6 +750,33 @@ All configuration via environment variables. No config file needed.
|
|
|
747
750
|
|
|
748
751
|
---
|
|
749
752
|
|
|
753
|
+
## 🐳 Docker
|
|
754
|
+
|
|
755
|
+
```bash
|
|
756
|
+
# Quick start
|
|
757
|
+
docker compose up -d
|
|
758
|
+
|
|
759
|
+
# Or build and run manually
|
|
760
|
+
docker build -t kore-memory .
|
|
761
|
+
docker run -d -p 8765:8765 -v kore-data:/data \
|
|
762
|
+
-e KORE_API_KEY=$(python3 -c "import secrets; print(secrets.token_urlsafe(32))") \
|
|
763
|
+
kore-memory
|
|
764
|
+
```
|
|
765
|
+
|
|
766
|
+
Dashboard at `http://localhost:8765/dashboard`, API docs at `http://localhost:8765/docs`.
|
|
767
|
+
|
|
768
|
+
Set `KORE_API_KEY` for authentication (required when `KORE_LOCAL_ONLY=0`). Data persists in the `kore-data` volume.
|
|
769
|
+
|
|
770
|
+
To also run the MCP HTTP server, override the command:
|
|
771
|
+
|
|
772
|
+
```bash
|
|
773
|
+
docker run -d -p 8766:8766 -v kore-data:/data \
|
|
774
|
+
-e KORE_MCP_TOKEN=your-token \
|
|
775
|
+
kore-memory kore-mcp --transport streamable-http --host 0.0.0.0
|
|
776
|
+
```
|
|
777
|
+
|
|
778
|
+
---
|
|
779
|
+
|
|
750
780
|
## 🛠️ Development
|
|
751
781
|
|
|
752
782
|
```bash
|
|
@@ -758,7 +788,7 @@ pip install -e ".[semantic,dev,mcp,watcher]"
|
|
|
758
788
|
# Run server
|
|
759
789
|
kore --reload
|
|
760
790
|
|
|
761
|
-
# Run all
|
|
791
|
+
# Run all 788 tests
|
|
762
792
|
pytest tests/ -v
|
|
763
793
|
|
|
764
794
|
# Run with coverage
|
|
@@ -16,7 +16,7 @@ Remembers what matters. Forgets what doesn't. Never calls home.
|
|
|
16
16
|
[](LICENSE)
|
|
17
17
|
[]()
|
|
18
18
|
[]()
|
|
19
|
-
[]()
|
|
20
20
|
[]()
|
|
21
21
|
|
|
22
22
|
<br/>
|
|
@@ -65,6 +65,8 @@ Every AI agent memory tool has the same problem: they remember everything foreve
|
|
|
65
65
|
|
|
66
66
|
## ✨ What's New in v3.0 — Cognitive Runtime
|
|
67
67
|
|
|
68
|
+
> **v3.0.2** (2026-04-28) — Hardening release: 24 bug fixes including critical data integrity fix in compressor, auth bypass prevention behind reverse proxy, full pipeline parity for batch save, and 14 dashboard fixes. See [CHANGELOG.md](CHANGELOG.md).
|
|
69
|
+
|
|
68
70
|
Wave 3 is complete. Kore Memory is now a full **Cognitive Runtime** for AI agents.
|
|
69
71
|
|
|
70
72
|
| Feature | Since |
|
|
@@ -213,7 +215,7 @@ pip install 'kore-memory[semantic,mcp,watcher,nlp]'
|
|
|
213
215
|
|
|
214
216
|
```bash
|
|
215
217
|
kore
|
|
216
|
-
# → Kore Memory v3.0.
|
|
218
|
+
# → Kore Memory v3.0.2 running on http://localhost:8765
|
|
217
219
|
# → Dashboard: http://localhost:8765/dashboard
|
|
218
220
|
# → API docs: http://localhost:8765/docs
|
|
219
221
|
```
|
|
@@ -653,6 +655,7 @@ All configuration via environment variables. No config file needed.
|
|
|
653
655
|
## 🔐 Security
|
|
654
656
|
|
|
655
657
|
- **API key** — auto-generated on first run, stored in `data/.api_key` (chmod 600). Override via `KORE_API_KEY`
|
|
658
|
+
- **Reverse proxy detection** — when `X-Forwarded-For` or `X-Real-IP` headers are present, `LOCAL_ONLY` mode requires API key authentication (prevents auth bypass behind nginx/Caddy)
|
|
656
659
|
- **Agent isolation** — all queries are scoped to `agent_id`. Agents cannot read each other's memories without explicit ACL grant
|
|
657
660
|
- **Parameterized queries** — no SQL injection possible; all DB queries use placeholders
|
|
658
661
|
- **Timing-safe comparison** — `secrets.compare_digest` for API key validation
|
|
@@ -688,7 +691,7 @@ All configuration via environment variables. No config file needed.
|
|
|
688
691
|
|
|
689
692
|
**Wave 4 — In Planning**
|
|
690
693
|
|
|
691
|
-
- [
|
|
694
|
+
- [x] Lifecycle Policy Engine (auto-archive rules, importance decay overrides)
|
|
692
695
|
- [ ] Ranking Profiles per-Agent (persistent custom weights)
|
|
693
696
|
- [ ] Temporal Graph (relations with valid_from/to)
|
|
694
697
|
- [ ] Explainable Graph Retrieval (graph_path in context package)
|
|
@@ -698,6 +701,33 @@ All configuration via environment variables. No config file needed.
|
|
|
698
701
|
|
|
699
702
|
---
|
|
700
703
|
|
|
704
|
+
## 🐳 Docker
|
|
705
|
+
|
|
706
|
+
```bash
|
|
707
|
+
# Quick start
|
|
708
|
+
docker compose up -d
|
|
709
|
+
|
|
710
|
+
# Or build and run manually
|
|
711
|
+
docker build -t kore-memory .
|
|
712
|
+
docker run -d -p 8765:8765 -v kore-data:/data \
|
|
713
|
+
-e KORE_API_KEY=$(python3 -c "import secrets; print(secrets.token_urlsafe(32))") \
|
|
714
|
+
kore-memory
|
|
715
|
+
```
|
|
716
|
+
|
|
717
|
+
Dashboard at `http://localhost:8765/dashboard`, API docs at `http://localhost:8765/docs`.
|
|
718
|
+
|
|
719
|
+
Set `KORE_API_KEY` for authentication (required when `KORE_LOCAL_ONLY=0`). Data persists in the `kore-data` volume.
|
|
720
|
+
|
|
721
|
+
To also run the MCP HTTP server, override the command:
|
|
722
|
+
|
|
723
|
+
```bash
|
|
724
|
+
docker run -d -p 8766:8766 -v kore-data:/data \
|
|
725
|
+
-e KORE_MCP_TOKEN=your-token \
|
|
726
|
+
kore-memory kore-mcp --transport streamable-http --host 0.0.0.0
|
|
727
|
+
```
|
|
728
|
+
|
|
729
|
+
---
|
|
730
|
+
|
|
701
731
|
## 🛠️ Development
|
|
702
732
|
|
|
703
733
|
```bash
|
|
@@ -709,7 +739,7 @@ pip install -e ".[semantic,dev,mcp,watcher]"
|
|
|
709
739
|
# Run server
|
|
710
740
|
kore --reload
|
|
711
741
|
|
|
712
|
-
# Run all
|
|
742
|
+
# Run all 788 tests
|
|
713
743
|
pytest tests/ -v
|
|
714
744
|
|
|
715
745
|
# Run with coverage
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
services:
|
|
2
|
+
kore:
|
|
3
|
+
build: .
|
|
4
|
+
image: ghcr.io/auriti-labs/kore-memory:latest
|
|
5
|
+
ports:
|
|
6
|
+
- "8765:8765" # REST API + Dashboard
|
|
7
|
+
- "8766:8766" # MCP HTTP transport (optional)
|
|
8
|
+
volumes:
|
|
9
|
+
- kore-data:/data
|
|
10
|
+
environment:
|
|
11
|
+
- KORE_LOCAL_ONLY=0
|
|
12
|
+
# Generate a key: python3 -c "import secrets; print(secrets.token_urlsafe(32))"
|
|
13
|
+
# - KORE_API_KEY=your-secret-key-here
|
|
14
|
+
# - KORE_MCP_TOKEN=your-mcp-token-here
|
|
15
|
+
# - KORE_EMBED_MODEL=paraphrase-multilingual-MiniLM-L12-v2
|
|
16
|
+
# - KORE_AUDIT_LOG=1
|
|
17
|
+
# - KORE_ENTITY_EXTRACTION=1
|
|
18
|
+
restart: unless-stopped
|
|
19
|
+
healthcheck:
|
|
20
|
+
test: ["CMD", "python", "-c", "import httpx; httpx.get('http://127.0.0.1:8765/health').raise_for_status()"]
|
|
21
|
+
interval: 30s
|
|
22
|
+
timeout: 5s
|
|
23
|
+
retries: 3
|
|
24
|
+
start_period: 15s
|
|
25
|
+
|
|
26
|
+
volumes:
|
|
27
|
+
kore-data:
|
|
@@ -68,9 +68,7 @@ def _is_local(request: Request) -> bool:
|
|
|
68
68
|
return False
|
|
69
69
|
# Detect reverse proxy: if X-Forwarded-For is present, the real client
|
|
70
70
|
# is NOT local — require auth even if socket peer is localhost
|
|
71
|
-
|
|
72
|
-
return False
|
|
73
|
-
return True
|
|
71
|
+
return not (request.headers.get("X-Forwarded-For") or request.headers.get("X-Real-IP"))
|
|
74
72
|
|
|
75
73
|
|
|
76
74
|
def _local_only_mode() -> bool:
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Kore — Optional Redis Cache Layer
|
|
3
|
+
Provides caching for search results, graph data, and analytics.
|
|
4
|
+
Disabled by default — activate with KORE_REDIS_ENABLED=1.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import logging
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from . import config
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
# ── Redis client (lazy-loaded) ────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
_redis_client = None
|
|
20
|
+
_redis_available = False
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _get_redis_client():
|
|
24
|
+
"""Lazy-load Redis client to avoid hard dependency."""
|
|
25
|
+
global _redis_client, _redis_available
|
|
26
|
+
|
|
27
|
+
if not config.REDIS_ENABLED:
|
|
28
|
+
return None
|
|
29
|
+
|
|
30
|
+
if _redis_client is not None:
|
|
31
|
+
return _redis_client
|
|
32
|
+
|
|
33
|
+
try:
|
|
34
|
+
import redis
|
|
35
|
+
from redis.exceptions import ConnectionError, TimeoutError
|
|
36
|
+
|
|
37
|
+
_redis_client = redis.Redis(
|
|
38
|
+
host=config.REDIS_HOST,
|
|
39
|
+
port=config.REDIS_PORT,
|
|
40
|
+
db=config.REDIS_DB,
|
|
41
|
+
password=config.REDIS_PASSWORD or None,
|
|
42
|
+
decode_responses=True,
|
|
43
|
+
socket_connect_timeout=5,
|
|
44
|
+
socket_timeout=5,
|
|
45
|
+
)
|
|
46
|
+
# Test connection
|
|
47
|
+
_redis_client.ping()
|
|
48
|
+
_redis_available = True
|
|
49
|
+
logger.info(
|
|
50
|
+
"Redis cache enabled: %s:%d/%d (prefix=%s)",
|
|
51
|
+
config.REDIS_HOST,
|
|
52
|
+
config.REDIS_PORT,
|
|
53
|
+
config.REDIS_DB,
|
|
54
|
+
config.REDIS_PREFIX,
|
|
55
|
+
)
|
|
56
|
+
return _redis_client
|
|
57
|
+
|
|
58
|
+
except ImportError:
|
|
59
|
+
logger.warning("Redis cache requested but redis-py not installed (pip install redis)")
|
|
60
|
+
_redis_available = False
|
|
61
|
+
return None
|
|
62
|
+
|
|
63
|
+
except (ConnectionError, TimeoutError) as e:
|
|
64
|
+
logger.warning("Redis connection failed: %s — cache disabled", e)
|
|
65
|
+
_redis_available = False
|
|
66
|
+
return None
|
|
67
|
+
|
|
68
|
+
except Exception as e:
|
|
69
|
+
logger.warning("Redis unexpected error: %s — cache disabled", e)
|
|
70
|
+
_redis_available = False
|
|
71
|
+
return None
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def is_available() -> bool:
|
|
75
|
+
"""Check if Redis cache is available and connected."""
|
|
76
|
+
if not config.REDIS_ENABLED:
|
|
77
|
+
return False
|
|
78
|
+
if _redis_available:
|
|
79
|
+
return True
|
|
80
|
+
# Try to connect
|
|
81
|
+
return _get_redis_client() is not None
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
# ── Cache helpers ─────────────────────────────────────────────────────────────
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _key(name: str) -> str:
|
|
88
|
+
"""Build cache key with prefix."""
|
|
89
|
+
return f"{config.REDIS_PREFIX}{name}"
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _serialize(value: Any) -> str:
|
|
93
|
+
"""Serialize value to JSON string, handling Pydantic models."""
|
|
94
|
+
|
|
95
|
+
def _default(o):
|
|
96
|
+
# Handle Pydantic v2 models
|
|
97
|
+
if hasattr(o, "model_dump"):
|
|
98
|
+
return o.model_dump()
|
|
99
|
+
if hasattr(o, "dict"):
|
|
100
|
+
# Pydantic v1 fallback
|
|
101
|
+
return o.dict()
|
|
102
|
+
return str(o)
|
|
103
|
+
|
|
104
|
+
return json.dumps(value, default=_default)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _deserialize(value: str | None) -> Any | None:
|
|
108
|
+
"""Deserialize JSON string to Python value."""
|
|
109
|
+
if value is None:
|
|
110
|
+
return None
|
|
111
|
+
try:
|
|
112
|
+
return json.loads(value)
|
|
113
|
+
except (json.JSONDecodeError, TypeError):
|
|
114
|
+
return None
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
# ── Cache operations ──────────────────────────────────────────────────────────
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def get(name: str) -> Any | None:
|
|
121
|
+
"""Get value from cache by name."""
|
|
122
|
+
client = _get_redis_client()
|
|
123
|
+
if not client:
|
|
124
|
+
return None
|
|
125
|
+
|
|
126
|
+
try:
|
|
127
|
+
value = client.get(_key(name))
|
|
128
|
+
return _deserialize(value)
|
|
129
|
+
except Exception as e:
|
|
130
|
+
logger.debug("Cache get error for %s: %s", name, e)
|
|
131
|
+
return None
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def set(name: str, value: Any, ttl: int | None = None) -> bool:
|
|
135
|
+
"""Set value in cache with optional TTL."""
|
|
136
|
+
client = _get_redis_client()
|
|
137
|
+
if not client:
|
|
138
|
+
return False
|
|
139
|
+
|
|
140
|
+
try:
|
|
141
|
+
key = _key(name)
|
|
142
|
+
serialized = _serialize(value)
|
|
143
|
+
if ttl is None:
|
|
144
|
+
ttl = config.REDIS_TTL
|
|
145
|
+
if ttl > 0:
|
|
146
|
+
client.setex(key, ttl, serialized)
|
|
147
|
+
else:
|
|
148
|
+
client.set(key, serialized)
|
|
149
|
+
return True
|
|
150
|
+
except Exception as e:
|
|
151
|
+
logger.debug("Cache set error for %s: %s", name, e)
|
|
152
|
+
return False
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def delete(name: str) -> bool:
|
|
156
|
+
"""Delete value from cache."""
|
|
157
|
+
client = _get_redis_client()
|
|
158
|
+
if not client:
|
|
159
|
+
return False
|
|
160
|
+
|
|
161
|
+
try:
|
|
162
|
+
client.delete(_key(name))
|
|
163
|
+
return True
|
|
164
|
+
except Exception as e:
|
|
165
|
+
logger.debug("Cache delete error for %s: %s", name, e)
|
|
166
|
+
return False
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def clear_pattern(pattern: str) -> int:
|
|
170
|
+
"""Clear all keys matching pattern (e.g., 'search:*'). Returns count deleted."""
|
|
171
|
+
client = _get_redis_client()
|
|
172
|
+
if not client:
|
|
173
|
+
return 0
|
|
174
|
+
|
|
175
|
+
try:
|
|
176
|
+
full_pattern = _key(pattern)
|
|
177
|
+
keys = list(client.scan_iter(match=full_pattern))
|
|
178
|
+
if keys:
|
|
179
|
+
return client.delete(*keys)
|
|
180
|
+
return 0
|
|
181
|
+
except Exception as e:
|
|
182
|
+
logger.debug("Cache clear error for %s: %s", pattern, e)
|
|
183
|
+
return 0
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
# ── High-level cache wrappers ─────────────────────────────────────────────────
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
class SearchCache:
|
|
190
|
+
"""Cache wrapper for search results."""
|
|
191
|
+
|
|
192
|
+
@staticmethod
|
|
193
|
+
def _cache_key(query: str, agent_id: str, semantic: bool, limit: int) -> str:
|
|
194
|
+
return f"search:{agent_id}:{'sem' if semantic else 'fts'}:{limit}:{hash(query)}"
|
|
195
|
+
|
|
196
|
+
@classmethod
|
|
197
|
+
def get(cls, query: str, agent_id: str, semantic: bool, limit: int) -> list[dict] | None:
|
|
198
|
+
"""Get cached search results."""
|
|
199
|
+
key = cls._cache_key(query, agent_id, semantic, limit)
|
|
200
|
+
result = get(key)
|
|
201
|
+
if result is not None:
|
|
202
|
+
logger.debug("Cache HIT for search: %s", query[:50])
|
|
203
|
+
return result
|
|
204
|
+
|
|
205
|
+
@classmethod
|
|
206
|
+
def set(cls, query: str, agent_id: str, semantic: bool, limit: int, results: list[dict]) -> bool:
|
|
207
|
+
"""Cache search results."""
|
|
208
|
+
key = cls._cache_key(query, agent_id, semantic, limit)
|
|
209
|
+
return set(key, results, ttl=config.REDIS_SEARCH_TTL)
|
|
210
|
+
|
|
211
|
+
@classmethod
|
|
212
|
+
def invalidate(cls, agent_id: str) -> int:
|
|
213
|
+
"""Invalidate all search cache for an agent."""
|
|
214
|
+
return clear_pattern(f"search:{agent_id}:*")
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
class GraphCache:
|
|
218
|
+
"""Cache wrapper for graph data."""
|
|
219
|
+
|
|
220
|
+
@staticmethod
|
|
221
|
+
def _cache_key(kind: str, **params) -> str:
|
|
222
|
+
param_str = ":".join(f"{k}={v}" for k, v in sorted(params.items()))
|
|
223
|
+
return f"graph:{kind}:{param_str}"
|
|
224
|
+
|
|
225
|
+
@classmethod
|
|
226
|
+
def get(cls, kind: str, **params) -> dict | None:
|
|
227
|
+
"""Get cached graph data."""
|
|
228
|
+
key = cls._cache_key(kind, **params)
|
|
229
|
+
return get(key)
|
|
230
|
+
|
|
231
|
+
@classmethod
|
|
232
|
+
def set(cls, kind: str, data: dict, **params) -> bool:
|
|
233
|
+
"""Cache graph data."""
|
|
234
|
+
key = cls._cache_key(kind, **params)
|
|
235
|
+
return set(key, data, ttl=config.REDIS_GRAPH_TTL)
|
|
236
|
+
|
|
237
|
+
@classmethod
|
|
238
|
+
def invalidate(cls) -> int:
|
|
239
|
+
"""Invalidate all graph cache."""
|
|
240
|
+
return clear_pattern("graph:*")
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
class AnalyticsCache:
|
|
244
|
+
"""Cache wrapper for analytics data."""
|
|
245
|
+
|
|
246
|
+
@staticmethod
|
|
247
|
+
def _cache_key(agent_id: str, kind: str) -> str:
|
|
248
|
+
return f"analytics:{agent_id}:{kind}"
|
|
249
|
+
|
|
250
|
+
@classmethod
|
|
251
|
+
def get(cls, agent_id: str, kind: str) -> dict | None:
|
|
252
|
+
"""Get cached analytics."""
|
|
253
|
+
key = cls._cache_key(agent_id, kind)
|
|
254
|
+
return get(key)
|
|
255
|
+
|
|
256
|
+
@classmethod
|
|
257
|
+
def set(cls, agent_id: str, kind: str, data: dict) -> bool:
|
|
258
|
+
"""Cache analytics data."""
|
|
259
|
+
key = cls._cache_key(agent_id, kind)
|
|
260
|
+
return set(key, data, ttl=config.REDIS_ANALYTICS_TTL)
|
|
261
|
+
|
|
262
|
+
@classmethod
|
|
263
|
+
def invalidate(cls, agent_id: str) -> int:
|
|
264
|
+
"""Invalidate all analytics cache for an agent."""
|
|
265
|
+
return clear_pattern(f"analytics:{agent_id}:*")
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
# ── Cache invalidation hooks ──────────────────────────────────────────────────
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def invalidate_on_save(agent_id: str) -> None:
|
|
272
|
+
"""Invalidate relevant caches when a memory is saved."""
|
|
273
|
+
SearchCache.invalidate(agent_id)
|
|
274
|
+
AnalyticsCache.invalidate(agent_id)
|
|
275
|
+
# Graph may need invalidation if entities/tags changed
|
|
276
|
+
GraphCache.invalidate()
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def invalidate_on_delete(agent_id: str) -> None:
|
|
280
|
+
"""Invalidate relevant caches when a memory is deleted."""
|
|
281
|
+
SearchCache.invalidate(agent_id)
|
|
282
|
+
AnalyticsCache.invalidate(agent_id)
|
|
283
|
+
GraphCache.invalidate()
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def invalidate_on_compress(agent_id: str) -> None:
|
|
287
|
+
"""Invalidate all caches after compression (many memories changed)."""
|
|
288
|
+
SearchCache.invalidate(agent_id)
|
|
289
|
+
AnalyticsCache.invalidate(agent_id)
|
|
290
|
+
GraphCache.invalidate()
|
|
@@ -93,7 +93,7 @@ def _load_compressible_memories(agent_id: str = "default") -> list[dict]:
|
|
|
93
93
|
with get_connection() as conn:
|
|
94
94
|
rows = conn.execute(
|
|
95
95
|
"""
|
|
96
|
-
SELECT id, content, category, importance, embedding
|
|
96
|
+
SELECT id, content, category, importance, embedding, agent_id
|
|
97
97
|
FROM memories
|
|
98
98
|
WHERE compressed_into IS NULL AND archived_at IS NULL AND embedding IS NOT NULL AND agent_id = ?
|
|
99
99
|
""",
|
|
@@ -310,6 +310,13 @@ def _merge_cluster(cluster: list[dict], agent_id: str = "default") -> int | None
|
|
|
310
310
|
if not cluster:
|
|
311
311
|
return None
|
|
312
312
|
|
|
313
|
+
# Sicurezza: verifica agent isolation nel cluster (difesa in profondità)
|
|
314
|
+
cluster_agent_ids = {m.get("agent_id") for m in cluster if "agent_id" in m}
|
|
315
|
+
if len(cluster_agent_ids) > 1:
|
|
316
|
+
# Cluster cross-agent rilevato — skip per sicurezza
|
|
317
|
+
print(f"[COMPRESSIONE] WARNING: cluster cross-agent rilevato: {cluster_agent_ids}. Skip per sicurezza.")
|
|
318
|
+
return None
|
|
319
|
+
|
|
313
320
|
# Build merged content: combine unique sentences
|
|
314
321
|
# Split on sentence boundaries (. ! ? followed by space/end) instead of pipe
|
|
315
322
|
combined_parts = []
|