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.
Files changed (148) hide show
  1. kore_memory-3.0.4/.dockerignore +20 -0
  2. {kore_memory-3.0.2 → kore_memory-3.0.4}/.github/workflows/ci.yml +3 -1
  3. {kore_memory-3.0.2 → kore_memory-3.0.4}/.gitignore +6 -4
  4. {kore_memory-3.0.2 → kore_memory-3.0.4}/CHANGELOG.md +51 -0
  5. kore_memory-3.0.4/Dockerfile +33 -0
  6. {kore_memory-3.0.2 → kore_memory-3.0.4}/PKG-INFO +35 -5
  7. {kore_memory-3.0.2 → kore_memory-3.0.4}/README.md +34 -4
  8. kore_memory-3.0.4/docker-compose.yml +27 -0
  9. {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/auth.py +1 -3
  10. kore_memory-3.0.4/kore_memory/cache.py +290 -0
  11. {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/compressor.py +8 -1
  12. {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/config.py +39 -1
  13. {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/conflict_detector.py +1 -1
  14. {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/context_assembler.py +2 -4
  15. {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/database.py +12 -0
  16. {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/main.py +240 -39
  17. {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/mcp_server.py +32 -1
  18. {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/models.py +55 -12
  19. {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/plugins.py +20 -0
  20. {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/ranking.py +107 -2
  21. {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/repository/__init__.py +2 -1
  22. {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/repository/graph.py +142 -3
  23. {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/repository/memory.py +47 -31
  24. {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/repository/search.py +1 -0
  25. {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/templates/dashboard.html +522 -23
  26. kore_memory-3.0.4/kore_memory/utils.py +24 -0
  27. {kore_memory-3.0.2 → kore_memory-3.0.4}/pyproject.toml +1 -1
  28. {kore_memory-3.0.2 → kore_memory-3.0.4}/sdk/js/package.json +1 -1
  29. kore_memory-3.0.4/tests/test_acl.py +158 -0
  30. {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/test_api.py +150 -12
  31. kore_memory-3.0.4/tests/test_api_endpoints.py +375 -0
  32. kore_memory-3.0.4/tests/test_cache.py +721 -0
  33. {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/test_client.py +9 -4
  34. {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/test_client_sync.py +10 -6
  35. kore_memory-3.0.4/tests/test_compressor.py +171 -0
  36. {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/test_dashboard.py +39 -0
  37. kore_memory-3.0.4/tests/test_integrations_init.py +66 -0
  38. {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/test_mcp.py +107 -0
  39. kore_memory-3.0.4/tests/test_plugins.py +216 -0
  40. kore_memory-3.0.4/tests/test_ranking_profiles.py +121 -0
  41. {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/test_wave3_graph.py +28 -0
  42. kore_memory-3.0.2/MIGRATION-v0.6.md +0 -107
  43. kore_memory-3.0.2/article-devto.md +0 -452
  44. kore_memory-3.0.2/docs/coding-memory-mode.md +0 -235
  45. kore_memory-3.0.2/docs/competitive-analysis-2025.md +0 -555
  46. kore_memory-3.0.2/docs/quickstart-v2.1.md +0 -310
  47. kore_memory-3.0.2/docs/v1.0-roadmap.md +0 -285
  48. {kore_memory-3.0.2 → kore_memory-3.0.4}/.coveragerc +0 -0
  49. {kore_memory-3.0.2 → kore_memory-3.0.4}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  50. {kore_memory-3.0.2 → kore_memory-3.0.4}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  51. {kore_memory-3.0.2 → kore_memory-3.0.4}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  52. {kore_memory-3.0.2 → kore_memory-3.0.4}/.github/workflows/benchmark.yml +0 -0
  53. {kore_memory-3.0.2 → kore_memory-3.0.4}/.github/workflows/build-sdk.yml +0 -0
  54. {kore_memory-3.0.2 → kore_memory-3.0.4}/.github/workflows/publish.yml +0 -0
  55. {kore_memory-3.0.2 → kore_memory-3.0.4}/CONTRIBUTING.md +0 -0
  56. {kore_memory-3.0.2 → kore_memory-3.0.4}/LICENSE +0 -0
  57. {kore_memory-3.0.2 → kore_memory-3.0.4}/assets/favicon.svg +0 -0
  58. {kore_memory-3.0.2 → kore_memory-3.0.4}/assets/logo.svg +0 -0
  59. {kore_memory-3.0.2 → kore_memory-3.0.4}/examples/async_usage.py +0 -0
  60. {kore_memory-3.0.2 → kore_memory-3.0.4}/examples/basic_usage.py +0 -0
  61. {kore_memory-3.0.2 → kore_memory-3.0.4}/examples/langchain_example.py +0 -0
  62. {kore_memory-3.0.2 → kore_memory-3.0.4}/hatch_build.py +0 -0
  63. {kore_memory-3.0.2 → kore_memory-3.0.4}/kore-daemon.sh +0 -0
  64. {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/__init__.py +0 -0
  65. {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/acl.py +0 -0
  66. {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/analytics.py +0 -0
  67. {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/audit.py +0 -0
  68. {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/auto_tuner.py +0 -0
  69. {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/cli.py +0 -0
  70. {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/client.py +0 -0
  71. {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/consolidation.py +0 -0
  72. {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/dashboard.py +0 -0
  73. {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/decay.py +0 -0
  74. {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/embedder.py +0 -0
  75. {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/events.py +0 -0
  76. {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/filesystem_overlay.py +0 -0
  77. {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/filesystem_watcher.py +0 -0
  78. {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/integrations/__init__.py +0 -0
  79. {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/integrations/crewai.py +0 -0
  80. {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/integrations/entities.py +0 -0
  81. {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/integrations/langchain.py +0 -0
  82. {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/integrations/openai_agents.py +0 -0
  83. {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/integrations/pydantic_ai.py +0 -0
  84. {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/policy_engine.py +0 -0
  85. {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/privacy.py +0 -0
  86. {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/py.typed +0 -0
  87. {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/repository/entity.py +0 -0
  88. {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/repository/lifecycle.py +0 -0
  89. {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/repository/sessions.py +0 -0
  90. {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/scorer.py +0 -0
  91. {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/structured.py +0 -0
  92. {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/summarizer.py +0 -0
  93. {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/vector_index.py +0 -0
  94. {kore_memory-3.0.2 → kore_memory-3.0.4}/kore_memory/welcome.py +0 -0
  95. {kore_memory-3.0.2 → kore_memory-3.0.4}/presets/claude-code/README.md +0 -0
  96. {kore_memory-3.0.2 → kore_memory-3.0.4}/presets/claude-code/mcp.json +0 -0
  97. {kore_memory-3.0.2 → kore_memory-3.0.4}/presets/claude-code-coding.md +0 -0
  98. {kore_memory-3.0.2 → kore_memory-3.0.4}/presets/cursor/README.md +0 -0
  99. {kore_memory-3.0.2 → kore_memory-3.0.4}/presets/cursor/mcp.json +0 -0
  100. {kore_memory-3.0.2 → kore_memory-3.0.4}/requirements.txt +0 -0
  101. {kore_memory-3.0.2 → kore_memory-3.0.4}/scripts/assert_benchmarks.py +0 -0
  102. {kore_memory-3.0.2 → kore_memory-3.0.4}/scripts/import_memory.py +0 -0
  103. {kore_memory-3.0.2 → kore_memory-3.0.4}/sdk/js/.gitignore +0 -0
  104. {kore_memory-3.0.2 → kore_memory-3.0.4}/sdk/js/README.md +0 -0
  105. {kore_memory-3.0.2 → kore_memory-3.0.4}/sdk/js/package-lock.json +0 -0
  106. {kore_memory-3.0.2 → kore_memory-3.0.4}/sdk/js/src/client.ts +0 -0
  107. {kore_memory-3.0.2 → kore_memory-3.0.4}/sdk/js/src/errors.ts +0 -0
  108. {kore_memory-3.0.2 → kore_memory-3.0.4}/sdk/js/src/index.ts +0 -0
  109. {kore_memory-3.0.2 → kore_memory-3.0.4}/sdk/js/src/types.ts +0 -0
  110. {kore_memory-3.0.2 → kore_memory-3.0.4}/sdk/js/tests/client.test.ts +0 -0
  111. {kore_memory-3.0.2 → kore_memory-3.0.4}/sdk/js/tests/errors.test.ts +0 -0
  112. {kore_memory-3.0.2 → kore_memory-3.0.4}/sdk/js/tests/helpers.ts +0 -0
  113. {kore_memory-3.0.2 → kore_memory-3.0.4}/sdk/js/tsconfig.json +0 -0
  114. {kore_memory-3.0.2 → kore_memory-3.0.4}/sdk/js/tsup.config.ts +0 -0
  115. {kore_memory-3.0.2 → kore_memory-3.0.4}/sdk/js/vitest.config.ts +0 -0
  116. {kore_memory-3.0.2 → kore_memory-3.0.4}/start.sh +0 -0
  117. {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/__init__.py +0 -0
  118. {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/benchmarks/__init__.py +0 -0
  119. {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/benchmarks/conftest_bench.py +0 -0
  120. {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/benchmarks/datasets/dataset_a_temporal.json +0 -0
  121. {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/benchmarks/datasets/dataset_b_conflicts.json +0 -0
  122. {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/benchmarks/datasets/dataset_c_coding.json +0 -0
  123. {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/benchmarks/datasets/dataset_d_graph.json +0 -0
  124. {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/benchmarks/datasets/dataset_e_context.json +0 -0
  125. {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/benchmarks/datasets/loaders.py +0 -0
  126. {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/benchmarks/test_benchmarks.py +0 -0
  127. {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/conftest.py +0 -0
  128. {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/test_audit.py +0 -0
  129. {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/test_auth_events.py +0 -0
  130. {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/test_auto_tuner.py +0 -0
  131. {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/test_cli.py +0 -0
  132. {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/test_conflict_detection.py +0 -0
  133. {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/test_coverage_gaps.py +0 -0
  134. {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/test_crewai.py +0 -0
  135. {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/test_entities.py +0 -0
  136. {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/test_langchain.py +0 -0
  137. {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/test_m1_graph_entity.py +0 -0
  138. {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/test_m2_structured_memory.py +0 -0
  139. {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/test_m3a_session_consolidation.py +0 -0
  140. {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/test_policy_engine.py +0 -0
  141. {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/test_ranking_engine.py +0 -0
  142. {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/test_sessions.py +0 -0
  143. {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/test_v11_fixes.py +0 -0
  144. {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/test_v12_features.py +0 -0
  145. {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/test_v21_temporal.py +0 -0
  146. {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/test_v2_features.py +0 -0
  147. {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/test_wave3_overlay.py +0 -0
  148. {kore_memory-3.0.2 → kore_memory-3.0.4}/tests/test_wave3_watcher.py +0 -0
@@ -0,0 +1,20 @@
1
+ .git
2
+ .venv
3
+ .pytest_cache
4
+ .ruff_cache
5
+ __pycache__
6
+ *.pyc
7
+ data/
8
+ logs/
9
+ dist/
10
+ .coverage
11
+ .env
12
+ .env.*
13
+ tests/
14
+ docs/
15
+ sdk/
16
+ scripts/
17
+ examples/
18
+ .github/
19
+ *.md
20
+ !README.md
@@ -144,7 +144,9 @@ jobs:
144
144
  python-version: "3.12"
145
145
 
146
146
  - name: Install tools
147
- run: pip install bandit pip-audit -e ".[dev]"
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
- # Piani di lavoro
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.2
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: MIT](https://img.shields.io/badge/license-MIT-green?style=flat-square)](LICENSE)
66
66
  [![Zero Cloud](https://img.shields.io/badge/cloud-zero-orange?style=flat-square)]()
67
67
  [![Multilingual](https://img.shields.io/badge/languages-50%2B-purple?style=flat-square)]()
68
- [![Tests](https://img.shields.io/badge/tests-648-brightgreen?style=flat-square)]()
68
+ [![Tests](https://img.shields.io/badge/tests-788-brightgreen?style=flat-square)]()
69
69
  [![Coverage](https://img.shields.io/badge/coverage-80%25-yellow?style=flat-square)]()
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.0 running on http://localhost:8765
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
- - [ ] Lifecycle Policy Engine (auto-archive rules, importance decay overrides)
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 648 tests
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: MIT](https://img.shields.io/badge/license-MIT-green?style=flat-square)](LICENSE)
17
17
  [![Zero Cloud](https://img.shields.io/badge/cloud-zero-orange?style=flat-square)]()
18
18
  [![Multilingual](https://img.shields.io/badge/languages-50%2B-purple?style=flat-square)]()
19
- [![Tests](https://img.shields.io/badge/tests-648-brightgreen?style=flat-square)]()
19
+ [![Tests](https://img.shields.io/badge/tests-788-brightgreen?style=flat-square)]()
20
20
  [![Coverage](https://img.shields.io/badge/coverage-80%25-yellow?style=flat-square)]()
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.0 running on http://localhost:8765
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
- - [ ] Lifecycle Policy Engine (auto-archive rules, importance decay overrides)
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 648 tests
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
- if request.headers.get("X-Forwarded-For") or request.headers.get("X-Real-IP"):
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 = []