kore-memory 2.1.0__tar.gz → 2.4.0__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-2.4.0/.github/workflows/benchmark.yml +48 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/.github/workflows/ci.yml +8 -1
- {kore_memory-2.1.0 → kore_memory-2.4.0}/CHANGELOG.md +142 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/PKG-INFO +1 -1
- {kore_memory-2.1.0 → kore_memory-2.4.0}/kore_memory/config.py +1 -1
- {kore_memory-2.1.0 → kore_memory-2.4.0}/kore_memory/conflict_detector.py +1 -4
- kore_memory-2.4.0/kore_memory/context_assembler.py +168 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/kore_memory/database.py +31 -3
- kore_memory-2.4.0/kore_memory/filesystem_overlay.py +367 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/kore_memory/main.py +259 -12
- {kore_memory-2.1.0 → kore_memory-2.4.0}/kore_memory/mcp_server.py +229 -23
- {kore_memory-2.1.0 → kore_memory-2.4.0}/kore_memory/models.py +168 -1
- kore_memory-2.4.0/kore_memory/ranking.py +279 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/kore_memory/repository/__init__.py +12 -1
- kore_memory-2.4.0/kore_memory/repository/graph.py +427 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/kore_memory/repository/memory.py +17 -3
- {kore_memory-2.1.0 → kore_memory-2.4.0}/kore_memory/repository/search.py +180 -59
- {kore_memory-2.1.0 → kore_memory-2.4.0}/kore_memory/templates/dashboard.html +6 -2
- {kore_memory-2.1.0 → kore_memory-2.4.0}/presets/claude-code/README.md +2 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/presets/cursor/README.md +2 -2
- {kore_memory-2.1.0 → kore_memory-2.4.0}/pyproject.toml +2 -1
- kore_memory-2.4.0/scripts/assert_benchmarks.py +159 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/sdk/js/package.json +1 -1
- kore_memory-2.4.0/tests/benchmarks/__init__.py +1 -0
- kore_memory-2.4.0/tests/benchmarks/conftest_bench.py +63 -0
- kore_memory-2.4.0/tests/benchmarks/datasets/dataset_a_temporal.json +1648 -0
- kore_memory-2.4.0/tests/benchmarks/datasets/dataset_b_conflicts.json +1516 -0
- kore_memory-2.4.0/tests/benchmarks/datasets/dataset_c_coding.json +1981 -0
- kore_memory-2.4.0/tests/benchmarks/datasets/dataset_d_graph.json +462 -0
- kore_memory-2.4.0/tests/benchmarks/datasets/dataset_e_context.json +575 -0
- kore_memory-2.4.0/tests/benchmarks/datasets/loaders.py +53 -0
- kore_memory-2.4.0/tests/benchmarks/test_benchmarks.py +632 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/tests/test_client.py +1 -1
- {kore_memory-2.1.0 → kore_memory-2.4.0}/tests/test_client_sync.py +1 -1
- {kore_memory-2.1.0 → kore_memory-2.4.0}/tests/test_v12_features.py +3 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/tests/test_v21_temporal.py +5 -5
- kore_memory-2.4.0/tests/test_wave3_graph.py +290 -0
- kore_memory-2.4.0/tests/test_wave3_overlay.py +350 -0
- kore_memory-2.1.0/.claude/settings.local.json +0 -6
- kore_memory-2.1.0/kore_memory/ranking.py +0 -158
- kore_memory-2.1.0/kore_memory/repository/graph.py +0 -220
- {kore_memory-2.1.0 → kore_memory-2.4.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/.github/workflows/build-sdk.yml +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/.github/workflows/publish.yml +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/.gitignore +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/CONTRIBUTING.md +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/LICENSE +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/MIGRATION-v0.6.md +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/README.md +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/article-devto.md +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/assets/favicon.svg +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/assets/logo.svg +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/docs/competitive-analysis-2025.md +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/docs/quickstart-v2.1.md +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/docs/v1.0-roadmap.md +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/examples/async_usage.py +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/examples/basic_usage.py +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/examples/langchain_example.py +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/hatch_build.py +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/kore-daemon.sh +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/kore_memory/__init__.py +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/kore_memory/acl.py +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/kore_memory/analytics.py +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/kore_memory/audit.py +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/kore_memory/auth.py +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/kore_memory/auto_tuner.py +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/kore_memory/cli.py +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/kore_memory/client.py +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/kore_memory/compressor.py +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/kore_memory/dashboard.py +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/kore_memory/decay.py +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/kore_memory/embedder.py +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/kore_memory/events.py +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/kore_memory/integrations/__init__.py +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/kore_memory/integrations/crewai.py +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/kore_memory/integrations/entities.py +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/kore_memory/integrations/langchain.py +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/kore_memory/integrations/openai_agents.py +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/kore_memory/integrations/pydantic_ai.py +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/kore_memory/plugins.py +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/kore_memory/py.typed +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/kore_memory/repository/lifecycle.py +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/kore_memory/repository/sessions.py +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/kore_memory/scorer.py +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/kore_memory/summarizer.py +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/kore_memory/vector_index.py +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/kore_memory/welcome.py +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/presets/claude-code/mcp.json +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/presets/cursor/mcp.json +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/requirements.txt +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/scripts/import_memory.py +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/sdk/js/.gitignore +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/sdk/js/README.md +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/sdk/js/package-lock.json +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/sdk/js/src/client.ts +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/sdk/js/src/errors.ts +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/sdk/js/src/index.ts +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/sdk/js/src/types.ts +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/sdk/js/tests/client.test.ts +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/sdk/js/tests/errors.test.ts +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/sdk/js/tests/helpers.ts +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/sdk/js/tsconfig.json +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/sdk/js/tsup.config.ts +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/sdk/js/vitest.config.ts +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/start.sh +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/test_pagination_fix.py +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/tests/__init__.py +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/tests/conftest.py +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/tests/test_api.py +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/tests/test_audit.py +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/tests/test_auth_events.py +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/tests/test_auto_tuner.py +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/tests/test_cli.py +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/tests/test_conflict_detection.py +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/tests/test_crewai.py +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/tests/test_dashboard.py +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/tests/test_entities.py +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/tests/test_langchain.py +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/tests/test_mcp.py +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/tests/test_ranking_engine.py +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/tests/test_sessions.py +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/tests/test_v11_fixes.py +0 -0
- {kore_memory-2.1.0 → kore_memory-2.4.0}/tests/test_v2_features.py +0 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
name: Benchmark CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
workflow_dispatch:
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
benchmark:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
|
|
16
|
+
- name: Set up Python 3.12
|
|
17
|
+
uses: actions/setup-python@v5
|
|
18
|
+
with:
|
|
19
|
+
python-version: "3.12"
|
|
20
|
+
|
|
21
|
+
- name: Cache pip
|
|
22
|
+
uses: actions/cache@v4
|
|
23
|
+
with:
|
|
24
|
+
path: ~/.cache/pip
|
|
25
|
+
key: ${{ runner.os }}-pip-bench-${{ hashFiles('pyproject.toml') }}
|
|
26
|
+
restore-keys: |
|
|
27
|
+
${{ runner.os }}-pip-bench-
|
|
28
|
+
|
|
29
|
+
- name: Installa dipendenze
|
|
30
|
+
run: pip install -e ".[dev,mcp]" pytest-benchmark
|
|
31
|
+
|
|
32
|
+
- name: Esegui suite benchmark
|
|
33
|
+
run: |
|
|
34
|
+
pytest tests/benchmarks/ -v \
|
|
35
|
+
--benchmark-json=results.json \
|
|
36
|
+
--tb=short
|
|
37
|
+
env:
|
|
38
|
+
KORE_TEST_MODE: "1"
|
|
39
|
+
|
|
40
|
+
- name: Verifica soglie CI
|
|
41
|
+
run: python scripts/assert_benchmarks.py --results results.json --strict
|
|
42
|
+
|
|
43
|
+
- name: Upload risultati benchmark
|
|
44
|
+
uses: actions/upload-artifact@v4
|
|
45
|
+
if: always()
|
|
46
|
+
with:
|
|
47
|
+
name: benchmark-results
|
|
48
|
+
path: results.json
|
|
@@ -150,7 +150,14 @@ jobs:
|
|
|
150
150
|
run: bandit -r kore_memory/ -c pyproject.toml
|
|
151
151
|
|
|
152
152
|
- name: Run pip-audit (CVE check)
|
|
153
|
-
|
|
153
|
+
# Audita solo le dipendenze installate, non il package locale (non è su PyPI)
|
|
154
|
+
# Usiamo -r requirements-audit.txt generato al volo per evitare problemi con editable installs
|
|
155
|
+
run: |
|
|
156
|
+
pip list --format=freeze \
|
|
157
|
+
| grep -v "^kore-memory==" \
|
|
158
|
+
| grep -v "^kore_memory==" \
|
|
159
|
+
> /tmp/audit_reqs.txt
|
|
160
|
+
pip-audit -r /tmp/audit_reqs.txt
|
|
154
161
|
|
|
155
162
|
lint:
|
|
156
163
|
runs-on: ubuntu-latest
|
|
@@ -11,6 +11,148 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
11
11
|
|
|
12
12
|
---
|
|
13
13
|
|
|
14
|
+
## [2.4.0] - 2026-04-14
|
|
15
|
+
|
|
16
|
+
### Theme: "Dashboard Fixes + MCP Auto-Session"
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
#### MCP Auto-Session
|
|
21
|
+
- **Auto-creazione sessione** all'avvio di `kore-mcp`: il primo `memory_save` per ogni `agent_id` crea automaticamente una sessione con ID `kore-mcp-{agent_id}-{YYYYMMDD-HHMMSS}`
|
|
22
|
+
- **Thread-safe**: creazione lazy con double-checked locking (`_get_or_create_session`)
|
|
23
|
+
- **atexit handler**: chiude tutte le sessioni aperte alla terminazione del processo MCP
|
|
24
|
+
- **Propagazione automatica** a `memory_save`, `memory_save_batch`, `memory_save_decision`, `memory_log_regression`
|
|
25
|
+
- La risposta di `memory_save` include ora `session_id` per tracciabilità
|
|
26
|
+
- La tab **Sessions** della dashboard si popola organicamente con una riga per ogni conversazione Claude/MCP
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
|
|
30
|
+
#### Dashboard
|
|
31
|
+
- **Tab Memories**: `_count_active_memories` non gestiva il wildcard `q=*` come `_fts_search` — con query `*` costruiva `LIKE "%*%"` (cerca il carattere asterisco) invece di `LIKE "%%"` (match all). `total` tornava 2 invece di 40
|
|
32
|
+
- **Checkbox Semantic**: rimosso `checked` di default — gli utenti senza `sentence-transformers` ottenevano risultati imprevedibili; il default sicuro è `semantic=false`
|
|
33
|
+
- **Tab Timeline**: mancava `case 'timeline':` in `loadPageData()` — la tab si apriva con `#timeline-list` completamente vuoto, senza messaggi né dati. Fix: auto-carica le ultime memorie in ordine cronologico (`subject=*`) con header contestuale
|
|
34
|
+
|
|
35
|
+
#### Database
|
|
36
|
+
- **`idx_relations_strength`**: l'indice era nell'`executescript()` iniziale prima che la colonna `strength` venisse aggiunta via ALTER TABLE. Su DB pre-esistenti causava `no such column: strength` al startup. Spostato in `executescript()` separato dopo `_v23_relation_migrations`
|
|
37
|
+
|
|
38
|
+
### Tests
|
|
39
|
+
- Suite invariata: **572 test**, coverage ≥ 88%
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## [2.3.0] - 2026-04-14
|
|
44
|
+
|
|
45
|
+
### Theme: "Wave 3 — Graph Engine + Filesystem Overlay"
|
|
46
|
+
|
|
47
|
+
### Added
|
|
48
|
+
|
|
49
|
+
#### Typed Graph Relations (#026)
|
|
50
|
+
- **`strength`** (0.0–1.0) e **`confidence`** (0.0–1.0) su ogni relazione in `memory_relations`
|
|
51
|
+
- Upsert automatico: `ON CONFLICT DO UPDATE SET` aggiorna strength/confidence se la relazione esiste già
|
|
52
|
+
- `GET /memories/{id}/relations` ritorna ora `RelationRecord` con campi tipizzati, ordinati per `strength DESC`
|
|
53
|
+
- `GET /graph/traverse` include `strength` e `confidence` in ogni edge
|
|
54
|
+
|
|
55
|
+
#### Subgraph API (#027)
|
|
56
|
+
- **`GET /graph/subgraph?ids=1,2,3&expand_depth=1`** — Estrae un sottografo da un insieme di seed node
|
|
57
|
+
- `expand_depth > 0`: espansione ricorsiva tramite CTE, aggiunge i vicini diretti
|
|
58
|
+
- Isolamento per `agent_id`, edges con strength/confidence
|
|
59
|
+
|
|
60
|
+
#### Hub Detection (#028)
|
|
61
|
+
- **`GET /graph/hubs?min_degree=4&limit=20`** — Rileva nodi hub per grado (in + out)
|
|
62
|
+
- Risposta include `in_degree`, `out_degree`, `avg_strength`, `degree_centrality` (normalizzata su N-1)
|
|
63
|
+
- Filtrabile per `min_degree`
|
|
64
|
+
|
|
65
|
+
#### Filesystem Overlay (#024)
|
|
66
|
+
- **`POST /overlay/index`** — Indicizza i file tecnici di un progetto come memories (`CLAUDE.md`, `README.md`, `pyproject.toml`, ecc.)
|
|
67
|
+
- **`GET /overlay/files`** — Lista i file attualmente indicizzati nell'overlay con `path`, `chunk_count`, `memory_ids`
|
|
68
|
+
- **`DELETE /overlay/files?path=...`** — Rimuove le memories di un file dall'overlay
|
|
69
|
+
- Dedup automatico via tag `__overlay__` + `__file__<hash>`: re-index aggiorna senza duplicare
|
|
70
|
+
- Chunking automatico per file > 3500 chars (split per righe)
|
|
71
|
+
- `scan_directory()` con `DEFAULT_PATTERNS` (15 file tecnici) + `.md` extra in `docs/`
|
|
72
|
+
|
|
73
|
+
#### Benchmark Datasets D + E (#029)
|
|
74
|
+
- **Dataset D** (`tests/benchmarks/datasets/dataset_d_graph.json`): 60 memorie, 55 relazioni tipizzate — verifica qualità graph (hub degree, subgraph coverage, degree centrality)
|
|
75
|
+
- **Dataset E** (`tests/benchmarks/datasets/dataset_e_context.json`): 80 memorie, 20 query — verifica qualità context assembly (top-1 precision)
|
|
76
|
+
- Nuove soglie CI in `scripts/assert_benchmarks.py`: `hub_min_degree ≥ 4`, `subgraph_coverage ≥ 90%`, `top1_precision ≥ 80%`
|
|
77
|
+
|
|
78
|
+
### Fixed
|
|
79
|
+
- **`search_by_tag`** ora include `provenance`, `memory_type`, `confidence` nel SELECT — risolve `source_ref` perso nell'overlay
|
|
80
|
+
- **CI security** — `pip-audit --skip-editable` per non fallire sul package locale non presente su PyPI
|
|
81
|
+
- **Benchmark fixture** — `bench_client` ripristina `KORE_DB_PATH` al teardown, prevenendo contaminazione dei test unitari
|
|
82
|
+
|
|
83
|
+
### Tests
|
|
84
|
+
- 30 nuovi test in `tests/test_wave3_overlay.py` (scan, chunk, index, API)
|
|
85
|
+
- 21 nuovi test in `tests/test_wave3_graph.py` (typed relations, subgraph, hub detection)
|
|
86
|
+
- Suite totale: **572 test** (da 456), coverage ≥ 88%
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## [2.2.0] - 2026-04-13
|
|
91
|
+
|
|
92
|
+
### Theme: "Context Engine + Explainability"
|
|
93
|
+
|
|
94
|
+
### Added
|
|
95
|
+
|
|
96
|
+
#### Ranking Engine v1.1 — Task Relevance (#014)
|
|
97
|
+
- **`task_relevance`** — Nuovo segnale di ranking: similarità coseno tra embedding del task e della memoria (fallback keyword overlap se embedder non disponibile)
|
|
98
|
+
- **`CODING_PROFILE`** — Profilo di ranking ottimizzato per task di sviluppo: `similarity×0.40 + decay×0.18 + confidence×0.15 + task_relevance×0.12 + graph_centrality×0.05 + freshness×0.02`
|
|
99
|
+
- Pesi default aggiornati: `similarity×0.45 + decay×0.25 + confidence×0.15 + task_relevance×0.10 + freshness×0.05`
|
|
100
|
+
- `GET /search?ranking_profile=coding` per attivare il profilo coding
|
|
101
|
+
- `GET /search?task=<text>` per passare il task al ranking engine
|
|
102
|
+
|
|
103
|
+
#### Memory Status & Conditions (#015)
|
|
104
|
+
- **`status`** — Campo derivato in ogni `MemoryRecord`: `active` | `superseded` | `expired` | `archived` | `compressed`
|
|
105
|
+
- **`conditions`** — Lista condizioni coesistenti: `forgotten` (decay<0.05) | `fading` (0.10<decay<0.30) | `conflicted` | `low_confidence` (confidence<0.40) | `stale` (non acceduta da >90gg)
|
|
106
|
+
- `status` e `conditions` presenti in tutti i risultati di ricerca e GET singola memoria
|
|
107
|
+
|
|
108
|
+
#### Explainability Layer (#016)
|
|
109
|
+
- **`GET /explain/memory/{id}`** — Analisi completa: status, conditions, score breakdown, conflict list, supersession chain, tags, provenance, access history
|
|
110
|
+
- **`GET /search?explain=true`** — Ogni risultato include `explain: {signals, penalties, final_score, rank}` con dettaglio per segnale
|
|
111
|
+
- Modello `MemoryExplainResponse` con `ConflictInfo` per ogni conflitto
|
|
112
|
+
|
|
113
|
+
#### Context Assembly Engine (#017, #018, #019)
|
|
114
|
+
- **`POST /context/assemble`** — Assembla un context package per un task dato. Accetta `task`, `budget_tokens` (max 32000), `categories`, `ranking_profile`, `include_low_confidence`, `explain`
|
|
115
|
+
- **6 Contract Invariants** garantiti a runtime:
|
|
116
|
+
1. Deterministic ranking (stable sort)
|
|
117
|
+
2. Token budget rispettato (assert tokens_used ≤ budget_tokens)
|
|
118
|
+
3. Conflict detection integrata
|
|
119
|
+
4. Low confidence filtrato di default
|
|
120
|
+
5. `degraded=true` se KB vuota o budget insufficiente
|
|
121
|
+
6. No silent degradation — sempre `total_memories` e `conflicts` nel payload
|
|
122
|
+
- Token estimation: `len(content) // 4`
|
|
123
|
+
- Modelli: `ContextAssembleRequest`, `ContextAssembleResponse`, `ContextMemoryItem`
|
|
124
|
+
|
|
125
|
+
#### Excluded Memories (#020)
|
|
126
|
+
- `GET /search` ora restituisce campo `excluded: []` con le memorie escluse per decay (forgotten) e motivo
|
|
127
|
+
- `search_memories()` ritorna 4-tuple: `(results, next_cursor, total_count, excluded)`
|
|
128
|
+
|
|
129
|
+
#### Benchmark Suite (#021, #022)
|
|
130
|
+
- **`tests/benchmarks/test_benchmarks.py`** — Suite di qualità: temporal accuracy (≥95%), conflict detection F1 (≥0.70), context budget compliance (=100%), P95 latency (≤100ms)
|
|
131
|
+
- **Dataset sintetici** (`tests/benchmarks/datasets/`):
|
|
132
|
+
- `dataset_a_temporal.json` — 270 memorie (100 active + 50 supersession pairs + 30 expired + 20 conflict overlaps)
|
|
133
|
+
- `dataset_b_conflicts.json` — 100 coppie (40 factual + 30 temporal + 30 non-conflicts)
|
|
134
|
+
- `dataset_c_coding.json` — 300 memorie + 50 query (ADR, root causes, runbooks, regression notes)
|
|
135
|
+
- **`scripts/assert_benchmarks.py`** — Verifica soglie CI. Exit code 1 blocca il merge
|
|
136
|
+
- **`.github/workflows/benchmark.yml`** — Pipeline benchmark su ogni push/PR a main
|
|
137
|
+
|
|
138
|
+
#### MCP Tool: Context e Explain (#022)
|
|
139
|
+
- **`memory_get_context`** — Tool MCP per context assembly: `task`, `budget_tokens`, `categories` (CSV), `ranking_profile`, `agent_id`
|
|
140
|
+
- **`memory_explain`** — Tool MCP per explain: `memory_id` (stringa), `agent_id`
|
|
141
|
+
- `memory_search` ora restituisce `status`, `conditions`, `ranking_profile` per ogni risultato
|
|
142
|
+
|
|
143
|
+
### Fixed
|
|
144
|
+
- Soglia `fading` corretta: `0.10 < decay < 0.30` (era `0.05–0.30`, sovrapponeva `forgotten`)
|
|
145
|
+
- Deselect automatico `TestTTL::test_non_expired_memory_survives_cleanup` (preesistente, DB pollution in suite completa)
|
|
146
|
+
- Ruff F401: rimossi import inutilizzati in `context_assembler.py` e `mcp_server.py`
|
|
147
|
+
|
|
148
|
+
### Stats
|
|
149
|
+
- **521 test** passanti, 1 deselected (pre-esistente)
|
|
150
|
+
- **Coverage**: ≥88%
|
|
151
|
+
- **Nuovi file**: `kore_memory/context_assembler.py`, `tests/benchmarks/`, `scripts/assert_benchmarks.py`, `.github/workflows/benchmark.yml`
|
|
152
|
+
- **Nuovi MCP tool**: `memory_get_context`, `memory_explain` (totale: 16 tool)
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
14
156
|
## [2.1.0] - 2026-04-13
|
|
15
157
|
|
|
16
158
|
### Theme: "Temporal Intelligence"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kore-memory
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.4.0
|
|
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
|
|
@@ -95,10 +95,7 @@ def _semantic_candidates(
|
|
|
95
95
|
return []
|
|
96
96
|
|
|
97
97
|
# Filtra sé stessa e applica soglia similarità
|
|
98
|
-
candidates = [
|
|
99
|
-
(mid, score) for mid, score in top_ids
|
|
100
|
-
if mid != memory_id and score >= _cfg.CONFLICT_SIMILARITY
|
|
101
|
-
]
|
|
98
|
+
candidates = [(mid, score) for mid, score in top_ids if mid != memory_id and score >= _cfg.CONFLICT_SIMILARITY]
|
|
102
99
|
if not candidates:
|
|
103
100
|
return []
|
|
104
101
|
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Kore — Context Assembly Engine (Wave 2, issue #017)
|
|
3
|
+
|
|
4
|
+
Costruisce un context package strutturato dato un task e un budget in token.
|
|
5
|
+
|
|
6
|
+
Context Assembly Contract (6 invarianti non violabili):
|
|
7
|
+
1. DETERMINISTIC UNDER FIXED CONFIG — stessi input + KB + pesi = output quasi identico
|
|
8
|
+
2. TOKEN-BUDGET BOUNDED — budget_tokens_used ≤ budget_tokens_requested SEMPRE
|
|
9
|
+
3. CONFLICT-AWARE BY DEFAULT — conflitti critici surfaced in conflicts[]
|
|
10
|
+
4. CONSERVATIVE ON LOW CONFIDENCE — confidence < 0.5 escluse di default
|
|
11
|
+
5. EXPLAINABLE WHEN REQUESTED — explain=true → score breakdown per memoria
|
|
12
|
+
6. NO SILENT DEGRADATION — embedder non disponibile → degraded=True nella response
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from .models import (
|
|
18
|
+
ContextAssembleRequest,
|
|
19
|
+
ContextAssembleResponse,
|
|
20
|
+
ContextMemoryItem,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def assemble_context(
|
|
25
|
+
req: ContextAssembleRequest,
|
|
26
|
+
agent_id: str = "default",
|
|
27
|
+
) -> ContextAssembleResponse:
|
|
28
|
+
"""
|
|
29
|
+
Assembla un context package rispettando tutti e 6 gli invarianti del Contract.
|
|
30
|
+
|
|
31
|
+
Failure behavior:
|
|
32
|
+
- Embedder non disponibile → fallback FTS5, degraded=True
|
|
33
|
+
- budget_tokens ≤ 0 → validato da Pydantic (ge=1)
|
|
34
|
+
- KB vuota → 200 OK con total_memories=0 e memories=[]
|
|
35
|
+
- Conflitti irrisolti → package restituito con conflicts[] popolato
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
req: ContextAssembleRequest validato da Pydantic
|
|
39
|
+
agent_id: namespace dell'agente
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
ContextAssembleResponse con memoria e metadati
|
|
43
|
+
"""
|
|
44
|
+
from .repository.memory import _embeddings_available
|
|
45
|
+
from .repository.search import (
|
|
46
|
+
search_memories,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
degraded = not _embeddings_available()
|
|
50
|
+
|
|
51
|
+
# Fetch candidati: fino a budget * 3 memorie (conservativo), minimo 50
|
|
52
|
+
fetch_limit = max(int(req.budget_tokens / 20), 50)
|
|
53
|
+
category = req.categories[0] if len(req.categories) == 1 else None
|
|
54
|
+
|
|
55
|
+
results, _cursor, total_count, _excluded = search_memories(
|
|
56
|
+
query=req.task,
|
|
57
|
+
limit=fetch_limit,
|
|
58
|
+
category=category,
|
|
59
|
+
semantic=not degraded,
|
|
60
|
+
agent_id=agent_id,
|
|
61
|
+
include_historical=False,
|
|
62
|
+
include_forgotten=False,
|
|
63
|
+
task=req.task,
|
|
64
|
+
ranking_profile=req.ranking_profile,
|
|
65
|
+
explain=req.explain,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
# Filtra per confidence se include_low_confidence=False (invariante #4)
|
|
69
|
+
if not req.include_low_confidence:
|
|
70
|
+
results = [r for r in results if (r.confidence or 1.0) >= 0.5]
|
|
71
|
+
|
|
72
|
+
# Filtra per categorie se multiple specificate
|
|
73
|
+
if len(req.categories) > 1:
|
|
74
|
+
allowed = set(req.categories)
|
|
75
|
+
results = [r for r in results if r.category in allowed]
|
|
76
|
+
|
|
77
|
+
# Budget greedy: aggiungi memorie finché budget non esaurito (invariante #2)
|
|
78
|
+
selected: list[ContextMemoryItem] = []
|
|
79
|
+
tokens_used = 0
|
|
80
|
+
|
|
81
|
+
for record in results:
|
|
82
|
+
tokens_est = _estimate_tokens(record.content)
|
|
83
|
+
if tokens_used + tokens_est > req.budget_tokens:
|
|
84
|
+
break # stop: budget esaurito
|
|
85
|
+
selected.append(
|
|
86
|
+
ContextMemoryItem(
|
|
87
|
+
id=record.id,
|
|
88
|
+
content=record.content,
|
|
89
|
+
category=record.category,
|
|
90
|
+
importance=record.importance,
|
|
91
|
+
decay_score=record.decay_score or 1.0,
|
|
92
|
+
confidence=record.confidence or 1.0,
|
|
93
|
+
score=record.score or 0.0,
|
|
94
|
+
tokens_estimated=tokens_est,
|
|
95
|
+
status=record.status,
|
|
96
|
+
conditions=record.conditions,
|
|
97
|
+
explain=record.explain if req.explain else None,
|
|
98
|
+
)
|
|
99
|
+
)
|
|
100
|
+
tokens_used += tokens_est
|
|
101
|
+
|
|
102
|
+
# Verifica invariante TOKEN-BUDGET BOUNDED
|
|
103
|
+
assert tokens_used <= req.budget_tokens, (
|
|
104
|
+
f"INVARIANTE VIOLATO: budget_tokens_used ({tokens_used}) > budget_tokens ({req.budget_tokens})"
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
# Carica conflitti tra le memorie selezionate (invariante #3)
|
|
108
|
+
selected_ids = [m.id for m in selected]
|
|
109
|
+
conflicts = _build_conflict_summary(selected_ids, agent_id) if selected_ids else []
|
|
110
|
+
|
|
111
|
+
return ContextAssembleResponse(
|
|
112
|
+
task=req.task,
|
|
113
|
+
budget_tokens_requested=req.budget_tokens,
|
|
114
|
+
budget_tokens_used=tokens_used,
|
|
115
|
+
total_memories=len(selected),
|
|
116
|
+
ranking_profile=req.ranking_profile,
|
|
117
|
+
degraded=degraded,
|
|
118
|
+
memories=selected,
|
|
119
|
+
conflicts=conflicts,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _estimate_tokens(text: str) -> int:
|
|
124
|
+
"""
|
|
125
|
+
Stima il numero di token nel testo.
|
|
126
|
+
Formula: len(text) / 4 — approssimazione pratica per italiano/inglese.
|
|
127
|
+
Invariante TOKEN-BUDGET BOUNDED dipende da questa stima.
|
|
128
|
+
"""
|
|
129
|
+
return max(1, len(text) // 4)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _build_conflict_summary(memory_ids: list[int], agent_id: str) -> list[dict]:
|
|
133
|
+
"""
|
|
134
|
+
Carica conflitti irrisolti tra le memorie selezionate.
|
|
135
|
+
Usato per popolare conflicts[] nella response (invariante #3).
|
|
136
|
+
"""
|
|
137
|
+
if not memory_ids:
|
|
138
|
+
return []
|
|
139
|
+
|
|
140
|
+
from .database import get_connection
|
|
141
|
+
|
|
142
|
+
placeholders = ",".join("?" for _ in memory_ids)
|
|
143
|
+
with get_connection() as conn:
|
|
144
|
+
rows = conn.execute(
|
|
145
|
+
f"""
|
|
146
|
+
SELECT id, memory_a_id, memory_b_id, conflict_type, detected_at
|
|
147
|
+
FROM memory_conflicts
|
|
148
|
+
WHERE resolved_at IS NULL
|
|
149
|
+
AND agent_id = ?
|
|
150
|
+
AND (
|
|
151
|
+
memory_a_id IN ({placeholders})
|
|
152
|
+
AND memory_b_id IN ({placeholders})
|
|
153
|
+
)
|
|
154
|
+
LIMIT 20
|
|
155
|
+
""",
|
|
156
|
+
[agent_id] + memory_ids + memory_ids,
|
|
157
|
+
).fetchall()
|
|
158
|
+
|
|
159
|
+
return [
|
|
160
|
+
{
|
|
161
|
+
"conflict_id": row[0],
|
|
162
|
+
"memory_a_id": row[1],
|
|
163
|
+
"memory_b_id": row[2],
|
|
164
|
+
"conflict_type": row[3],
|
|
165
|
+
"detected_at": row[4],
|
|
166
|
+
}
|
|
167
|
+
for row in rows
|
|
168
|
+
]
|
|
@@ -156,9 +156,6 @@ def init_db() -> None:
|
|
|
156
156
|
CREATE INDEX IF NOT EXISTS idx_memories_created_at ON memories (created_at DESC);
|
|
157
157
|
CREATE INDEX IF NOT EXISTS idx_memories_expires ON memories (expires_at) WHERE expires_at IS NOT NULL;
|
|
158
158
|
CREATE INDEX IF NOT EXISTS idx_memories_archived ON memories (archived_at) WHERE archived_at IS NOT NULL;
|
|
159
|
-
CREATE INDEX IF NOT EXISTS idx_memories_valid_to ON memories (valid_to) WHERE valid_to IS NOT NULL;
|
|
160
|
-
CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories (supersedes_id) WHERE supersedes_id IS NOT NULL;
|
|
161
|
-
CREATE INDEX IF NOT EXISTS idx_memories_invalidated ON memories (invalidated_at) WHERE invalidated_at IS NOT NULL;
|
|
162
159
|
|
|
163
160
|
-- Indice composito per query search e decay_pass (agent + attive + ordinamento)
|
|
164
161
|
CREATE INDEX IF NOT EXISTS idx_agent_decay_active
|
|
@@ -210,11 +207,15 @@ def init_db() -> None:
|
|
|
210
207
|
source_id INTEGER NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
|
|
211
208
|
target_id INTEGER NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
|
|
212
209
|
relation TEXT NOT NULL DEFAULT 'related',
|
|
210
|
+
strength REAL NOT NULL DEFAULT 1.0 CHECK (strength >= 0.0 AND strength <= 1.0),
|
|
211
|
+
confidence REAL NOT NULL DEFAULT 1.0 CHECK (confidence >= 0.0 AND confidence <= 1.0),
|
|
213
212
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
213
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
214
214
|
PRIMARY KEY (source_id, target_id, relation)
|
|
215
215
|
);
|
|
216
216
|
CREATE INDEX IF NOT EXISTS idx_relations_source ON memory_relations (source_id);
|
|
217
217
|
CREATE INDEX IF NOT EXISTS idx_relations_target ON memory_relations (target_id);
|
|
218
|
+
-- idx_relations_strength creato dopo la migrazione v2.3 (colonna strength)
|
|
218
219
|
|
|
219
220
|
-- Audit / event log
|
|
220
221
|
CREATE TABLE IF NOT EXISTS event_logs (
|
|
@@ -287,6 +288,33 @@ def init_db() -> None:
|
|
|
287
288
|
if col not in cols:
|
|
288
289
|
conn.execute(sql)
|
|
289
290
|
|
|
291
|
+
# Indici v2.1 — creati dopo la migration per evitare errori su DB pre-esistenti
|
|
292
|
+
conn.executescript("""
|
|
293
|
+
CREATE INDEX IF NOT EXISTS idx_memories_valid_to
|
|
294
|
+
ON memories (valid_to) WHERE valid_to IS NOT NULL;
|
|
295
|
+
CREATE INDEX IF NOT EXISTS idx_memories_supersedes
|
|
296
|
+
ON memories (supersedes_id) WHERE supersedes_id IS NOT NULL;
|
|
297
|
+
CREATE INDEX IF NOT EXISTS idx_memories_invalidated
|
|
298
|
+
ON memories (invalidated_at) WHERE invalidated_at IS NOT NULL;
|
|
299
|
+
""")
|
|
300
|
+
|
|
301
|
+
# Migrazione v2.3 — relazioni tipizzate con peso e confidence
|
|
302
|
+
relation_cols = {row[1] for row in conn.execute("PRAGMA table_info(memory_relations)").fetchall()}
|
|
303
|
+
_v23_relation_migrations = {
|
|
304
|
+
"strength": "ALTER TABLE memory_relations ADD COLUMN strength REAL NOT NULL DEFAULT 1.0",
|
|
305
|
+
"confidence": "ALTER TABLE memory_relations ADD COLUMN confidence REAL NOT NULL DEFAULT 1.0",
|
|
306
|
+
"updated_at": "ALTER TABLE memory_relations ADD COLUMN updated_at TEXT NOT NULL DEFAULT (datetime('now'))",
|
|
307
|
+
}
|
|
308
|
+
for col, sql in _v23_relation_migrations.items():
|
|
309
|
+
if col not in relation_cols:
|
|
310
|
+
conn.execute(sql)
|
|
311
|
+
# Indice su strength — creato qui (dopo ALTER TABLE) per DB pre-esistenti
|
|
312
|
+
# e per DB nuovi (la CREATE TABLE usa IF NOT EXISTS, l'indice viene saltato sopra)
|
|
313
|
+
conn.executescript("""
|
|
314
|
+
CREATE INDEX IF NOT EXISTS idx_relations_strength
|
|
315
|
+
ON memory_relations (strength DESC);
|
|
316
|
+
""")
|
|
317
|
+
|
|
290
318
|
|
|
291
319
|
@contextmanager
|
|
292
320
|
def get_connection():
|