luminary-memory 0.2.3__tar.gz → 0.2.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.
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/CHANGELOG.md +12 -0
- luminary_memory-0.2.4/PKG-INFO +176 -0
- luminary_memory-0.2.4/PLAN-v0.2.4.md +128 -0
- luminary_memory-0.2.4/README.md +142 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/docs/lifecycle.md +36 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/hermes/hooks/luminary-activity/handler.py +40 -27
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/hermes/install.sh +21 -15
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/pyproject.toml +1 -1
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/__init__.py +1 -1
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/api.py +103 -1
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/cli.py +26 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/ingest/llm.py +1 -1
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/hermes/test_config.py +26 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/hermes/test_hooks.py +28 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/hermes/test_lifecycle.py +33 -0
- luminary_memory-0.2.4/tests/hermes/test_session_boundaries.py +128 -0
- luminary_memory-0.2.4/tests/hermes/test_sync_turn.py +137 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/hermes/test_tools.py +34 -0
- luminary_memory-0.2.4/tests/test_api.py +184 -0
- luminary_memory-0.2.4/tests/test_api_extended.py +371 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/test_cli.py +49 -0
- luminary_memory-0.2.4/tests/test_ingest_llm.py +177 -0
- luminary_memory-0.2.3/PKG-INFO +0 -277
- luminary_memory-0.2.3/README.md +0 -243
- luminary_memory-0.2.3/tests/hermes/test_session_boundaries.py +0 -67
- luminary_memory-0.2.3/tests/hermes/test_sync_turn.py +0 -73
- luminary_memory-0.2.3/tests/test_api.py +0 -68
- luminary_memory-0.2.3/tests/test_api_extended.py +0 -83
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/.github/workflows/ci.yml +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/.github/workflows/pages.yml +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/.github/workflows/publish.yml +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/.gitignore +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/CODE_OF_CONDUCT.md +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/CONTRIBUTING.md +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/LICENSE +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/PLAN-v0.2.0.md +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/PLAN-v0.2.1.md +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/PLAN.md +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/ROADMAP.md +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/SECURITY.md +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/benchmarks/README.md +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/benchmarks/RESULTS.md +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/benchmarks/__init__.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/benchmarks/hermes_provider_bench.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/benchmarks/metrics.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/benchmarks/run_benchmarks.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/benchmarks/synthetic.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/docs/api.md +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/docs/architecture.md +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/docs/backends.md +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/docs/cli.md +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/docs/hermes-integration.md +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/docs/index.md +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/docs/quickstart.md +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/docs/recall.md +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/hermes/README.md +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/hermes/SKILL.md +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/hermes/hooks/luminary-activity/HOOK.yaml +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/hermes/hooks/luminary-activity/README.md +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/backends/__init__.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/backends/base.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/backends/pgvector.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/backends/sqlite.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/budget.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/config.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/embeddings/__init__.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/embeddings/fastembed.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/export.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/hermes/__init__.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/hermes/config.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/hermes/config_schema.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/hermes/plugin.yaml +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/hermes/provider.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/ingest/__init__.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/ingest/whitelist.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/lifecycle/__init__.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/lifecycle/cleanup.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/lifecycle/consolidate.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/lifecycle/prune.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/lifecycle/runner.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/recall/__init__.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/recall/dedup.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/recall/fusion.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/recall/graph.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/recall/keyword.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/recall/planner.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/recall/semantic.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/recall/snippets.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/recall/temporal.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/schema.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/types.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/__init__.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/conftest.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/hermes/test_backup_paths.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/hermes/test_entrypoint.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/hermes/test_provider_import.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/hermes/test_recall.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/hermes/test_system_prompt.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/hermes_stubs/agent/memory_provider.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/test_backend_pgvector.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/test_backend_sqlite.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/test_budget.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/test_dedup_jaccard.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/test_embeddings.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/test_fusion_rrf.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/test_ingest_whitelist.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/test_lifecycle.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/test_recall_graph.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/test_recall_keyword.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/test_recall_orchestrator.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/test_recall_semantic.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/test_recall_temporal.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/test_schema.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/test_t10_export.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/test_t11_limit_zero.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/test_t1_hnsw.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/test_t3_planner.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/test_t4_batch.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/test_t5_enricher.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/test_t6_tag_scoped.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/test_t7_updated_at.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/test_t8_snippets.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/test_t9_cli_json.py +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/website/.nojekyll +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/website/css/luminary.css +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/website/index.html +0 -0
- {luminary_memory-0.2.3 → luminary_memory-0.2.4}/website/js/luminary.js +0 -0
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project are documented in this file. The format follows [Keep a Changelog](https://keepachangelog.com/), and the project adheres to [Semantic Versioning](https://semver.org/).
|
|
4
4
|
|
|
5
|
+
## [0.2.4] - 2026-08-18
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **`MemoryClient.health_score()`** — store health report (0-100) across five dimensions: duplicate rate, staleness, importance, graph density, and size — plus actionable recommendations. Empty store scores 100.
|
|
10
|
+
- **`luminary-memory health` CLI** — human-readable bar + `--json` output.
|
|
11
|
+
- **Test coverage raised 82% → 90%** (roadmap DoD): config_schema standalone shim, enricher `_call_llm`/`review_memories` paths, maintenance CRUD edges, provider lifecycle/maintenance/shutdown, CLI whitelist/limit errors, recall strategy failure fallbacks.
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- **`run_maintenance` bad-response detection** — `data.get("actions") or []` masked non-list responses as "no actions"; now correctly reports `{"error": "bad LLM response"}`.
|
|
16
|
+
|
|
5
17
|
## [0.2.3] - 2026-08-18
|
|
6
18
|
|
|
7
19
|
### Added
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: luminary-memory
|
|
3
|
+
Version: 0.2.4
|
|
4
|
+
Summary: A lightweight, self-hosted memory layer for AI agents
|
|
5
|
+
Project-URL: Homepage, https://github.com/alertxsto/luminary-memory
|
|
6
|
+
Project-URL: Repository, https://github.com/alertxsto/luminary-memory
|
|
7
|
+
Project-URL: Documentation, https://github.com/alertxsto/luminary-memory#readme
|
|
8
|
+
Author: Dwiky Candra
|
|
9
|
+
License: Apache-2.0
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
19
|
+
Requires-Python: >=3.11
|
|
20
|
+
Requires-Dist: fastembed>=0.4.0
|
|
21
|
+
Requires-Dist: numpy>=1.26
|
|
22
|
+
Requires-Dist: pgvector>=0.2.5
|
|
23
|
+
Requires-Dist: psycopg[binary]>=3.1
|
|
24
|
+
Requires-Dist: rich>=13.0
|
|
25
|
+
Requires-Dist: typer>=0.12
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: build>=1.0; extra == 'dev'
|
|
28
|
+
Requires-Dist: pdoc>=14; extra == 'dev'
|
|
29
|
+
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
|
|
30
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
31
|
+
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
32
|
+
Provides-Extra: hermes
|
|
33
|
+
Description-Content-Type: text/markdown
|
|
34
|
+
|
|
35
|
+
# 🌙 luminary-memory
|
|
36
|
+
|
|
37
|
+
**A lightweight, self-hosted memory layer for AI agents.**
|
|
38
|
+
|
|
39
|
+
[](https://pypi.org/project/luminary-memory)
|
|
40
|
+
[](https://pypi.org/project/luminary-memory)
|
|
41
|
+
[](LICENSE)
|
|
42
|
+
[](https://github.com/alertxsto/luminary-memory/actions)
|
|
43
|
+
[](https://github.com/alertxsto/luminary-memory/actions)
|
|
44
|
+
[](https://github.com/alertxsto/luminary-memory)
|
|
45
|
+
[](https://github.com/alertxsto/luminary-memory)
|
|
46
|
+
|
|
47
|
+
**Self-hosted · Private · Budget-aware · Self-maintaining**
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## What your agent remembers is what it becomes.
|
|
52
|
+
|
|
53
|
+
Agents are only as good as what they remember. A stateless agent re-learns the same context every session — paying the same tokens, making the same mistakes. luminary-memory closes that gap with a local memory store that persists between runs, retrieves the right context on demand, and keeps itself tidy over time.
|
|
54
|
+
|
|
55
|
+
**Four retrieval strategies. One fused answer. Zero cloud.**
|
|
56
|
+
|
|
57
|
+
- **Semantic** — ONNX embeddings (384-dim, CPU, no GPU needed)
|
|
58
|
+
- **Keyword** — FTS5 BM25 (SQLite, zero config)
|
|
59
|
+
- **Temporal** — recency decay × access count
|
|
60
|
+
- **Graph** — entity co-occurrence with automatic curation
|
|
61
|
+
|
|
62
|
+
Strategies run in parallel and fuse via **Reciprocal Rank Fusion (k=60)** → **Jaccard deduplication (0.85)** → **token budget (4096)**.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Quickstart
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
pip install luminary-memory
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
from luminary_memory import MemoryClient
|
|
74
|
+
|
|
75
|
+
client = MemoryClient(db_path="memory.db")
|
|
76
|
+
|
|
77
|
+
# store a durable fact
|
|
78
|
+
client.ingest("The deploy target is the staging cluster", tags=["deploy", "infra"])
|
|
79
|
+
|
|
80
|
+
# recall — four strategies fused into one ranked answer
|
|
81
|
+
result = client.recall("where do we deploy?")
|
|
82
|
+
for memory, score in zip(result.memories, result.scores):
|
|
83
|
+
print(f"{score:.3f} {memory.content}")
|
|
84
|
+
# → 0.942 The deploy target is the staging cluster
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
# CLI
|
|
89
|
+
luminary-memory add "deploy target is staging" --tags deploy
|
|
90
|
+
luminary-memory recall "where do we deploy?" --json
|
|
91
|
+
luminary-memory list
|
|
92
|
+
luminary-memory lifecycle
|
|
93
|
+
luminary-memory stats
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Hermes Agent — first-class memory provider
|
|
99
|
+
|
|
100
|
+
Drop-in. Install the provider with `pip install "luminary-memory[hermes]"`, then add
|
|
101
|
+
`memory.provider: luminary` to your Hermes config. That's it.
|
|
102
|
+
|
|
103
|
+
From the next session: **auto-recall** injects relevant memories every turn,
|
|
104
|
+
**auto-save** persists completed turns, and the model can call
|
|
105
|
+
`luminary_recall` / `luminary_ingest` / `luminary_list` on demand.
|
|
106
|
+
|
|
107
|
+
Two optional LLM-powered features keep the store sharp:
|
|
108
|
+
|
|
109
|
+
- **`ingest_llm`** — evaluates every turn before saving: drops chit-chat, stores factual summaries instead of raw transcripts.
|
|
110
|
+
- **`auto_maintain`** — reviews the store at session end: keeps current facts, updates changed ones, deletes stale or duplicate ones.
|
|
111
|
+
|
|
112
|
+
18 settings are exposed in the [Hermes dashboard](https://alertxsto.github.io/luminary-memory) for zero-hassle tuning. See
|
|
113
|
+
[hermes/README.md](hermes/README.md) for the one-shot installer and full configuration.
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## Configuration
|
|
118
|
+
|
|
119
|
+
Every setting has a `LUMINARY_*` env var or a `Settings` object.
|
|
120
|
+
|
|
121
|
+
| Setting | Env var | Default |
|
|
122
|
+
|---------|---------|---------|
|
|
123
|
+
| `backend` | `LUMINARY_BACKEND` | `sqlite` |
|
|
124
|
+
| `db_path` | `LUMINARY_DB_PATH` | `luminary_memory.db` |
|
|
125
|
+
| `pg_dsn` | `LUMINARY_PG_DSN` | — (pgvector only) |
|
|
126
|
+
| `embedding_model` | `LUMINARY_EMBEDDING_MODEL` | `BAAI/bge-small-en-v1.5` |
|
|
127
|
+
| `embedding_dim` | `LUMINARY_EMBEDDING_DIM` | `384` |
|
|
128
|
+
| `ingest_llm` | `LUMINARY_INGEST_LLM` | `false` |
|
|
129
|
+
| `rrf_k` | `LUMINARY_RRF_K` | `60` |
|
|
130
|
+
| `dedup_jaccard_threshold` | `LUMINARY_DEDUP_JACCARD_THRESHOLD` | `0.85` |
|
|
131
|
+
| `token_budget` | `LUMINARY_TOKEN_BUDGET` | `4096` |
|
|
132
|
+
| `ttl_default_seconds` | `LUMINARY_TTL_DEFAULT_SECONDS` | `null` |
|
|
133
|
+
| `prune_min_importance` | `LUMINARY_PRUNE_MIN_IMPORTANCE` | `0.2` |
|
|
134
|
+
| `consolidate_jaccard_threshold` | `LUMINARY_CONSOLIDATE_JACCARD_THRESHOLD` | `0.9` |
|
|
135
|
+
|
|
136
|
+
See [hermes/SKILL.md](hermes/SKILL.md) for the full provider config table (18 settings).
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Architecture
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
ingest(text) ──► whitelist filter ──► (LLM curation) ──► embed ──► SQLite / pgvector
|
|
144
|
+
|
|
145
|
+
recall(query) ──► semantic │ keyword │ temporal │ graph
|
|
146
|
+
──► RRF fusion ──► Jaccard dedup ──► token budget ──► ranked results
|
|
147
|
+
|
|
148
|
+
lifecycle() ──► cleanup (TTL) ──► consolidate (near-dupes) ──► prune (low-value)
|
|
149
|
+
|
|
150
|
+
maintenance() ──► LLM reviews store ──► keep │ update │ delete stale facts
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Built-in lifecycle keeps the store lean. LLM maintenance (optional) keeps it accurate. `health_score()` gives you a 0-100 checkup with actionable recommendations.
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## Documentation
|
|
158
|
+
|
|
159
|
+
| Section | |
|
|
160
|
+
|---------|-----|
|
|
161
|
+
| [Quickstart](docs/quickstart.md) | Install and first use |
|
|
162
|
+
| [Architecture](docs/architecture.md) | Pipelines and data flow |
|
|
163
|
+
| [Python API](docs/api.md) | `MemoryClient` reference |
|
|
164
|
+
| [CLI](docs/cli.md) | All subcommands |
|
|
165
|
+
| [Recall](docs/recall.md) | Four strategies + fusion |
|
|
166
|
+
| [Lifecycle](docs/lifecycle.md) | Cleanup, consolidation, pruning, LLM maintenance |
|
|
167
|
+
| [Backends](docs/backends.md) | SQLite vs pgvector |
|
|
168
|
+
| [Hermes integration](docs/hermes-integration.md) | Provider, config, installer |
|
|
169
|
+
| [Roadmap](ROADMAP.md) | v0.2.3 → v1.0.0 |
|
|
170
|
+
| [Benchmarks](benchmarks/RESULTS.md) | 230 ms recall @ 5k, 0 LLM tokens |
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## License
|
|
175
|
+
|
|
176
|
+
[Apache-2.0](LICENSE) © 2026 Dwiky Candra
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# PLAN v0.2.4 — Coverage 90% + Health Score
|
|
2
|
+
|
|
3
|
+
**Version:** 0.2.4 · **Milestone:** Quality hardening + health introspection
|
|
4
|
+
**Goal:** Raise test coverage from 82% → ≥90% (roadmap DoD) and ship
|
|
5
|
+
`MemoryClient.health_score()` for store quality monitoring.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Phase 1 — Coverage: test the untested (T1–T5)
|
|
10
|
+
|
|
11
|
+
Targets identified by `pytest --cov --cov-report=term-missing`:
|
|
12
|
+
|
|
13
|
+
| Module | Current | Target | Missing lines to cover |
|
|
14
|
+
|--------|---------|--------|------------------------|
|
|
15
|
+
| `hermes/config_schema.py` | 11% | ≥90% | Standalone fallback shim (no hermes runtime) |
|
|
16
|
+
| `ingest/llm.py` | 68% | ≥90% | `review_memories`, `_call_llm` error/empty paths, enrich failures |
|
|
17
|
+
| `api.py` | 75% | ≥90% | `run_maintenance` actions, `delete`/`update` edges, error handling |
|
|
18
|
+
| `hermes/provider.py` | 81% | ≥90% | maintenance trigger, shutdown, prefetch guards, error paths |
|
|
19
|
+
|
|
20
|
+
### T1 — config_schema standalone coverage
|
|
21
|
+
- Test: import `config_schema.py` **without** hermes-agent runtime (simulate CI)
|
|
22
|
+
- Assert `CONFIG_SCHEMA` shim exposes `name`, `label`, `storage`, 18 fields
|
|
23
|
+
- Assert `auto_maintain` and `ingest_llm` fields present
|
|
24
|
+
|
|
25
|
+
### T2 — llm.py enricher paths
|
|
26
|
+
- Test `OpenAICompatibleEnricher.review_memories` with mocked `_call_llm`
|
|
27
|
+
- returns valid actions JSON → parsed
|
|
28
|
+
- returns `{}` → `{"skipped": ...}` handled by caller
|
|
29
|
+
- raises (network) → returns `{}` (best-effort, no crash)
|
|
30
|
+
- Test `_call_llm` with mocked `urlopen`:
|
|
31
|
+
- 200 with choices → content returned
|
|
32
|
+
- 200 empty choices → `""`
|
|
33
|
+
- HTTPError → propagates to caller fallback
|
|
34
|
+
- Test `enrich()` fallback when `base_url` empty → `EnrichedContent(content=text)`
|
|
35
|
+
|
|
36
|
+
### T3 — api.py maintenance + CRUD edges
|
|
37
|
+
- Test `run_maintenance`:
|
|
38
|
+
- no enricher → `{"skipped": ...}`
|
|
39
|
+
- empty store → `{"reviewed": 0, ...}`
|
|
40
|
+
- enricher returns delete/update/keep actions → applied correctly
|
|
41
|
+
- enricher returns malformed actions (not a list) → `{"error": "bad LLM response"}`
|
|
42
|
+
- enricher returns action for unknown id → skipped
|
|
43
|
+
- Test `update()` with changed content → `updated_at` bumped, embedding recomputed
|
|
44
|
+
- Test `delete()` on non-existent id → graceful (no raise)
|
|
45
|
+
|
|
46
|
+
### T4 — provider.py lifecycle paths
|
|
47
|
+
- Test `on_session_end` with `auto_maintain=true` + `ingest_llm=true` → `run_maintenance` called, logged
|
|
48
|
+
- Test `on_session_end` with maintenance raising → exception logged, not propagated
|
|
49
|
+
- Test `shutdown` writer-thread close (regression: no SQLite thread-affinity crash)
|
|
50
|
+
- Test prefetch guards: `auto_recall=false`, `mode=tools`, shutdown flag → no prefetch
|
|
51
|
+
|
|
52
|
+
### T5 — coverage gate
|
|
53
|
+
- Add `--cov-fail-under=90` to CI workflow + `Makefile`/`pyproject` pytest config
|
|
54
|
+
- Verify `pytest -q --cov=luminary_memory` ≥ 90% locally
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Phase 2 — Health score (T6–T8)
|
|
59
|
+
|
|
60
|
+
### T6 — `health_score()` API in `api.py`
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
def health_score(self) -> dict:
|
|
64
|
+
"""Store health report: overall 0-100 + per-dimension breakdown."""
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Dimensions (computed from existing store data — no new schema):
|
|
68
|
+
|
|
69
|
+
| Dimension | Weight | Signal |
|
|
70
|
+
|-----------|--------|--------|
|
|
71
|
+
| `duplicate_rate` | 25% | % memories with a near-dupe (Jaccard > 0.85) — lower better |
|
|
72
|
+
| `staleness` | 25% | % memories not accessed in 30d (decayed) — lower better |
|
|
73
|
+
| `importance` | 20% | % memories above `prune_min_importance` — higher better |
|
|
74
|
+
| `density` | 15% | memories with graph relations / total — higher better |
|
|
75
|
+
| `size` | 15% | store size vs `token_budget` scale (0 = empty, 100 = healthy volume) |
|
|
76
|
+
|
|
77
|
+
Score = weighted sum. Returns:
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
{
|
|
81
|
+
"score": 87.5,
|
|
82
|
+
"dimensions": {
|
|
83
|
+
"duplicate_rate": {"value": 0.02, "weight": 0.25, "health": 98.0},
|
|
84
|
+
"staleness": {...}, "importance": {...}, "density": {...}, "size": {...}
|
|
85
|
+
},
|
|
86
|
+
"recommendations": ["2 stale memories older than 30d — run lifecycle prune"]
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
`recommendations` — generated from low-scoring dimensions (≤70):
|
|
91
|
+
- duplicates → "run lifecycle consolidate"
|
|
92
|
+
- stale → "run lifecycle prune or LLM maintenance"
|
|
93
|
+
- low importance → "raise prune_min_importance or review store"
|
|
94
|
+
|
|
95
|
+
### T7 — CLI `health` subcommand
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
luminary-memory health --json
|
|
99
|
+
luminary-memory health
|
|
100
|
+
# 📊 Memory Health: 87.5/100
|
|
101
|
+
# • Duplicates: 2.0% ✅
|
|
102
|
+
# • Stale (>30d): 4.0% ✅
|
|
103
|
+
# • Importance: 92% ✅
|
|
104
|
+
# • Graph density: 33% ⚠️
|
|
105
|
+
# • Size: 60% ✅
|
|
106
|
+
# → 2 stale memories — run `luminary-memory lifecycle`
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### T8 — health score tests + docs
|
|
110
|
+
|
|
111
|
+
- Test: empty store → score 0? or 100? (decide: empty store = 100 "nothing wrong" — document)
|
|
112
|
+
- Test: all duplicates → low duplicate_rate dimension
|
|
113
|
+
- Test: all stale → low staleness dimension
|
|
114
|
+
- Test: healthy mixed store → score ≥ 80
|
|
115
|
+
- Test CLI `health` output shape (`--json` parseable)
|
|
116
|
+
- Docs: `docs/lifecycle.md` + README (health section), CHANGELOG entry
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Definition of Done (v0.2.4)
|
|
121
|
+
|
|
122
|
+
- [ ] `pytest -q` green (≥90% coverage, CI gate)
|
|
123
|
+
- [ ] `ruff check src tests` clean
|
|
124
|
+
- [ ] CLI smoke: `luminary-memory health` + `--json`
|
|
125
|
+
- [ ] Health score verified against real store (`~/.hermes/luminary/memory.db`)
|
|
126
|
+
- [ ] CHANGELOG 0.2.4 entry
|
|
127
|
+
- [ ] Version bumped to 0.2.4
|
|
128
|
+
- [ ] Tag + GitHub release + PyPI upload (auto-publish)
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# 🌙 luminary-memory
|
|
2
|
+
|
|
3
|
+
**A lightweight, self-hosted memory layer for AI agents.**
|
|
4
|
+
|
|
5
|
+
[](https://pypi.org/project/luminary-memory)
|
|
6
|
+
[](https://pypi.org/project/luminary-memory)
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
[](https://github.com/alertxsto/luminary-memory/actions)
|
|
9
|
+
[](https://github.com/alertxsto/luminary-memory/actions)
|
|
10
|
+
[](https://github.com/alertxsto/luminary-memory)
|
|
11
|
+
[](https://github.com/alertxsto/luminary-memory)
|
|
12
|
+
|
|
13
|
+
**Self-hosted · Private · Budget-aware · Self-maintaining**
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## What your agent remembers is what it becomes.
|
|
18
|
+
|
|
19
|
+
Agents are only as good as what they remember. A stateless agent re-learns the same context every session — paying the same tokens, making the same mistakes. luminary-memory closes that gap with a local memory store that persists between runs, retrieves the right context on demand, and keeps itself tidy over time.
|
|
20
|
+
|
|
21
|
+
**Four retrieval strategies. One fused answer. Zero cloud.**
|
|
22
|
+
|
|
23
|
+
- **Semantic** — ONNX embeddings (384-dim, CPU, no GPU needed)
|
|
24
|
+
- **Keyword** — FTS5 BM25 (SQLite, zero config)
|
|
25
|
+
- **Temporal** — recency decay × access count
|
|
26
|
+
- **Graph** — entity co-occurrence with automatic curation
|
|
27
|
+
|
|
28
|
+
Strategies run in parallel and fuse via **Reciprocal Rank Fusion (k=60)** → **Jaccard deduplication (0.85)** → **token budget (4096)**.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Quickstart
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install luminary-memory
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
from luminary_memory import MemoryClient
|
|
40
|
+
|
|
41
|
+
client = MemoryClient(db_path="memory.db")
|
|
42
|
+
|
|
43
|
+
# store a durable fact
|
|
44
|
+
client.ingest("The deploy target is the staging cluster", tags=["deploy", "infra"])
|
|
45
|
+
|
|
46
|
+
# recall — four strategies fused into one ranked answer
|
|
47
|
+
result = client.recall("where do we deploy?")
|
|
48
|
+
for memory, score in zip(result.memories, result.scores):
|
|
49
|
+
print(f"{score:.3f} {memory.content}")
|
|
50
|
+
# → 0.942 The deploy target is the staging cluster
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
# CLI
|
|
55
|
+
luminary-memory add "deploy target is staging" --tags deploy
|
|
56
|
+
luminary-memory recall "where do we deploy?" --json
|
|
57
|
+
luminary-memory list
|
|
58
|
+
luminary-memory lifecycle
|
|
59
|
+
luminary-memory stats
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Hermes Agent — first-class memory provider
|
|
65
|
+
|
|
66
|
+
Drop-in. Install the provider with `pip install "luminary-memory[hermes]"`, then add
|
|
67
|
+
`memory.provider: luminary` to your Hermes config. That's it.
|
|
68
|
+
|
|
69
|
+
From the next session: **auto-recall** injects relevant memories every turn,
|
|
70
|
+
**auto-save** persists completed turns, and the model can call
|
|
71
|
+
`luminary_recall` / `luminary_ingest` / `luminary_list` on demand.
|
|
72
|
+
|
|
73
|
+
Two optional LLM-powered features keep the store sharp:
|
|
74
|
+
|
|
75
|
+
- **`ingest_llm`** — evaluates every turn before saving: drops chit-chat, stores factual summaries instead of raw transcripts.
|
|
76
|
+
- **`auto_maintain`** — reviews the store at session end: keeps current facts, updates changed ones, deletes stale or duplicate ones.
|
|
77
|
+
|
|
78
|
+
18 settings are exposed in the [Hermes dashboard](https://alertxsto.github.io/luminary-memory) for zero-hassle tuning. See
|
|
79
|
+
[hermes/README.md](hermes/README.md) for the one-shot installer and full configuration.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Configuration
|
|
84
|
+
|
|
85
|
+
Every setting has a `LUMINARY_*` env var or a `Settings` object.
|
|
86
|
+
|
|
87
|
+
| Setting | Env var | Default |
|
|
88
|
+
|---------|---------|---------|
|
|
89
|
+
| `backend` | `LUMINARY_BACKEND` | `sqlite` |
|
|
90
|
+
| `db_path` | `LUMINARY_DB_PATH` | `luminary_memory.db` |
|
|
91
|
+
| `pg_dsn` | `LUMINARY_PG_DSN` | — (pgvector only) |
|
|
92
|
+
| `embedding_model` | `LUMINARY_EMBEDDING_MODEL` | `BAAI/bge-small-en-v1.5` |
|
|
93
|
+
| `embedding_dim` | `LUMINARY_EMBEDDING_DIM` | `384` |
|
|
94
|
+
| `ingest_llm` | `LUMINARY_INGEST_LLM` | `false` |
|
|
95
|
+
| `rrf_k` | `LUMINARY_RRF_K` | `60` |
|
|
96
|
+
| `dedup_jaccard_threshold` | `LUMINARY_DEDUP_JACCARD_THRESHOLD` | `0.85` |
|
|
97
|
+
| `token_budget` | `LUMINARY_TOKEN_BUDGET` | `4096` |
|
|
98
|
+
| `ttl_default_seconds` | `LUMINARY_TTL_DEFAULT_SECONDS` | `null` |
|
|
99
|
+
| `prune_min_importance` | `LUMINARY_PRUNE_MIN_IMPORTANCE` | `0.2` |
|
|
100
|
+
| `consolidate_jaccard_threshold` | `LUMINARY_CONSOLIDATE_JACCARD_THRESHOLD` | `0.9` |
|
|
101
|
+
|
|
102
|
+
See [hermes/SKILL.md](hermes/SKILL.md) for the full provider config table (18 settings).
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Architecture
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
ingest(text) ──► whitelist filter ──► (LLM curation) ──► embed ──► SQLite / pgvector
|
|
110
|
+
|
|
111
|
+
recall(query) ──► semantic │ keyword │ temporal │ graph
|
|
112
|
+
──► RRF fusion ──► Jaccard dedup ──► token budget ──► ranked results
|
|
113
|
+
|
|
114
|
+
lifecycle() ──► cleanup (TTL) ──► consolidate (near-dupes) ──► prune (low-value)
|
|
115
|
+
|
|
116
|
+
maintenance() ──► LLM reviews store ──► keep │ update │ delete stale facts
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Built-in lifecycle keeps the store lean. LLM maintenance (optional) keeps it accurate. `health_score()` gives you a 0-100 checkup with actionable recommendations.
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## Documentation
|
|
124
|
+
|
|
125
|
+
| Section | |
|
|
126
|
+
|---------|-----|
|
|
127
|
+
| [Quickstart](docs/quickstart.md) | Install and first use |
|
|
128
|
+
| [Architecture](docs/architecture.md) | Pipelines and data flow |
|
|
129
|
+
| [Python API](docs/api.md) | `MemoryClient` reference |
|
|
130
|
+
| [CLI](docs/cli.md) | All subcommands |
|
|
131
|
+
| [Recall](docs/recall.md) | Four strategies + fusion |
|
|
132
|
+
| [Lifecycle](docs/lifecycle.md) | Cleanup, consolidation, pruning, LLM maintenance |
|
|
133
|
+
| [Backends](docs/backends.md) | SQLite vs pgvector |
|
|
134
|
+
| [Hermes integration](docs/hermes-integration.md) | Provider, config, installer |
|
|
135
|
+
| [Roadmap](ROADMAP.md) | v0.2.3 → v1.0.0 |
|
|
136
|
+
| [Benchmarks](benchmarks/RESULTS.md) | 230 ms recall @ 5k, 0 LLM tokens |
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## License
|
|
141
|
+
|
|
142
|
+
[Apache-2.0](LICENSE) © 2026 Dwiky Candra
|
|
@@ -55,6 +55,42 @@ every session end:
|
|
|
55
55
|
Results are recorded in the transparency log
|
|
56
56
|
(`~/.hermes/luminary/luminary.log`): `maintenance {'reviewed': N, 'deleted': N, 'updated': N}`.
|
|
57
57
|
|
|
58
|
+
## `health_score()` — store health report (v0.2.4+)
|
|
59
|
+
|
|
60
|
+
Returns a 0-100 health score with a per-dimension breakdown, computed from
|
|
61
|
+
existing store data (no new schema):
|
|
62
|
+
|
|
63
|
+
| Dimension | Weight | What it measures |
|
|
64
|
+
|-----------|--------|------------------|
|
|
65
|
+
| `duplicate_rate` | 25% | Share of memories with a near-duplicate (Jaccard > threshold) |
|
|
66
|
+
| `staleness` | 25% | Share of memories not accessed in 30 days |
|
|
67
|
+
| `importance` | 20% | Share of memories above `prune_min_importance` |
|
|
68
|
+
| `density` | 15% | Share of memories with graph relations |
|
|
69
|
+
| `size` | 15% | Store volume (scales toward 100 at ~1k memories) |
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
report = client.health_score()
|
|
73
|
+
print(report["score"]) # 87.5
|
|
74
|
+
print(report["recommendations"]) # actionable hints
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
CLI:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
luminary-memory health # human-readable bar
|
|
81
|
+
luminary-memory health --json # raw JSON
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
📊 Memory Health: 87.5/100 █████████░
|
|
86
|
+
✅ duplicate_rate: 98%
|
|
87
|
+
⚠️ density: 33%
|
|
88
|
+
→ 2 stale memories (>30d) — run lifecycle prune or LLM maintenance
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Empty store scores 100 (nothing wrong); low-scoring dimensions produce
|
|
92
|
+
recommendations.
|
|
93
|
+
|
|
58
94
|
## Scheduling
|
|
59
95
|
|
|
60
96
|
Run via cron for a self-maintaining store:
|
|
@@ -27,6 +27,16 @@ DB_PATH = os.getenv(
|
|
|
27
27
|
)
|
|
28
28
|
LOG_FILE = Path.home() / ".hermes" / "hooks" / "luminary-activity" / "hook.log"
|
|
29
29
|
LOG_FILE.parent.mkdir(parents=True, exist_ok=True)
|
|
30
|
+
STATE_FILE = LOG_FILE.parent / "state.json"
|
|
31
|
+
|
|
32
|
+
def _last_shown_id() -> int:
|
|
33
|
+
try:
|
|
34
|
+
return int(json.loads(STATE_FILE.read_text())["last_id"])
|
|
35
|
+
except Exception:
|
|
36
|
+
return 0
|
|
37
|
+
|
|
38
|
+
def _set_last_shown_id(mid: int) -> None:
|
|
39
|
+
STATE_FILE.write_text(json.dumps({"last_id": mid}))
|
|
30
40
|
logging.basicConfig(
|
|
31
41
|
filename=str(LOG_FILE), level=logging.INFO,
|
|
32
42
|
format="%(asctime)s %(levelname)s %(message)s",
|
|
@@ -53,40 +63,43 @@ def _post(text: str) -> None:
|
|
|
53
63
|
|
|
54
64
|
|
|
55
65
|
def _recent_activity(seconds: int = 30) -> str | None:
|
|
56
|
-
"""Return a
|
|
66
|
+
"""Return a status line for *new* memories since last post (no repeats)."""
|
|
57
67
|
if not Path(DB_PATH).exists():
|
|
58
68
|
return None
|
|
59
69
|
try:
|
|
60
70
|
conn = sqlite3.connect(f"file:{DB_PATH}?mode=ro", uri=True)
|
|
61
71
|
conn.row_factory = sqlite3.Row
|
|
62
|
-
|
|
63
|
-
#
|
|
64
|
-
|
|
65
|
-
"SELECT
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
label = "stored" if new_mem else "recalled"
|
|
76
|
-
noun = "memory" if n == 1 else "memories"
|
|
77
|
-
lines = [f"🌙 Luminary — {n} {noun} {label}"]
|
|
78
|
-
# Show the content of newly stored facts (transparency).
|
|
79
|
-
if new_mem:
|
|
72
|
+
last_id = _last_shown_id()
|
|
73
|
+
# First run after fix: only show the single newest to avoid spamming history
|
|
74
|
+
if last_id == 0:
|
|
75
|
+
row = conn.execute("SELECT MAX(id) as max_id FROM memories").fetchone()
|
|
76
|
+
max_id = int(row["max_id"] or 0)
|
|
77
|
+
if max_id == 0:
|
|
78
|
+
conn.close()
|
|
79
|
+
return None
|
|
80
|
+
new_rows = conn.execute(
|
|
81
|
+
"SELECT id, content FROM memories WHERE id = ?",
|
|
82
|
+
(max_id,),
|
|
83
|
+
).fetchall()
|
|
84
|
+
else:
|
|
80
85
|
new_rows = conn.execute(
|
|
81
|
-
"SELECT content FROM memories "
|
|
82
|
-
|
|
83
|
-
"ORDER BY id DESC LIMIT 3",
|
|
84
|
-
(cutoff,),
|
|
86
|
+
"SELECT id, content FROM memories WHERE id > ? ORDER BY id ASC LIMIT 3",
|
|
87
|
+
(last_id,),
|
|
85
88
|
).fetchall()
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
89
|
+
if not new_rows:
|
|
90
|
+
conn.close()
|
|
91
|
+
return None
|
|
92
|
+
n_new = len(new_rows)
|
|
93
|
+
noun = "memory" if n_new == 1 else "memories"
|
|
94
|
+
lines = [f"🌙 Luminary — {n_new} {noun} stored"]
|
|
95
|
+
for r in new_rows:
|
|
96
|
+
content = str(r["content"] or "").replace("\n", " ").strip()
|
|
97
|
+
if len(content) > 120:
|
|
98
|
+
content = content[:120].rsplit(" ", 1)[0] + "…"
|
|
99
|
+
if content:
|
|
100
|
+
lines.append(f" • {content}")
|
|
101
|
+
max_shown = max(int(r["id"]) for r in new_rows)
|
|
102
|
+
_set_last_shown_id(max_shown)
|
|
90
103
|
conn.close()
|
|
91
104
|
return "\n".join(lines)
|
|
92
105
|
except Exception:
|
|
@@ -55,27 +55,33 @@ if [ "$DO_PROVIDER" -eq 1 ]; then
|
|
|
55
55
|
log "no config.yaml found — creating minimal config with memory.provider"
|
|
56
56
|
mkdir -p "$HERMES_HOME"
|
|
57
57
|
printf 'memory:\n provider: luminary\n' > "$CONFIG"
|
|
58
|
-
elif grep -q "provider:" "$CONFIG"; then
|
|
59
|
-
log "memory.provider already set — skipping config edit"
|
|
60
58
|
else
|
|
61
|
-
|
|
62
|
-
#
|
|
63
|
-
|
|
64
|
-
|
|
59
|
+
# Use Python to inspect ONLY the ^memory: block — never match a
|
|
60
|
+
# `provider:` line elsewhere (e.g. provider: command-code in the
|
|
61
|
+
# models section), which would wrongly skip the edit.
|
|
62
|
+
python3 - "$CONFIG" <<'PY'
|
|
65
63
|
import re, sys
|
|
66
64
|
p = sys.argv[1]
|
|
67
65
|
s = open(p).read()
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
66
|
+
|
|
67
|
+
def memory_block(text):
|
|
68
|
+
m = re.search(r"(?m)^memory:\s*(.*?)(?=^\S|\Z)", text, re.S)
|
|
69
|
+
return m.group(1) if m else ""
|
|
70
|
+
|
|
71
|
+
block = memory_block(s)
|
|
72
|
+
if "provider:" in block:
|
|
73
|
+
print("memory.provider already set in memory: block — skipping")
|
|
74
|
+
raise SystemExit(0)
|
|
75
|
+
|
|
76
|
+
if re.search(r"(?m)^memory:\s*$", s):
|
|
77
|
+
# memory: block exists but has no provider — insert under it
|
|
78
|
+
s = re.sub(r"(?m)^memory:\s*$", "memory:\n provider: luminary", s, count=1)
|
|
79
|
+
print("inserted memory.provider under existing memory: block")
|
|
72
80
|
else:
|
|
73
|
-
|
|
81
|
+
s += "\nmemory:\n provider: luminary\n"
|
|
82
|
+
print("appended memory: block to config.yaml")
|
|
83
|
+
open(p, "w").write(s)
|
|
74
84
|
PY
|
|
75
|
-
else
|
|
76
|
-
printf '\nmemory:\n provider: luminary\n' >> "$CONFIG"
|
|
77
|
-
log "appended memory: block to config.yaml"
|
|
78
|
-
fi
|
|
79
85
|
fi
|
|
80
86
|
fi
|
|
81
87
|
|