engrava 0.3.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.
- engrava-0.3.0/CHANGELOG.md +760 -0
- engrava-0.3.0/LICENSE +21 -0
- engrava-0.3.0/MANIFEST.in +10 -0
- engrava-0.3.0/PKG-INFO +287 -0
- engrava-0.3.0/README.md +235 -0
- engrava-0.3.0/docs/api-reference.md +300 -0
- engrava-0.3.0/docs/architecture.md +139 -0
- engrava-0.3.0/docs/benchmarks.md +126 -0
- engrava-0.3.0/docs/configuration.md +181 -0
- engrava-0.3.0/docs/dreaming.md +277 -0
- engrava-0.3.0/docs/extension-hooks.md +126 -0
- engrava-0.3.0/docs/extensions.md +308 -0
- engrava-0.3.0/docs/known-limitations.md +94 -0
- engrava-0.3.0/docs/mindql.md +112 -0
- engrava-0.3.0/docs/observability.md +58 -0
- engrava-0.3.0/docs/quickstart.md +210 -0
- engrava-0.3.0/docs/scopes.md +40 -0
- engrava-0.3.0/docs/search.md +183 -0
- engrava-0.3.0/docs/upgrade.md +98 -0
- engrava-0.3.0/pyproject.toml +132 -0
- engrava-0.3.0/setup.cfg +4 -0
- engrava-0.3.0/src/engrava/__init__.py +213 -0
- engrava-0.3.0/src/engrava/benchmarks/__init__.py +6 -0
- engrava-0.3.0/src/engrava/benchmarks/longmemeval/README.md +179 -0
- engrava-0.3.0/src/engrava/benchmarks/longmemeval/__init__.py +30 -0
- engrava-0.3.0/src/engrava/benchmarks/longmemeval/__main__.py +10 -0
- engrava-0.3.0/src/engrava/benchmarks/longmemeval/cli.py +178 -0
- engrava-0.3.0/src/engrava/benchmarks/longmemeval/dataset_loader.py +282 -0
- engrava-0.3.0/src/engrava/benchmarks/longmemeval/evaluate.py +240 -0
- engrava-0.3.0/src/engrava/benchmarks/longmemeval/runner.py +512 -0
- engrava-0.3.0/src/engrava/benchmarks/synthetic/README.md +38 -0
- engrava-0.3.0/src/engrava/benchmarks/synthetic/__init__.py +19 -0
- engrava-0.3.0/src/engrava/benchmarks/synthetic/__main__.py +10 -0
- engrava-0.3.0/src/engrava/benchmarks/synthetic/datasets/__init__.py +7 -0
- engrava-0.3.0/src/engrava/benchmarks/synthetic/datasets/synthetic-v1.json +17021 -0
- engrava-0.3.0/src/engrava/benchmarks/synthetic/evaluate.py +759 -0
- engrava-0.3.0/src/engrava/benchmarks/synthetic/generate.py +686 -0
- engrava-0.3.0/src/engrava/benchmarks/synthetic/runner.py +805 -0
- engrava-0.3.0/src/engrava/benchmarks/synthetic/scenarios.py +718 -0
- engrava-0.3.0/src/engrava/cli/__init__.py +1 -0
- engrava-0.3.0/src/engrava/cli/config.py +68 -0
- engrava-0.3.0/src/engrava/cli/main.py +1078 -0
- engrava-0.3.0/src/engrava/config.py +1973 -0
- engrava-0.3.0/src/engrava/domain/__init__.py +1 -0
- engrava-0.3.0/src/engrava/domain/enums.py +287 -0
- engrava-0.3.0/src/engrava/domain/exceptions.py +192 -0
- engrava-0.3.0/src/engrava/domain/manifest.py +43 -0
- engrava-0.3.0/src/engrava/domain/models/__init__.py +31 -0
- engrava-0.3.0/src/engrava/domain/models/action.py +91 -0
- engrava-0.3.0/src/engrava/domain/models/edge.py +56 -0
- engrava-0.3.0/src/engrava/domain/models/embedding.py +81 -0
- engrava-0.3.0/src/engrava/domain/models/journal.py +82 -0
- engrava-0.3.0/src/engrava/domain/models/metrics.py +63 -0
- engrava-0.3.0/src/engrava/domain/models/mutation_type.py +28 -0
- engrava-0.3.0/src/engrava/domain/models/search.py +42 -0
- engrava-0.3.0/src/engrava/domain/models/thought.py +254 -0
- engrava-0.3.0/src/engrava/domain/models/ttl.py +51 -0
- engrava-0.3.0/src/engrava/domain/protocols/__init__.py +17 -0
- engrava-0.3.0/src/engrava/domain/protocols/embedding_provider.py +71 -0
- engrava-0.3.0/src/engrava/domain/protocols/engrava_core.py +366 -0
- engrava-0.3.0/src/engrava/domain/protocols/hooks.py +205 -0
- engrava-0.3.0/src/engrava/embeddings/__init__.py +4 -0
- engrava-0.3.0/src/engrava/embeddings/callback.py +93 -0
- engrava-0.3.0/src/engrava/embeddings/huggingface.py +158 -0
- engrava-0.3.0/src/engrava/embeddings/ollama.py +166 -0
- engrava-0.3.0/src/engrava/embeddings/openai_compatible.py +183 -0
- engrava-0.3.0/src/engrava/embeddings/sentence_transformer.py +150 -0
- engrava-0.3.0/src/engrava/extensions/__init__.py +6 -0
- engrava-0.3.0/src/engrava/extensions/discovery.py +80 -0
- engrava-0.3.0/src/engrava/extensions/dreaming.py +1535 -0
- engrava-0.3.0/src/engrava/extensions/dreaming_cluster_quality.py +512 -0
- engrava-0.3.0/src/engrava/extensions/dreaming_keyphrases.py +664 -0
- engrava-0.3.0/src/engrava/extensions/dreaming_reflection_content.py +455 -0
- engrava-0.3.0/src/engrava/extensions/dreaming_signals.py +312 -0
- engrava-0.3.0/src/engrava/extensions/vector_sqlite_vec.py +223 -0
- engrava-0.3.0/src/engrava/infrastructure/__init__.py +1 -0
- engrava-0.3.0/src/engrava/infrastructure/read_only_store.py +268 -0
- engrava-0.3.0/src/engrava/infrastructure/service_manager.py +325 -0
- engrava-0.3.0/src/engrava/infrastructure/sqlite/__init__.py +7 -0
- engrava-0.3.0/src/engrava/infrastructure/sqlite/centroid.py +58 -0
- engrava-0.3.0/src/engrava/infrastructure/sqlite/engrava_core.py +4008 -0
- engrava-0.3.0/src/engrava/infrastructure/sqlite/extension_migrations.py +235 -0
- engrava-0.3.0/src/engrava/infrastructure/sqlite/journal_writer.py +308 -0
- engrava-0.3.0/src/engrava/infrastructure/sqlite/schema_core.sql +185 -0
- engrava-0.3.0/src/engrava/metadata.py +135 -0
- engrava-0.3.0/src/engrava/mindql/__init__.py +1 -0
- engrava-0.3.0/src/engrava/mindql/executor.py +287 -0
- engrava-0.3.0/src/engrava/mindql/parser.py +293 -0
- engrava-0.3.0/src/engrava/py.typed +0 -0
- engrava-0.3.0/src/engrava.egg-info/PKG-INFO +287 -0
- engrava-0.3.0/src/engrava.egg-info/SOURCES.txt +93 -0
- engrava-0.3.0/src/engrava.egg-info/dependency_links.txt +1 -0
- engrava-0.3.0/src/engrava.egg-info/entry_points.txt +2 -0
- engrava-0.3.0/src/engrava.egg-info/requires.txt +32 -0
- engrava-0.3.0/src/engrava.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,760 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to engrava will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog 1.1.0](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## 0.3.0 (2026-06-02)
|
|
9
|
+
|
|
10
|
+
* ci: add on-demand smoke-gate workflow (#10) ([50e2bf2](https://github.com/sovantica/engrava/commit/50e2bf2)), closes [#10](https://github.com/sovantica/engrava/issues/10)
|
|
11
|
+
* ci: automated release pipeline ([18c1d68](https://github.com/sovantica/engrava/commit/18c1d68))
|
|
12
|
+
* ci: quote semantic_version input to fix release workflow startup ([2f3c9c3](https://github.com/sovantica/engrava/commit/2f3c9c3))
|
|
13
|
+
* ci: skip branch-name guard for automated dependency PRs (#8) ([b14bb0a](https://github.com/sovantica/engrava/commit/b14bb0a)), closes [#8](https://github.com/sovantica/engrava/issues/8)
|
|
14
|
+
* ci: skip upgrade smoke test when the baseline is not yet published (#9) ([cc05ed7](https://github.com/sovantica/engrava/commit/cc05ed7)), closes [#9](https://github.com/sovantica/engrava/issues/9)
|
|
15
|
+
* ci: use semantic-release CLI directly to satisfy actions allowlist ([0ebf9f1](https://github.com/sovantica/engrava/commit/0ebf9f1))
|
|
16
|
+
* release: merge dev into release/v0.3.0 (automated release pipeline) ([02d3f05](https://github.com/sovantica/engrava/commit/02d3f05))
|
|
17
|
+
* release: v0.3.0 — first public release ([8bd705e](https://github.com/sovantica/engrava/commit/8bd705e))
|
|
18
|
+
* feat: graph memory database — dreaming consolidation, hybrid search, audit trail ([ed82259](https://github.com/sovantica/engrava/commit/ed82259))
|
|
19
|
+
* test: refresh stale FTS-upgrade fixture and public-export baseline (#11) ([46cab01](https://github.com/sovantica/engrava/commit/46cab01)), closes [#11](https://github.com/sovantica/engrava/issues/11)
|
|
20
|
+
* chore(deps): bump actions/checkout from 4 to 6 (#3) ([840ab88](https://github.com/sovantica/engrava/commit/840ab88)), closes [#3](https://github.com/sovantica/engrava/issues/3)
|
|
21
|
+
* chore(deps): bump actions/download-artifact from 4 to 8 (#7) ([c7ca7cf](https://github.com/sovantica/engrava/commit/c7ca7cf)), closes [#7](https://github.com/sovantica/engrava/issues/7)
|
|
22
|
+
* chore(deps): bump actions/setup-node from 4 to 6 (#6) ([99acddc](https://github.com/sovantica/engrava/commit/99acddc)), closes [#6](https://github.com/sovantica/engrava/issues/6)
|
|
23
|
+
* chore(deps): bump actions/upload-artifact from 4 to 7 (#5) ([7ad3f2f](https://github.com/sovantica/engrava/commit/7ad3f2f)), closes [#5](https://github.com/sovantica/engrava/issues/5)
|
|
24
|
+
* chore(deps): bump softprops/action-gh-release from 2 to 3 (#4) ([9eb3729](https://github.com/sovantica/engrava/commit/9eb3729)), closes [softprops/action-#release](https://github.com/softprops/action-/issues/release) [#4](https://github.com/sovantica/engrava/issues/4)
|
|
25
|
+
* Bump idna from 3.13 to 3.15 (#1) ([f402fd2](https://github.com/sovantica/engrava/commit/f402fd2)), closes [#1](https://github.com/sovantica/engrava/issues/1)
|
|
26
|
+
* docs: align metadata and docs with product tagline; add dependabot and issue config (#2) ([b3bb62d](https://github.com/sovantica/engrava/commit/b3bb62d)), closes [#2](https://github.com/sovantica/engrava/issues/2)
|
|
27
|
+
|
|
28
|
+
# Changelog
|
|
29
|
+
|
|
30
|
+
All notable changes to engrava will be documented in this file.
|
|
31
|
+
|
|
32
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
33
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
34
|
+
|
|
35
|
+
## [0.3.0]
|
|
36
|
+
|
|
37
|
+
### Documentation
|
|
38
|
+
|
|
39
|
+
- **docs:** align package description and documentation with the product
|
|
40
|
+
tagline ("The memory database for AI agents") and the public contact
|
|
41
|
+
address.
|
|
42
|
+
|
|
43
|
+
### Maintenance
|
|
44
|
+
|
|
45
|
+
- **Pre-publish smoke gate.** The publish workflow now runs a hard-fail
|
|
46
|
+
quality gate before any wheel or sdist is built: the bundled
|
|
47
|
+
synthetic benchmark is driven in its binding acceptance-criterion
|
|
48
|
+
mode and the committed floors (synthesis coverage, direct-query
|
|
49
|
+
neutrality, sanity-scenario neutrality, sanity with reflection-boost)
|
|
50
|
+
are enforced from source. A breach blocks publish. A standalone
|
|
51
|
+
``scripts/check_smoke_gate.py`` exposes the same gate for local runs
|
|
52
|
+
and is wired into ``make smoke-gate``. An optional LongMemEval
|
|
53
|
+
recall@5 probe enforces a calibrated absolute floor when invoked via
|
|
54
|
+
``--include-longmemeval``; the probe stays off in CI by default
|
|
55
|
+
because the LongMemEval dataset is user-download and the run takes
|
|
56
|
+
several minutes, but maintainers invoke it manually before tagging a
|
|
57
|
+
release.
|
|
58
|
+
|
|
59
|
+
### Added
|
|
60
|
+
|
|
61
|
+
- **Bundled walkthrough example + self-anchored metadata helpers.** A
|
|
62
|
+
single-file script a Free-tier user can run directly from the repo
|
|
63
|
+
to see the engine work in under a minute:
|
|
64
|
+
|
|
65
|
+
* `examples/quickstart.py` — boots an in-memory store with a local
|
|
66
|
+
embedding encoder, ingests a handful of percepts and utterances
|
|
67
|
+
built via the new `engrava.metadata` helpers, runs one dreaming
|
|
68
|
+
consolidation cycle, and queries via hybrid search.
|
|
69
|
+
|
|
70
|
+
The script pre-flights `sentence_transformers` with a clean
|
|
71
|
+
actionable message and `sys.exit(2)` when the `[embeddings-local]`
|
|
72
|
+
extra is missing.
|
|
73
|
+
|
|
74
|
+
Adds three pure-function helpers in `engrava.metadata`, re-exported
|
|
75
|
+
from the top-level package: `percept(...)` for input arriving at
|
|
76
|
+
the agent, `utterance(...)` for the agent's own outgoing content,
|
|
77
|
+
and `thought(...)` for the agent's internal cognition. They build
|
|
78
|
+
the structured `metadata` dictionary the persistence layer
|
|
79
|
+
recognises, anchoring every stored thought to a perspective and a
|
|
80
|
+
source. Same arguments always return an equal dictionary.
|
|
81
|
+
|
|
82
|
+
`docs/quickstart.md` gains a "Run the bundled walkthrough" section
|
|
83
|
+
with the dreaming and self-anchored-identity narrative; new
|
|
84
|
+
`examples/README.md` indexes the scripts.
|
|
85
|
+
|
|
86
|
+
- **LongMemEval public benchmark harness** — engrava-side runner for
|
|
87
|
+
the published LongMemEval memory-evaluation dataset (Wu et al., ICLR
|
|
88
|
+
2025, arXiv:2410.10813).
|
|
89
|
+
|
|
90
|
+
* `python -m engrava.benchmarks.longmemeval` ingests each
|
|
91
|
+
question's haystack into a fresh engrava store, optionally runs
|
|
92
|
+
one dreaming consolidation cycle, queries via `search_hybrid`,
|
|
93
|
+
and scores the retrieved chunks.
|
|
94
|
+
* Three evaluation modes: deterministic substring containment
|
|
95
|
+
(default), deterministic cosine-similarity over a configurable
|
|
96
|
+
threshold, and an opt-in LLM judge wired via a thin
|
|
97
|
+
`LLMJudgeClient` protocol so callers can plug in any provider.
|
|
98
|
+
* The dataset is **not bundled** — the loader downloads the
|
|
99
|
+
requested variant from the upstream HuggingFace distribution on
|
|
100
|
+
first use and caches it under `~/.engrava/benchmarks/longmemeval/`.
|
|
101
|
+
* Self-anchored thought metadata follows the same shape as the
|
|
102
|
+
synthetic benchmark (`perspective`, `source.is_self`,
|
|
103
|
+
`session_id`, `turn_index`), so dreaming filters see the same
|
|
104
|
+
inputs they do in production.
|
|
105
|
+
* See `src/engrava/benchmarks/longmemeval/README.md` for
|
|
106
|
+
attribution, license, and download instructions.
|
|
107
|
+
|
|
108
|
+
- **Synthetic benchmark suite** — reproducible dreaming evidence
|
|
109
|
+
runnable on any laptop without API keys or network access.
|
|
110
|
+
|
|
111
|
+
* `python -m engrava.benchmarks.synthetic` runs binding acceptance
|
|
112
|
+
measurements (~5 minutes): synthesis coverage, direct retrieval
|
|
113
|
+
neutrality, sanity tolerance.
|
|
114
|
+
* `python -m engrava.benchmarks.synthetic --with-reproducibility`
|
|
115
|
+
adds full per-scenario texture from the bundled
|
|
116
|
+
`synthetic-v1.json` dataset (~10 minutes total).
|
|
117
|
+
* Measures three properties: synthesis coverage (dreaming produces
|
|
118
|
+
REFLECTIONs that consolidate related facts), direct retrieval
|
|
119
|
+
neutrality (dreaming does not degrade baseline competence), and
|
|
120
|
+
sanity tolerance (small, non-pathological influence on neutral
|
|
121
|
+
queries).
|
|
122
|
+
* See `docs/benchmarks.md` for the interpretation guide and the
|
|
123
|
+
v0.4.0 roadmap (tighter neutrality ceilings + recall-lift
|
|
124
|
+
evidence).
|
|
125
|
+
|
|
126
|
+
- **Cluster quality gates for the dreaming consolidation loop.** Seven
|
|
127
|
+
deterministic gates run on every candidate cluster before a
|
|
128
|
+
REFLECTION is materialised, dropping clusters that would produce
|
|
129
|
+
low-signal or actively misleading memories. Most gates are
|
|
130
|
+
language-agnostic; the contradiction gate (Gate 3) ships with an
|
|
131
|
+
English-only sentiment-token lexicon and silently passes clusters
|
|
132
|
+
in other languages. Gates fire in two phases:
|
|
133
|
+
|
|
134
|
+
*Pre-build (cheap rejections before content assembly):*
|
|
135
|
+
|
|
136
|
+
* **Gate 1 — duplicate content members** rejects clusters that
|
|
137
|
+
contain byte-identical member content within the same cluster
|
|
138
|
+
(a dedup escape that would inflate one statement into a
|
|
139
|
+
pseudo-cluster).
|
|
140
|
+
* **Gate 2 — persona-only** rejects clusters where the share of
|
|
141
|
+
members carrying a persona/identity marker (and no conversation
|
|
142
|
+
marker) exceeds the configured threshold.
|
|
143
|
+
* **Gate 3 — contradictory** rejects clusters with member pairs
|
|
144
|
+
asserting documented opposite predicates (English lexicon).
|
|
145
|
+
* **Gate 4 — low cohesion** rejects clusters whose mean pairwise
|
|
146
|
+
cosine similarity (over L2-normalised embeddings) falls below
|
|
147
|
+
the configured threshold.
|
|
148
|
+
* **Gate 5 — external-source homogeneity** rejects clusters whose
|
|
149
|
+
share of members with `metadata["source"]["is_self"] != True`
|
|
150
|
+
falls below the configured minimum-external fraction; missing
|
|
151
|
+
or malformed `source` is treated as external under safe
|
|
152
|
+
fallback. Belt-and-suspenders over the upstream eligibility
|
|
153
|
+
filter.
|
|
154
|
+
* **Gate 6 — named-entity consistency** rejects clusters where the
|
|
155
|
+
fraction of members whose per-member named-entity set
|
|
156
|
+
intersects the first member's set falls below the configured
|
|
157
|
+
threshold; single-member and empty-NE-on-anchor clusters pass
|
|
158
|
+
vacuously.
|
|
159
|
+
|
|
160
|
+
*Post-build (after `build_reflection_content_v2`):*
|
|
161
|
+
|
|
162
|
+
* **Gate 8 — meaningful keyphrases** rejects REFLECTIONs whose final
|
|
163
|
+
`top_keyphrases` list is empty or composed solely of generic
|
|
164
|
+
filler bigrams.
|
|
165
|
+
|
|
166
|
+
All gates are pure functions in
|
|
167
|
+
`engrava.extensions.dreaming_cluster_quality`. They are wired into
|
|
168
|
+
the consolidation loop with per-gate rejection counters surfaced via
|
|
169
|
+
a single `INFO` summary log per pass.
|
|
170
|
+
|
|
171
|
+
Six new `DreamingGates` fields control the behaviour, all validated
|
|
172
|
+
in `__post_init__` so direct construction cannot bypass the
|
|
173
|
+
`[0.0, 1.0]` contract:
|
|
174
|
+
|
|
175
|
+
* `cluster_quality_gating_enabled: bool = True` — master switch.
|
|
176
|
+
* `cluster_quality_persona_threshold: float = 0.75` — share of
|
|
177
|
+
persona-marked members above which a cluster is persona-only.
|
|
178
|
+
* `cluster_quality_cohesion_threshold: float = 0.40` — minimum mean
|
|
179
|
+
pairwise cosine for the cluster to be considered coherent.
|
|
180
|
+
* `cluster_quality_external_homogeneity_threshold: float = 0.95` —
|
|
181
|
+
minimum fraction of external-source members for the gate to
|
|
182
|
+
pass (under safe fallback, missing/malformed `metadata.source`
|
|
183
|
+
counts as external).
|
|
184
|
+
* `cluster_quality_ne_consistency_threshold: float = 0.60` —
|
|
185
|
+
minimum fraction of members whose named-entity set intersects
|
|
186
|
+
the anchor (first) member's set.
|
|
187
|
+
* `cluster_quality_require_meaningful_keyphrases: bool = True` —
|
|
188
|
+
reject post-build REFLECTIONs without informative keyphrases.
|
|
189
|
+
|
|
190
|
+
Gating is on by default; existing fixtures that intentionally pass
|
|
191
|
+
synthetic, low-signal clusters opt out via
|
|
192
|
+
`cluster_quality_gating_enabled=False`. Legacy thought metadata
|
|
193
|
+
without `source` keys is treated as external under safe fallback,
|
|
194
|
+
preserving backward compatibility.
|
|
195
|
+
|
|
196
|
+
- **Statistical cross-cluster boilerplate filter for REFLECTION
|
|
197
|
+
keyphrases.** Two language-agnostic helpers ship in
|
|
198
|
+
`engrava.extensions.dreaming_keyphrases` —
|
|
199
|
+
`compute_cluster_phrase_frequency` (cluster-count map over the
|
|
200
|
+
per-cluster top-N keyphrase lists) and `is_boilerplate_phrase`
|
|
201
|
+
(case-insensitive threshold check with a small-corpus bypass).
|
|
202
|
+
`build_reflection_content_v2` accepts two new optional kwargs,
|
|
203
|
+
`cluster_phrase_df` and `total_clusters`, and uses them to drop
|
|
204
|
+
phrases that exceed the configured share of clusters; both kwargs
|
|
205
|
+
default to `None`, so existing callers see no behaviour change.
|
|
206
|
+
The dreaming consolidation loop now runs a lightweight pre-pass
|
|
207
|
+
that builds the document-frequency map ahead of REFLECTION
|
|
208
|
+
creation and forwards the kwargs into the content builder.
|
|
209
|
+
|
|
210
|
+
Three new `DreamingConfig` knobs control the filter:
|
|
211
|
+
|
|
212
|
+
* `boilerplate_threshold: float = 0.30` — phrases appearing in
|
|
213
|
+
more than 30 % of clusters are dropped. Set to `1.0` to
|
|
214
|
+
disable the filter.
|
|
215
|
+
* `boilerplate_min_corpus_size: int = 5` — minimum cluster count
|
|
216
|
+
before the filter engages; smaller runs preserve every
|
|
217
|
+
keyphrase regardless of frequency.
|
|
218
|
+
* `boilerplate_min_keyphrases_per_refl: int = 1` — fallback
|
|
219
|
+
guard. If filtering would shrink a REFLECTION's keyphrase
|
|
220
|
+
list below this size the raw list is kept, so REFLECTIONs
|
|
221
|
+
never end up with an empty `top_keyphrases` field.
|
|
222
|
+
|
|
223
|
+
Because the filter is statistical and operates on lowercased
|
|
224
|
+
phrases, it learns "boilerplate" from the live deployment without
|
|
225
|
+
any hardcoded blocklist and works equally well across English,
|
|
226
|
+
Polish, Japanese, French, German and every other language that
|
|
227
|
+
the TF-IDF tokeniser already supports.
|
|
228
|
+
|
|
229
|
+
- **Metadata-aware dreaming filter** on `DreamingConfig`. Five new
|
|
230
|
+
fields gate which thoughts the dreaming pipeline considers eligible
|
|
231
|
+
for promotion and REFLECTION clustering:
|
|
232
|
+
|
|
233
|
+
* `eligible_perspectives: frozenset[Literal["percept", "utterance",
|
|
234
|
+
"thought"]] | None` — positive filter on
|
|
235
|
+
`metadata["perspective"]`.
|
|
236
|
+
* `self_filter_mode: Literal["any", "self_only", "external_only"]`
|
|
237
|
+
— filter on `metadata["source"]["is_self"]`.
|
|
238
|
+
* `min_source_confidence: Literal["high", "medium", "low"]` —
|
|
239
|
+
minimum required `metadata["source"]["confidence"]`, ranked
|
|
240
|
+
`low < medium < high`.
|
|
241
|
+
* `excluded_content_types: frozenset[str]` — negative filter on
|
|
242
|
+
`metadata["content_type"]`; defaults to `frozenset({"code"})`
|
|
243
|
+
because code fragments cluster poorly under cosine similarity.
|
|
244
|
+
* `eligible_content_types: frozenset[str] | None` — optional
|
|
245
|
+
positive filter on `metadata["content_type"]`.
|
|
246
|
+
|
|
247
|
+
The filter is applied during promotion (filtered candidates skip the
|
|
248
|
+
P1-promotion decision) and during REFLECTION creation (only eligible
|
|
249
|
+
cluster members feed the cluster hash, the centroid embedding, the
|
|
250
|
+
structured content payload and the `CONSOLIDATED_FROM` lineage edges
|
|
251
|
+
— a cluster whose eligible subset falls below
|
|
252
|
+
`DreamingGates.min_cluster_size` is dropped entirely). Defaults
|
|
253
|
+
preserve the pre-existing dreaming behaviour: thoughts without any
|
|
254
|
+
structured metadata pass unconditionally, and a freshly-constructed
|
|
255
|
+
`DreamingConfig()` leaves every axis disabled. Promotion-side
|
|
256
|
+
rejections are reported at `DEBUG` level (`dreaming filter: N/M
|
|
257
|
+
candidates rejected by metadata filter (cycle X)`).
|
|
258
|
+
|
|
259
|
+
- **`ThoughtRecord.metadata` accepts nested dict values** for structured
|
|
260
|
+
namespaces. The `MetadataValue` type alias now resolves recursively to
|
|
261
|
+
`str | int | float | bool | None | dict[str, MetadataValue]`, so callers
|
|
262
|
+
can express grouped attributes such as
|
|
263
|
+
`metadata["source"] = {"is_self": True, "confidence": "high", ...}`
|
|
264
|
+
directly instead of flattening to dot-prefixed keys. Leaf values are
|
|
265
|
+
still restricted to JSON scalars; lists, tuples, sets and custom
|
|
266
|
+
containers remain rejected at every depth. The persistence-layer
|
|
267
|
+
validator walks nested dicts recursively and produces dotted key-path
|
|
268
|
+
error messages (e.g. `metadata value at source.tags type list not
|
|
269
|
+
allowed`). Backward-compatible: flat-only callers see no behaviour
|
|
270
|
+
change; the SQLite `metadata_json TEXT` column already stored the
|
|
271
|
+
serialised JSON faithfully, so no schema migration is required.
|
|
272
|
+
|
|
273
|
+
### Fixed
|
|
274
|
+
|
|
275
|
+
- **REFLECTION freshness (lifecycle-aware consolidation).** A REFLECTION is
|
|
276
|
+
a synthesis of a live cluster of thoughts, but until now it was frozen at
|
|
277
|
+
creation and never re-bound to the current state of that cluster. Three
|
|
278
|
+
related freshness gaps are closed so dreaming improves recall over a
|
|
279
|
+
long-running agent's lifetime instead of slowly polluting it:
|
|
280
|
+
|
|
281
|
+
* **Orphan retire.** The consolidation pass now sweeps existing
|
|
282
|
+
REFLECTIONs and retires (ACTIVE → ARCHIVED) any whose every consolidated
|
|
283
|
+
source thought has left the active set, so an ordinary `gc` reclaims it
|
|
284
|
+
(cascading its centroid embedding and consolidation edges). The sweep
|
|
285
|
+
fires only when *all* sources are gone and the REFLECTION has at least
|
|
286
|
+
one source — a partially-archived cluster keeps its synthesis, and a
|
|
287
|
+
source-less REFLECTION is never retired.
|
|
288
|
+
|
|
289
|
+
* **Centroid re-bind on evolve.** When a source thought's essence or
|
|
290
|
+
content changes and the thought is re-embedded, every REFLECTION that
|
|
291
|
+
was consolidated from it has its centroid recomputed from the current
|
|
292
|
+
member vectors (the same deterministic L2-normalized mean used at
|
|
293
|
+
creation, overwritten in place — no schema change). Metadata- or
|
|
294
|
+
priority-only edits do not re-embed the source and therefore leave
|
|
295
|
+
dependent REFLECTION centroids untouched.
|
|
296
|
+
|
|
297
|
+
* **Recall freshness floor.** Similarity, hybrid, and reflection-only
|
|
298
|
+
search no longer surface a retired REFLECTION in the window between
|
|
299
|
+
archival and physical collection, so a stale synthesis can no longer
|
|
300
|
+
out-rank fresh relevant thoughts. The floor is a lifecycle check at the
|
|
301
|
+
data layer; existing reflection ranking knobs are unchanged.
|
|
302
|
+
|
|
303
|
+
Every mechanism is deterministic (vector mean / cosine / SQL); no model is
|
|
304
|
+
invoked. Pre-publish recall-quality fix.
|
|
305
|
+
|
|
306
|
+
- **Referential integrity (cascade delete + edge FK).** The schema now
|
|
307
|
+
declares foreign keys with `ON DELETE CASCADE` for the three child
|
|
308
|
+
tables that reference `thought` (edges on both endpoints, embeddings,
|
|
309
|
+
and action records). Deleting a thought now actually removes the
|
|
310
|
+
related rows instead of leaving them behind as orphans, and inserting
|
|
311
|
+
an edge to a non-existent thought is rejected with a typed domain
|
|
312
|
+
exception (`ReferentialIntegrityError`) instead of the raw SQLite
|
|
313
|
+
integrity error. The change ships as schema version 12; existing
|
|
314
|
+
databases migrate in place via a recreate-table step that purges
|
|
315
|
+
any pre-existing orphan rows before enabling the constraints. The
|
|
316
|
+
migration is idempotent and recovers cleanly when a previous pass
|
|
317
|
+
was interrupted between tables. The connection-level pragma that
|
|
318
|
+
enables FK enforcement is now issued automatically from
|
|
319
|
+
`ensure_schema`, so callers that construct the store directly (not
|
|
320
|
+
via `from_config`) still get enforcement.
|
|
321
|
+
|
|
322
|
+
- **PyPI wheel and sdist now include `schema_core.sql`.**
|
|
323
|
+
`SqliteEngravaCore.ensure_schema` loads the core SQL schema via
|
|
324
|
+
`importlib.resources`, but `pyproject.toml` had no
|
|
325
|
+
`[tool.setuptools.package-data]` entry and there was no
|
|
326
|
+
`MANIFEST.in`, so neither the wheel nor the sdist actually bundled
|
|
327
|
+
the file. A fresh `pip install engrava` therefore crashed on the
|
|
328
|
+
first `EngravaCore` initialisation with `FileNotFoundError`. The
|
|
329
|
+
fix adds an explicit narrow package-data pattern
|
|
330
|
+
(`engrava = ["infrastructure/sqlite/*.sql"]`) plus a minimal
|
|
331
|
+
`MANIFEST.in` for the sdist side, so installed distributions now
|
|
332
|
+
contain the schema file and `EngravaCore` initialises cleanly out
|
|
333
|
+
of the box. Critical pre-publish fix.
|
|
334
|
+
|
|
335
|
+
### Maintenance
|
|
336
|
+
|
|
337
|
+
- **Build artifacts removed from tracked state.** The generated
|
|
338
|
+
`coverage.json` is no longer checked into the repository — it is
|
|
339
|
+
regenerated on every CI run. `.gitignore` gains `.coverage.*`,
|
|
340
|
+
`coverage.json`, `coverage.xml`, and a defensive bare-`.env` /
|
|
341
|
+
`.env.*` pair (with a `!.env.example` exception for template
|
|
342
|
+
files), so future coverage and environment files cannot
|
|
343
|
+
accidentally re-enter the public surface. No behavioural change —
|
|
344
|
+
repository hygiene only.
|
|
345
|
+
|
|
346
|
+
- **Packaging guard script + broader package-data coverage.** Adds
|
|
347
|
+
`scripts/verify_wheel_data.py`, a standalone build-and-inspect step
|
|
348
|
+
that rebuilds wheel + sdist and asserts the critical data files
|
|
349
|
+
(`schema_core.sql`, `synthetic-v1.json`) are bundled in both
|
|
350
|
+
artifacts; non-zero exit on missing files. `MANIFEST.in` adds
|
|
351
|
+
`recursive-include src/engrava *.json` so sdist coverage tracks the
|
|
352
|
+
wheel's `[tool.setuptools.package-data]` entry. Closes the
|
|
353
|
+
fresh-install regression class previously fixed only for
|
|
354
|
+
`schema_core.sql` (see Fixed); cumulative outcome with prior
|
|
355
|
+
contributor-instructions and benchmark refreshes.
|
|
356
|
+
|
|
357
|
+
- **Contributor instructions refreshed.** Per-repo agent guidance files
|
|
358
|
+
(`CLAUDE.md`, `AGENTS.md`, `.cursorrules`, `.github/copilot-instructions.md`,
|
|
359
|
+
`BRANCHING.md`) regenerated from canonical templates. Generic OSS-friendly
|
|
360
|
+
guidance on Conventional Commits, branch naming, code quality bar, and
|
|
361
|
+
contribution flow. Internal workflow references removed from the public
|
|
362
|
+
surface.
|
|
363
|
+
|
|
364
|
+
- **Repository metadata aligned with organization branding.** The
|
|
365
|
+
`LICENSE` copyright holder, the `pyproject.toml` `[project] authors`
|
|
366
|
+
field, and the `[project.urls]` GitHub references now reflect the
|
|
367
|
+
Sovantica organization identity instead of an individual maintainer.
|
|
368
|
+
A `hello@sovantica.ai` contact address replaces the previous
|
|
369
|
+
unauthored entry, and the `Documentation` URL points at
|
|
370
|
+
`https://docs.engrava.ai` so PyPI listings link to the product
|
|
371
|
+
documentation rather than the in-repo `docs/` tree. No behavioural
|
|
372
|
+
changes — packaging metadata only.
|
|
373
|
+
|
|
374
|
+
### Behavior Changes
|
|
375
|
+
|
|
376
|
+
- **`ThoughtRecord.metadata` field for caller-supplied structured attributes.**
|
|
377
|
+
Each thought now carries an extensible `dict[str, MetadataValue]`
|
|
378
|
+
(`MetadataValue = str | int | float | bool | None`) for flat scalar
|
|
379
|
+
attributes such as conversation role, source language, content type,
|
|
380
|
+
external session identifier, turn index or speaker name. The field
|
|
381
|
+
defaults to an empty dict, so existing callers compile and run
|
|
382
|
+
unchanged — no code change is required to upgrade.
|
|
383
|
+
|
|
384
|
+
Persistence: a new `metadata_json` column (`TEXT NOT NULL DEFAULT '{}'`)
|
|
385
|
+
is added to the `thought` table; the core schema bumps from
|
|
386
|
+
`user_version = 10` to `11` and `_migrate_core_v10_to_v11` performs
|
|
387
|
+
the additive migration with duplicate-column tolerance for safe
|
|
388
|
+
re-runs. Pre-existing rows receive the empty-dict default
|
|
389
|
+
automatically. JSON serialization uses `ensure_ascii=False` so
|
|
390
|
+
non-ASCII attribute values round-trip byte-exact.
|
|
391
|
+
|
|
392
|
+
Validation: caller-supplied metadata is checked at both API entries
|
|
393
|
+
(`create_thought` and `update_thought`). Nested or list values raise
|
|
394
|
+
`ValueError` with a per-key message. Serialized payloads above
|
|
395
|
+
~4 KiB emit a `WARNING` log; payloads above ~64 KiB are rejected
|
|
396
|
+
outright with `ValueError`.
|
|
397
|
+
|
|
398
|
+
No new public API surface beyond the field itself — downstream
|
|
399
|
+
filtering / dispatching consumers are tracked separately.
|
|
400
|
+
|
|
401
|
+
- **`DreamingConfig.max_p1_fraction` defaults to 5 % (was effectively unlimited).**
|
|
402
|
+
Dreaming consolidation now caps the fraction of corpus thoughts at priority P1
|
|
403
|
+
to `max_p1_fraction` (default `0.05`). Once the cap is reached, further
|
|
404
|
+
promotions are silently skipped for that run and `ConsolidationResult.promotion_capped`
|
|
405
|
+
is set to `True`. Motivation: empirical analysis found that
|
|
406
|
+
unrestricted promotion accumulated 29.9 % P1 thoughts, giving those entries a
|
|
407
|
+
systematic 67 % ranking boost in hybrid-search fusion.
|
|
408
|
+
|
|
409
|
+
**Upgrade impact:** existing databases with >5 % P1 will stop receiving new
|
|
410
|
+
P1 promotions until the fraction drops below the cap (via normal thought
|
|
411
|
+
expiry / lifecycle transitions). Run `python -m scripts.rebalance_p1
|
|
412
|
+
--db-path <PATH>` to immediately demote excess P1 thoughts to P2.
|
|
413
|
+
Set `extensions.dreaming.max_p1_fraction: 1.0` in YAML to restore legacy
|
|
414
|
+
unlimited behaviour.
|
|
415
|
+
|
|
416
|
+
- **`DreamingConfig.promote_targets` defaults to `"OBS_ONLY"`.** Only
|
|
417
|
+
`OBSERVATION` thoughts are now eligible for P1 promotion by default.
|
|
418
|
+
`REFLECTION` thoughts can be included by setting
|
|
419
|
+
`extensions.dreaming.promote_targets: ALL` or `REFL_ONLY`.
|
|
420
|
+
|
|
421
|
+
- **`DreamingConfig.reflection_default_priority` defaults to `"P2"`.**
|
|
422
|
+
Newly-created `REFLECTION` thoughts now start at P2 instead of inheriting
|
|
423
|
+
the highest priority of their cluster members (which was effectively P1 in
|
|
424
|
+
most stores). Configure via `extensions.dreaming.reflection_default_priority`.
|
|
425
|
+
|
|
426
|
+
- **`ConsolidationResult` gains two new fields** (zero-impact on existing
|
|
427
|
+
consumers — both default to `False` / `0.0`):
|
|
428
|
+
- `promotion_capped: bool` — True when the P1 fraction cap stopped promotion.
|
|
429
|
+
- `p1_fraction_after: float` — fraction of total corpus at P1 after the run.
|
|
430
|
+
|
|
431
|
+
- **`SqliteEngravaCore.count_thoughts()` gains a `priority` keyword filter.**
|
|
432
|
+
Allows callers to count thoughts at a specific priority level without
|
|
433
|
+
fetching full records (e.g. `await store.count_thoughts(priority="P1")`).
|
|
434
|
+
|
|
435
|
+
- **New utility `python -m scripts.rebalance_p1`** — demotes excess P1 thoughts
|
|
436
|
+
to P2 to meet the `max_p1_fraction` cap on existing databases. Idempotent,
|
|
437
|
+
supports `--dry-run`, and `--max-p1-fraction` to override the target fraction.
|
|
438
|
+
|
|
439
|
+
- **Extended `SENTENCE_STARTER_BLOCKLIST` with 11 empirical entries** from a
|
|
440
|
+
short07 NE top-15 audit (2026-05-04). 11 of the 15 most frequent
|
|
441
|
+
`named_entities` tokens in 47 REFLECTION thoughts were sentence-starter words
|
|
442
|
+
absent from the prior blocklist: `Also`, `Did`, `Embracing`, `For`,
|
|
443
|
+
`Have`, `How`, `Instead`, `Lastly`, `Not`, `Reflecting`, `Ultimately`. An
|
|
444
|
+
additional 15 common gerund/participle starters (`Conducting`, `Connecting`,
|
|
445
|
+
`Continuing`, …) are added as preventive coverage. Additive-only — no
|
|
446
|
+
existing entry removed; real proper nouns (`Alex`, `Cornell`, `1974`, …)
|
|
447
|
+
remain unblocked.
|
|
448
|
+
|
|
449
|
+
- **Structural REFLECTION content schema v2.** The dreaming extension
|
|
450
|
+
now emits a richer structural JSON when it creates a REFLECTION
|
|
451
|
+
thought from a cluster. The legacy three-field layout
|
|
452
|
+
(`member_ids`, `keywords`, `cluster_hash`) is preserved verbatim
|
|
453
|
+
for backward compatibility, and the dict gains nine additional
|
|
454
|
+
fields:
|
|
455
|
+
|
|
456
|
+
- `type` / `version` — schema-dispatch markers
|
|
457
|
+
(`"reflection"` / `2`). Legacy v1 emissions never carried these
|
|
458
|
+
keys; consumers can detect a legacy row by their absence.
|
|
459
|
+
- `member_count` / `cluster_algorithm` / `created_at` — fields
|
|
460
|
+
mandated by the cognitive-boundary REFLECTION spec but missing
|
|
461
|
+
from the previous emitter.
|
|
462
|
+
- `top_keyphrases` — TF-IDF-scored 2-3 word n-grams over the
|
|
463
|
+
cluster, with the corpus baseline supplied by the caller.
|
|
464
|
+
- `member_excerpts` — top-N members by priority + recency, each
|
|
465
|
+
truncated at the word boundary to ~80 characters.
|
|
466
|
+
- `temporal_span` — `min_created_at` / `max_created_at` plus the
|
|
467
|
+
span in days across the cluster.
|
|
468
|
+
- `named_entities` — regex-based capitalised tokens plus year /
|
|
469
|
+
measurement matches.
|
|
470
|
+
|
|
471
|
+
All enrichment is deterministic and LLM-free; the cognitive-
|
|
472
|
+
boundary CI guard test covers the new sibling modules
|
|
473
|
+
(`engrava.extensions.dreaming_keyphrases`,
|
|
474
|
+
`engrava.extensions.dreaming_reflection_content`) so an LLM SDK
|
|
475
|
+
cannot sneak in via either of them.
|
|
476
|
+
|
|
477
|
+
Two new `DreamingConfig` fields tune the structural output:
|
|
478
|
+
`top_keyphrases_count` (default 3) and `top_member_excerpts_count`
|
|
479
|
+
(default 5). Both default to safe values, are additive on the
|
|
480
|
+
dataclass, and can be overridden via the standard
|
|
481
|
+
`extensions.dreaming` YAML section.
|
|
482
|
+
|
|
483
|
+
Existing REFLECTION rows in production databases continue to read
|
|
484
|
+
correctly — the dispatch parser detects the legacy schema by the
|
|
485
|
+
absence of the `version` field. A new opt-in utility
|
|
486
|
+
`python -m scripts.reenrich_reflections_to_v2 --db-path PATH`
|
|
487
|
+
walks the database in batches and rewrites legacy v1 content to
|
|
488
|
+
v2 in place; idempotent (re-running on an already-migrated DB is
|
|
489
|
+
a no-op) with a `--dry-run` mode for previewing.
|
|
490
|
+
|
|
491
|
+
Empirical motivation: AMB PersonaMem MCQ judges score parsable
|
|
492
|
+
prose-like surface higher than terse JSON; the structural
|
|
493
|
+
enrichment closes most of that gap without crossing the no-LLM
|
|
494
|
+
cognitive boundary.
|
|
495
|
+
|
|
496
|
+
- **Structural REFLECTION content quality amendment.** The v2
|
|
497
|
+
builder gains three deterministic quality fixes layered on top of
|
|
498
|
+
the existing schema (no key changes — only field VALUES improve):
|
|
499
|
+
|
|
500
|
+
- **Sentence-starter blocklist for `named_entities`.** Common
|
|
501
|
+
capitalised non-entity words (`Absolutely`, `However`,
|
|
502
|
+
`Therefore`, `Furthermore`, `User`, `Assistant`, `System`, …)
|
|
503
|
+
no longer pollute the entity list. The blocklist is exposed
|
|
504
|
+
publicly as `SENTENCE_STARTER_BLOCKLIST` from
|
|
505
|
+
`engrava.extensions.dreaming_keyphrases` for downstream
|
|
506
|
+
consumers that want the same filter. Real proper nouns
|
|
507
|
+
(`Cornell`, `Berlin`, `Anthropic`, …) are unaffected.
|
|
508
|
+
- **Role-marker stripping before keyphrase extraction.**
|
|
509
|
+
`[USER] User: …` / `[ASSISTANT] Assistant: …` / `[SYSTEM] …`
|
|
510
|
+
prefixes are stripped before tokenisation, so artefact tokens
|
|
511
|
+
like `"user user"` or `"assistant"` no longer surface as
|
|
512
|
+
top-ranked keyphrases or simple keywords.
|
|
513
|
+
- **Member excerpt size raised 80 → 150 characters** and made
|
|
514
|
+
configurable via the new `DreamingConfig.member_excerpt_max_chars`
|
|
515
|
+
field (positive integer; YAML key
|
|
516
|
+
`extensions.dreaming.member_excerpt_max_chars`). The bump
|
|
517
|
+
gives downstream LLM judges a meaningfully longer window into
|
|
518
|
+
each member while keeping the cluster content well within the
|
|
519
|
+
2 KB structural budget.
|
|
520
|
+
|
|
521
|
+
All three changes are deterministic, LLM-free, and additive — the
|
|
522
|
+
v2 dispatch parser, the 12 v2 schema keys, and existing v1
|
|
523
|
+
consumers are unchanged.
|
|
524
|
+
|
|
525
|
+
- **Opt-in content-hash deduplication on `create_thought`.** The
|
|
526
|
+
persistence layer now exposes a new keyword-only argument
|
|
527
|
+
`deduplicate: bool = False` on
|
|
528
|
+
`SqliteEngravaCore.create_thought`. When `True`, identical
|
|
529
|
+
`content` (SHA-256 hash collision over the UTF-8 bytes, no
|
|
530
|
+
normalization) collapses into a single thought whose
|
|
531
|
+
`confirmation_count` is incremented and `updated_at` refreshed,
|
|
532
|
+
instead of producing a duplicate row. Default behavior is
|
|
533
|
+
unchanged (`deduplicate=False` preserves the legacy create-on-every-
|
|
534
|
+
call semantic).
|
|
535
|
+
|
|
536
|
+
Configuration: a new top-level `IngestConfig` value object exposes
|
|
537
|
+
`deduplication_enabled: bool = True`, accessible via
|
|
538
|
+
`EngravaConfig.ingest`. YAML callers can flip it via
|
|
539
|
+
```yaml
|
|
540
|
+
ingest:
|
|
541
|
+
deduplication_enabled: false
|
|
542
|
+
```
|
|
543
|
+
Ingest-pipeline callers (e.g. benchmark adapters, bulk-import
|
|
544
|
+
tooling) should read `config.ingest.deduplication_enabled` and pass
|
|
545
|
+
it through to `create_thought(..., deduplicate=...)`.
|
|
546
|
+
|
|
547
|
+
Schema: the `thought` table gains a nullable `content_hash TEXT`
|
|
548
|
+
column and a supporting `idx_thought_content_hash` index.
|
|
549
|
+
`PRAGMA user_version` is bumped from 9 to 10. A new
|
|
550
|
+
`_migrate_core_v9_to_v10` helper participates in the existing
|
|
551
|
+
`ensure_schema` upgrade cascade, so DBs at any prior supported
|
|
552
|
+
version (v3 through v9) upgrade in a single call. The migration
|
|
553
|
+
is idempotent (ALTER TABLE tolerates duplicate-column errors,
|
|
554
|
+
CREATE INDEX uses `IF NOT EXISTS`).
|
|
555
|
+
|
|
556
|
+
Backfill: pre-v10 thoughts retain `content_hash IS NULL` until the
|
|
557
|
+
bundled `scripts/backfill_content_hashes.py` utility populates them
|
|
558
|
+
in batches; running benchmarks against a freshly-bootstrapped DB
|
|
559
|
+
(e.g. AMB PersonaMem fixtures) is unaffected because the column is
|
|
560
|
+
filled on every insert.
|
|
561
|
+
|
|
562
|
+
Empirical motivation: AMB PersonaMem benchmark runs (with full
|
|
563
|
+
ingest tracing, two independent runs in DREAM and NODREAM modes)
|
|
564
|
+
reproduced ~38.5% duplicate observation thoughts, with persona
|
|
565
|
+
intros multiplied 12-13x per session; clustering and
|
|
566
|
+
reflection-of-duplicates pollution amplified the waste downstream.
|
|
567
|
+
|
|
568
|
+
### Breaking Changes
|
|
569
|
+
|
|
570
|
+
- **Removed observability hook surface from the public package.** The
|
|
571
|
+
`EngravaObservabilityHooksProtocol`, `ObservabilityDispatcher`,
|
|
572
|
+
`DefaultObservabilityHooks`, `ObservabilityGates`, all 12 event
|
|
573
|
+
dataclasses (`QueryStartEvent`, `QueryEndEvent`, `CandidatesFusedEvent`,
|
|
574
|
+
`CandidateRecord`, `IngestStartEvent`, `IngestEndEvent`,
|
|
575
|
+
`ThoughtCreatedEvent`, `EmbeddingComputedEvent`, `EdgeCreatedEvent`,
|
|
576
|
+
`CycleStartEvent`, `CycleEndEvent`, `ClusterDecisionEvent`,
|
|
577
|
+
`ReflectionCreatedEvent`), the `register_observability_hook()` method
|
|
578
|
+
on the store, and the `obs_dispatcher` / `obs_gates` constructor
|
|
579
|
+
parameters are gone. The `observability:` section in the YAML config
|
|
580
|
+
is no longer parsed.
|
|
581
|
+
|
|
582
|
+
- **`benchmarks/` directory removed** — benchmark runners and their
|
|
583
|
+
adapters / checkpointing / LLM-judge helpers are no longer included
|
|
584
|
+
in the open-source distribution. A public reproducibility benchmark
|
|
585
|
+
remains available via `python -m engrava.benchmarks.synthetic`
|
|
586
|
+
(see `docs/benchmarks.md`).
|
|
587
|
+
|
|
588
|
+
### Database Changes
|
|
589
|
+
|
|
590
|
+
- Upgrade-path validation is now part of release preparation for minor bumps.
|
|
591
|
+
- Automatic schema migration still runs on first connection via `ensure_schema()`.
|
|
592
|
+
- Releases that change schema behavior must document the change here explicitly.
|
|
593
|
+
|
|
594
|
+
### Changed
|
|
595
|
+
|
|
596
|
+
- **Search: `default_graph_weight` reverted to `0.0`** — graph-neighbour signal
|
|
597
|
+
disabled by default. Empirical evidence from two independent AMB PersonaMem
|
|
598
|
+
benchmark runs showed `graph_weight=0.3` caused a −8 pp accuracy regression
|
|
599
|
+
(Chi² p=0.045). The score-adjustment mode cascade promotes short REFLECTION
|
|
600
|
+
summaries over detail-rich source OBSERVATIONs. Graph signal remains available
|
|
601
|
+
as an explicit opt-in via `search.default_graph_weight` in YAML config.
|
|
602
|
+
Until candidate-expansion via `CONSOLIDATED_FROM` ships, the graph backend
|
|
603
|
+
stays disabled.
|
|
604
|
+
|
|
605
|
+
### Added
|
|
606
|
+
|
|
607
|
+
- **`store.metrics()` snapshot API** — added `EngravaMetrics`
|
|
608
|
+
with `thoughts`, `edges`, `storage`, and rolling-window `search_latency`
|
|
609
|
+
percentiles (`p50` / `p95` / `p99`). `engrava info` now renders this
|
|
610
|
+
contract instead of ad-hoc SQL counts, and the new `metrics:` YAML section
|
|
611
|
+
configures the latency window size and opt-out behaviour.
|
|
612
|
+
|
|
613
|
+
- **Dreaming — clustering + REFLECTION thoughts** — `run_consolidation()`
|
|
614
|
+
now builds thought clusters from the ASSOCIATED edge graph (Label Propagation
|
|
615
|
+
Algorithm, with agglomerative cosine-similarity fallback) and creates
|
|
616
|
+
`ThoughtType.REFLECTION` thoughts that summarise each qualifying cluster.
|
|
617
|
+
A centroid embedding (mean of member vectors, L2-normalised) is stored for
|
|
618
|
+
each REFLECTION. `CONSOLIDATED_FROM` edges link the REFLECTION back to each
|
|
619
|
+
cluster member. Idempotent: re-runs skip clusters whose content-hash already
|
|
620
|
+
exists. Opt-out via `DreamingGates.enable_reflections = False`.
|
|
621
|
+
`ConsolidationResult` gains a new `reflections_created` counter.
|
|
622
|
+
- **`DreamingGates` cluster fields** — `min_cluster_size` (default `3`),
|
|
623
|
+
`cluster_similarity_threshold` (default `0.7`), `cluster_algorithm`
|
|
624
|
+
(`"lpa"` | `"agglomerative"`, default `"lpa"`), and `enable_reflections`
|
|
625
|
+
(default `True`).
|
|
626
|
+
- **`search_hybrid()` — `include_reflections` + `reflection_boost`** — callers
|
|
627
|
+
can now pass `include_reflections=False` to exclude REFLECTION thoughts from
|
|
628
|
+
hybrid search results, or a custom `reflection_boost` multiplier to re-rank
|
|
629
|
+
them. The default boost (`SearchConfig.reflection_boost = 1.2`) gives
|
|
630
|
+
REFLECTION thoughts a mild up-ranking.
|
|
631
|
+
- **`search_reflections_only()`** — convenience method on `SqliteEngravaCore`
|
|
632
|
+
that returns only `ThoughtType.REFLECTION` thoughts ranked by hybrid score.
|
|
633
|
+
- **`list_edges()`** — new `SqliteEngravaCore` method for querying edges by
|
|
634
|
+
optional `edge_type` / `source` filters with configurable `limit`.
|
|
635
|
+
- **`SearchConfig.reflection_boost`** — new field (default `1.2`) parsed from
|
|
636
|
+
YAML `search.reflection_boost`.
|
|
637
|
+
|
|
638
|
+
- **Dream-created edges** — `run_consolidation()` now creates
|
|
639
|
+
`ASSOCIATED` edges between promoted thoughts and their nearest neighbours.
|
|
640
|
+
Controlled via `DreamingConfig.edges` (`EdgeCreationConfig`). Edges use
|
|
641
|
+
`source=KnowledgeSource.DREAMING` for attribution. Idempotent: re-runs do
|
|
642
|
+
not create duplicate edges.
|
|
643
|
+
- **Graph-aware hybrid search** — `search_hybrid()` supports an
|
|
644
|
+
optional 5th scoring signal (`graph_weight`) using 1-hop-weighted neighbour
|
|
645
|
+
boost. Disabled by default (`graph_weight=0.0`, opt-in). Controlled via
|
|
646
|
+
`SearchConfig.default_graph_weight`, `graph_edge_decay`, and
|
|
647
|
+
`max_neighbors_per_candidate`.
|
|
648
|
+
- **`KnowledgeSource.DREAMING`** — new enum value for dream-originated edges.
|
|
649
|
+
- **`EdgeCreationConfig`** — frozen dataclass for edge-creation parameters
|
|
650
|
+
(`enabled`, `top_k`, `min_similarity`, `edge_weight_factor`).
|
|
651
|
+
- **Priority signal in hybrid search** — `search_hybrid()` now supports an optional
|
|
652
|
+
4th scoring signal based on thought `priority` (P1–P4). Controlled via
|
|
653
|
+
`SearchConfig.default_priority_weight` (default `0.05`) and per-priority boost
|
|
654
|
+
multipliers (`priority_boost_p1` through `priority_boost_p4`). Higher-priority
|
|
655
|
+
thoughts receive a proportionally higher score contribution.
|
|
656
|
+
- **`DreamingGates.allow_zero_confirmation`** — new boolean field (default `True`)
|
|
657
|
+
that bypasses the `min_confirmations` gate, allowing freshly ingested thoughts
|
|
658
|
+
with zero confirmations to be eligible for dreaming promotion.
|
|
659
|
+
- `examples/config.yaml` — out-of-the-box configuration with dreaming enabled and
|
|
660
|
+
sensible defaults.
|
|
661
|
+
- **Upgrade documentation and release template** — added `docs/upgrade.md`,
|
|
662
|
+
`.github/release-notes-template.md`, and a README upgrade entry so release
|
|
663
|
+
communication now has a stable place for compatibility guidance.
|
|
664
|
+
|
|
665
|
+
### Changed
|
|
666
|
+
|
|
667
|
+
- **`DreamingGates.min_age_cycles`** default changed from `10` to `1`. Freshly
|
|
668
|
+
ingested thoughts become eligible for consolidation after a single cycle instead
|
|
669
|
+
of ten.
|
|
670
|
+
- **`SearchConfig.default_vector_weight`** changed from `0.6` to `0.55` to
|
|
671
|
+
accommodate the new priority signal while keeping weights summing to `1.0`.
|
|
672
|
+
|
|
673
|
+
### Fixed
|
|
674
|
+
|
|
675
|
+
- **Dreaming consolidation now actually runs on fresh batches.** Previously, the
|
|
676
|
+
`min_confirmations=2` gate combined with `min_age_cycles=10` prevented any
|
|
677
|
+
promotion in single-write batch-ingest scenarios (confirmation count = 0, age = 0).
|
|
678
|
+
With `allow_zero_confirmation=True` (new default) and `min_age_cycles=1`, a
|
|
679
|
+
single `run_consolidation()` call on a fresh batch can now promote qualifying
|
|
680
|
+
thoughts.
|
|
681
|
+
|
|
682
|
+
## [0.2.0] — 2026-04-12
|
|
683
|
+
|
|
684
|
+
### Breaking Changes
|
|
685
|
+
|
|
686
|
+
- None.
|
|
687
|
+
|
|
688
|
+
### Database Changes
|
|
689
|
+
|
|
690
|
+
- Schema version bumped to core-5.
|
|
691
|
+
- Added `access_count`, `last_accessed_at`, `confirmation_count`,
|
|
692
|
+
`consolidated_from`, and `visibility` to the thought table.
|
|
693
|
+
- Existing databases upgrade automatically through `ensure_schema()`.
|
|
694
|
+
|
|
695
|
+
### Added
|
|
696
|
+
|
|
697
|
+
- **Full-text search (FTS5)** — `search_fts()` method with BM25 ranking on `essence`
|
|
698
|
+
and `content` fields. Hybrid search combines vector similarity, text relevance, and
|
|
699
|
+
recency scoring via configurable `SearchConfig` weights.
|
|
700
|
+
- **Extension system** — `EngravaHooksProtocol` with 5 hook points (`on_store`,
|
|
701
|
+
`on_retrieve`, `score_function`, `decay_function`, `mindql_extension_registry`).
|
|
702
|
+
`DefaultEngravaHooks` provides no-op defaults.
|
|
703
|
+
- **Dreaming / memory consolidation** — `DreamingExtension` with 5 pluggable signal
|
|
704
|
+
types (`ConfidenceSignal`, `ConfirmationSignal`, `FrequencySignal`, `RecencySignal`,
|
|
705
|
+
`StalenessSignal`) and configurable gate thresholds.
|
|
706
|
+
- **sqlite-vec backend** — optional `SqliteVecSearchBackend` for hardware-accelerated
|
|
707
|
+
vector search via the `vec` extra.
|
|
708
|
+
- **Multi-service isolation** — `EngravaManager` for running multiple independent
|
|
709
|
+
databases, each with its own schema, embeddings, and FTS index.
|
|
710
|
+
- **YAML configuration** — `load_config()` factory, `EngravaConfig` with `SearchConfig`,
|
|
711
|
+
`DreamingConfig`, `EmbeddingConfig`, and `ServicesConfig` sections.
|
|
712
|
+
- **5 embedding providers** — `SentenceTransformerProvider`, `OpenAICompatibleProvider`,
|
|
713
|
+
`OllamaProvider`, `HuggingFaceProvider`, `CallbackProvider`.
|
|
714
|
+
- **MindQL enhancements** — `COUNT`, `SELECT` with `WHERE` clauses, extensible command
|
|
715
|
+
registry via hooks.
|
|
716
|
+
- **CLI enhancements** — `export`, `import`, `gc`, `migrate` subcommands. Multi-service
|
|
717
|
+
`--service` flag for `snapshot`/`restore`/`export`.
|
|
718
|
+
- **Read-only store** — `ReadOnlyEngrava` wrapper that raises `ReadOnlyViolationError`
|
|
719
|
+
on write attempts.
|
|
720
|
+
- **`ExtensionManifest`** value object for extension discovery and registration.
|
|
721
|
+
- **`HybridSearchResult`** model combining vector, FTS, and recency scores.
|
|
722
|
+
- **`EmbeddingModelMismatchError`** exception for restore-time model validation.
|
|
723
|
+
- Open source release: standalone repository, MIT license, GitHub Actions CI/CD,
|
|
724
|
+
PyPI publishing.
|
|
725
|
+
|
|
726
|
+
### Changed
|
|
727
|
+
|
|
728
|
+
- Bumped version from 0.1.0 to 0.2.0.
|
|
729
|
+
- `pyproject.toml` URLs now point to the standalone GitHub repository.
|
|
730
|
+
- Description updated to "Thought-graph database for AI agents".
|
|
731
|
+
|
|
732
|
+
### Fixed
|
|
733
|
+
|
|
734
|
+
- `--re-embed` flag now raises an error when no embedding provider is configured
|
|
735
|
+
instead of silently succeeding.
|
|
736
|
+
- `snapshot --service` validates service existence before attempting export.
|
|
737
|
+
- `EngravaManager.get_store()` uses `asyncio.Lock` to prevent race conditions
|
|
738
|
+
during concurrent lazy initialization.
|
|
739
|
+
|
|
740
|
+
## [0.1.0] — 2026-04-01
|
|
741
|
+
|
|
742
|
+
### Breaking Changes
|
|
743
|
+
|
|
744
|
+
- Initial release.
|
|
745
|
+
|
|
746
|
+
### Database Changes
|
|
747
|
+
|
|
748
|
+
- Initial SQLite schema introduced.
|
|
749
|
+
- No downgrade guarantees; follow forward-only upgrade policy from later releases.
|
|
750
|
+
|
|
751
|
+
### Added
|
|
752
|
+
|
|
753
|
+
- Initial release (internal).
|
|
754
|
+
- `SqliteEngravaCore` — async thought/edge/embedding/action CRUD.
|
|
755
|
+
- `ThoughtRecord`, `EdgeRecord`, `EmbeddingRecord`, `ActionRecord` frozen Pydantic models.
|
|
756
|
+
- 9 domain enums (`ThoughtType`, `Priority`, `LifecycleStatus`, `EdgeType`, etc.).
|
|
757
|
+
- Brute-force cosine similarity embedding search.
|
|
758
|
+
- `MindQLParser` and `MindQLExecutor` — `FIND` and basic query support.
|
|
759
|
+
- CLI with `info`, `query`, `snapshot`, `restore` subcommands.
|
|
760
|
+
- Schema migration support via `ensure_schema()`.
|