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.
Files changed (130) hide show
  1. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/CHANGELOG.md +12 -0
  2. luminary_memory-0.2.4/PKG-INFO +176 -0
  3. luminary_memory-0.2.4/PLAN-v0.2.4.md +128 -0
  4. luminary_memory-0.2.4/README.md +142 -0
  5. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/docs/lifecycle.md +36 -0
  6. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/hermes/hooks/luminary-activity/handler.py +40 -27
  7. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/hermes/install.sh +21 -15
  8. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/pyproject.toml +1 -1
  9. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/__init__.py +1 -1
  10. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/api.py +103 -1
  11. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/cli.py +26 -0
  12. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/ingest/llm.py +1 -1
  13. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/hermes/test_config.py +26 -0
  14. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/hermes/test_hooks.py +28 -0
  15. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/hermes/test_lifecycle.py +33 -0
  16. luminary_memory-0.2.4/tests/hermes/test_session_boundaries.py +128 -0
  17. luminary_memory-0.2.4/tests/hermes/test_sync_turn.py +137 -0
  18. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/hermes/test_tools.py +34 -0
  19. luminary_memory-0.2.4/tests/test_api.py +184 -0
  20. luminary_memory-0.2.4/tests/test_api_extended.py +371 -0
  21. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/test_cli.py +49 -0
  22. luminary_memory-0.2.4/tests/test_ingest_llm.py +177 -0
  23. luminary_memory-0.2.3/PKG-INFO +0 -277
  24. luminary_memory-0.2.3/README.md +0 -243
  25. luminary_memory-0.2.3/tests/hermes/test_session_boundaries.py +0 -67
  26. luminary_memory-0.2.3/tests/hermes/test_sync_turn.py +0 -73
  27. luminary_memory-0.2.3/tests/test_api.py +0 -68
  28. luminary_memory-0.2.3/tests/test_api_extended.py +0 -83
  29. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  30. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  31. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  32. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/.github/workflows/ci.yml +0 -0
  33. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/.github/workflows/pages.yml +0 -0
  34. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/.github/workflows/publish.yml +0 -0
  35. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/.gitignore +0 -0
  36. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/CODE_OF_CONDUCT.md +0 -0
  37. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/CONTRIBUTING.md +0 -0
  38. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/LICENSE +0 -0
  39. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/PLAN-v0.2.0.md +0 -0
  40. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/PLAN-v0.2.1.md +0 -0
  41. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/PLAN.md +0 -0
  42. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/ROADMAP.md +0 -0
  43. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/SECURITY.md +0 -0
  44. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/benchmarks/README.md +0 -0
  45. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/benchmarks/RESULTS.md +0 -0
  46. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/benchmarks/__init__.py +0 -0
  47. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/benchmarks/hermes_provider_bench.py +0 -0
  48. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/benchmarks/metrics.py +0 -0
  49. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/benchmarks/run_benchmarks.py +0 -0
  50. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/benchmarks/synthetic.py +0 -0
  51. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/docs/api.md +0 -0
  52. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/docs/architecture.md +0 -0
  53. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/docs/backends.md +0 -0
  54. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/docs/cli.md +0 -0
  55. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/docs/hermes-integration.md +0 -0
  56. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/docs/index.md +0 -0
  57. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/docs/quickstart.md +0 -0
  58. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/docs/recall.md +0 -0
  59. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/hermes/README.md +0 -0
  60. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/hermes/SKILL.md +0 -0
  61. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/hermes/hooks/luminary-activity/HOOK.yaml +0 -0
  62. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/hermes/hooks/luminary-activity/README.md +0 -0
  63. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/backends/__init__.py +0 -0
  64. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/backends/base.py +0 -0
  65. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/backends/pgvector.py +0 -0
  66. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/backends/sqlite.py +0 -0
  67. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/budget.py +0 -0
  68. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/config.py +0 -0
  69. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/embeddings/__init__.py +0 -0
  70. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/embeddings/fastembed.py +0 -0
  71. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/export.py +0 -0
  72. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/hermes/__init__.py +0 -0
  73. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/hermes/config.py +0 -0
  74. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/hermes/config_schema.py +0 -0
  75. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/hermes/plugin.yaml +0 -0
  76. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/hermes/provider.py +0 -0
  77. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/ingest/__init__.py +0 -0
  78. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/ingest/whitelist.py +0 -0
  79. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/lifecycle/__init__.py +0 -0
  80. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/lifecycle/cleanup.py +0 -0
  81. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/lifecycle/consolidate.py +0 -0
  82. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/lifecycle/prune.py +0 -0
  83. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/lifecycle/runner.py +0 -0
  84. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/recall/__init__.py +0 -0
  85. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/recall/dedup.py +0 -0
  86. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/recall/fusion.py +0 -0
  87. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/recall/graph.py +0 -0
  88. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/recall/keyword.py +0 -0
  89. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/recall/planner.py +0 -0
  90. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/recall/semantic.py +0 -0
  91. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/recall/snippets.py +0 -0
  92. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/recall/temporal.py +0 -0
  93. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/schema.py +0 -0
  94. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/src/luminary_memory/types.py +0 -0
  95. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/__init__.py +0 -0
  96. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/conftest.py +0 -0
  97. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/hermes/test_backup_paths.py +0 -0
  98. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/hermes/test_entrypoint.py +0 -0
  99. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/hermes/test_provider_import.py +0 -0
  100. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/hermes/test_recall.py +0 -0
  101. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/hermes/test_system_prompt.py +0 -0
  102. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/hermes_stubs/agent/memory_provider.py +0 -0
  103. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/test_backend_pgvector.py +0 -0
  104. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/test_backend_sqlite.py +0 -0
  105. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/test_budget.py +0 -0
  106. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/test_dedup_jaccard.py +0 -0
  107. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/test_embeddings.py +0 -0
  108. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/test_fusion_rrf.py +0 -0
  109. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/test_ingest_whitelist.py +0 -0
  110. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/test_lifecycle.py +0 -0
  111. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/test_recall_graph.py +0 -0
  112. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/test_recall_keyword.py +0 -0
  113. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/test_recall_orchestrator.py +0 -0
  114. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/test_recall_semantic.py +0 -0
  115. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/test_recall_temporal.py +0 -0
  116. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/test_schema.py +0 -0
  117. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/test_t10_export.py +0 -0
  118. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/test_t11_limit_zero.py +0 -0
  119. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/test_t1_hnsw.py +0 -0
  120. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/test_t3_planner.py +0 -0
  121. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/test_t4_batch.py +0 -0
  122. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/test_t5_enricher.py +0 -0
  123. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/test_t6_tag_scoped.py +0 -0
  124. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/test_t7_updated_at.py +0 -0
  125. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/test_t8_snippets.py +0 -0
  126. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/tests/test_t9_cli_json.py +0 -0
  127. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/website/.nojekyll +0 -0
  128. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/website/css/luminary.css +0 -0
  129. {luminary_memory-0.2.3 → luminary_memory-0.2.4}/website/index.html +0 -0
  130. {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
+ [![PyPI version](https://img.shields.io/pypi/v/luminary-memory?color=8ab4e8&label=PyPI)](https://pypi.org/project/luminary-memory)
40
+ [![Python](https://img.shields.io/pypi/pyversions/luminary-memory?color=8ab4e8)](https://pypi.org/project/luminary-memory)
41
+ [![License](https://img.shields.io/github/license/alertxsto/luminary-memory?color=8ab4e8)](LICENSE)
42
+ [![CI](https://img.shields.io/github/actions/workflow/status/alertxsto/luminary-memory/ci.yml?color=8ab4e8&label=CI)](https://github.com/alertxsto/luminary-memory/actions)
43
+ [![Tests](https://img.shields.io/badge/tests-168%20passing-8ab4e8)](https://github.com/alertxsto/luminary-memory/actions)
44
+ [![Coverage](https://img.shields.io/badge/coverage-85%25-8ab4e8)](https://github.com/alertxsto/luminary-memory)
45
+ [![Stars](https://img.shields.io/github/stars/alertxsto/luminary-memory?color=8ab4e8)](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
+ [![PyPI version](https://img.shields.io/pypi/v/luminary-memory?color=8ab4e8&label=PyPI)](https://pypi.org/project/luminary-memory)
6
+ [![Python](https://img.shields.io/pypi/pyversions/luminary-memory?color=8ab4e8)](https://pypi.org/project/luminary-memory)
7
+ [![License](https://img.shields.io/github/license/alertxsto/luminary-memory?color=8ab4e8)](LICENSE)
8
+ [![CI](https://img.shields.io/github/actions/workflow/status/alertxsto/luminary-memory/ci.yml?color=8ab4e8&label=CI)](https://github.com/alertxsto/luminary-memory/actions)
9
+ [![Tests](https://img.shields.io/badge/tests-168%20passing-8ab4e8)](https://github.com/alertxsto/luminary-memory/actions)
10
+ [![Coverage](https://img.shields.io/badge/coverage-85%25-8ab4e8)](https://github.com/alertxsto/luminary-memory)
11
+ [![Stars](https://img.shields.io/github/stars/alertxsto/luminary-memory?color=8ab4e8)](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 compact status line if the store was active recently."""
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
- cutoff = int(time.time()) - seconds
63
- # created recently (recall updates last_accessed_at, ingest inserts new)
64
- row = conn.execute(
65
- "SELECT COUNT(*) AS n, "
66
- "MAX(CASE WHEN strftime('%s', created_at) > ? THEN 1 ELSE 0 END) AS new_mem "
67
- "FROM memories",
68
- (cutoff,),
69
- ).fetchone()
70
- n = int(row["n"] or 0)
71
- new_mem = bool(row["new_mem"])
72
- if n == 0:
73
- conn.close()
74
- return None
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
- "WHERE strftime('%s', created_at) > ? "
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
- for r in new_rows:
87
- content = str(r["content"] or "").replace("\n", " ")[:90]
88
- if content:
89
- lines.append(f" • {content}")
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
- log "enabling memory.provider = luminary ..."
62
- # Insert under the memory: section if present, else append a memory block.
63
- if [ -f "$CONFIG" ] && grep -q "^memory:" "$CONFIG"; then
64
- python3 - "$CONFIG" <<'PY'
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
- if re.search(r"(?m)^memory:\s*$", s) and "provider:" not in s.split("memory:")[1].split("\n\n")[0]:
69
- s = s.replace("memory:", "memory:\n provider: luminary", 1)
70
- open(p, "w").write(s)
71
- print(" inserted memory.provider under existing memory: block")
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
- print(" memory block unchanged (provider may already exist)")
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
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "luminary-memory"
7
- version = "0.2.3"
7
+ version = "0.2.4"
8
8
  description = "A lightweight, self-hosted memory layer for AI agents"
9
9
  readme = "README.md"
10
10
  license = { text = "Apache-2.0" }
@@ -1,4 +1,4 @@
1
1
  from luminary_memory.api import MemoryClient
2
2
 
3
- __version__ = "0.2.3"
3
+ __version__ = "0.2.4"
4
4
  __all__ = ["MemoryClient", "__version__"]