Releases: draca-glitch/Mnemos
Release list
v10.24.0 archive-side embedding lifecycle: no more tier-2 leaks
Driven by a forensic audit of the prod embedding-row lifecycle (the open item
from the 10.19.0 coherence check's first prod run): 51 archived memories had
no tier-2 vector, clustered exactly on consolidation days; 3 orphan arch rows
survived hard-deleted memories; embed_meta tables created before the
localtime DDL still carried a UTC embedded_at default, making synchronous
embeds look hours older than their memory's creation.
Fixed
- Consolidation archived memories without moving their vectors. Both raw
status-UPDATE sites in phases (merge, supersede) now go through a new
archive_memory(conn, mid, tag_suffix)helper that archives and moves the
vector into the tier-2 index in one step.cleanup_orphan_vectorsremains
as the end-of-cycle safety net. - Soft delete now moves the vector to the tier-2 index instead of
leaving it in the active index (delete_memory(hard=False)); the move
never blocks the archive itself. - Hard delete purges tier-2 rows too.
delete_memory(hard=True)used to
stop at the active index, leaving embed_meta_arch/embed_vec_arch rows
behind forever. - Doctor: UTC embedded_at dialect detected and migrated. Tables whose DDL
predates the localtime default are rebuilt with the canonical schema and
existing values converted viadatetime(x, 'localtime')(DST-correct,
detected from sqlite_master so the check needs no marker and is
idempotent). Pre-migration backup taken. - Doctor: orphan tier-2 rows detected and cleaned (
--migrate), plus
advisory counts for archived memories without a vector and rows with
legacy pre-canonical hashes, both pointing atreindex-archived.
Added
reindex-archivedre-embeds legacy-hash rows: tier-2 meta rows with a
pre-v10.6 truncated (16-char) hash were embedded under a pre-canonical
formula; they are re-embedded and re-hashed, reported as
legacy_reembedded. Backfill of missing vectors unchanged and idempotent.embed_vec_archis created with the declared-PK schema
(vec0(id INTEGER PRIMARY KEY, ...)) on new stores, matching what the
active side already handles; existing implicit-rowid arch tables keep
working through a new_arch_join_coldetection used by every arch-side
query (store, search, move, cleanup, doctor). No rebuild of existing
tables: both schemas are supported in the wild, same policy as embed_vec.- Conn-level lifecycle helpers
move_embedding_to_archive_conn/
_store_archived_embedding_connshared by the store methods, the
consolidation phases and external plumbing. - 9 new tests (273 total).
v10.23.0 doctor flags an empty store instead of blessing it
Added
- Empty-store detection in
doctor: a store with 0 active memories for the
resolved namespace is reported as an issue with a config hint instead of
"healthy". A doctor run against the wrong MNEMOS_DB path or a mismatched
MNEMOS_NAMESPACE previously passed every check vacuously (each check
verified nothing and reported ok), which let a false all-clear survive
during the 2026-07-05 embed-formula incident. Two hint variants: a fully
empty database points at MNEMOS_DB (expected once for a brand-new store),
and a database whose memories all live in other namespaces lists the
per-namespace counts and points at MNEMOS_NAMESPACE. Populated stores gain
a "Store populated: N active memories" check line. No auto-fix on purpose:
an empty store has exactly two causes, fresh install (nothing to fix) or
misconfiguration (the fix is the caller's env, and guessing where the real
data lives is how a tool initializes an empty store at a wrong path and
buries the actual problem). 4 new tests; 264 pass.
v10.22.0 embed-text excludes Nyx bookkeeping tags; coherence stays green
Fixed
- prep_memory_text folded ALL tags into the canonical embed-text, but Nyx
rewrites bookkeeping tags (consolidated, nyx-split, merged-into-,
split-from-, split-part-*) on every consolidation cycle. Because the
coherence hash is computed over the embed-text, every memory that had
ever been consolidated reported as content/vector mismatched (observed:
723/723 on the production store) anddoctorrecommended a full
re-embed that would just recur on the next Nyx run. The embed-text now
excludes those churning, retrieval-irrelevant tags via stable_tags(),
so coherence stays green across consolidation; semantic tags are kept.
Adopting it requires a one-time re-embed of the active set (done on
Epsilon: 723 verified, stale=0). 260 tests pass unchanged.
v10.21.0 contradiction judge gains an UNRELATED verdict
Fixed
- The phase-4 contradiction judge presumed its two inputs were about the
same topic (the prompt asserted "these memories are about similar
topics") and offered only SUPERSEDED / EVOLVED / CONTRADICTS /
COMPATIBLE. But candidacy is exhaustive same-project: the cosine floor
(CONTRADICT_MIN_SIM=0.60) is inert on multilingual-e5-large, where a
measured 45% of all active pairs clear 0.78, so the judge is routinely
handed same-project pairs about entirely different subjects. With no
way to say "these are unrelated", the model scope-conflated shared
vocabulary into false CONTRADICTS (observed: a hardened-VPS firewall
memory flagged as contradicting an on-prem office LAN memory). The
prompt now decides same-subject first and can answer UNRELATED;
unrelated pairs are tombstoned as contradiction-cleared so they never
re-enter the finder/judge loop. Validated by A/B on real
same-project-different-subject pairs plus unambiguous contradiction
controls: UNRELATED cleanly separates different subjects with zero
regression on true contradictions (RAM 64 vs 32, nginx vs Apache,
blood type, backup time all still flagged). Also replaced the ambiguous
"weekly vs daily backup" CONTRADICTS example (those coexist) with a
same-job time clash.
v10.20.0 phase 0.5 removed; Nyx is namespace-scoped
Removed
- Phase 0.5 (Cemelify) no longer exists in the Nyx cycle. Rewriting
already-stored memories every night is generation against content
whose fidelity is the product: it drifted exact strings on weaker
models, was disabled on every known deployment, and ran ungated by
the phase list, so a zero-LLM--phases 1,2,4,6run on a
key-configured host still fired LLM calls (observed in the field:
28x 401 against a stale key).MNEMOS_NYX_CEMELIFYis gone;
cemelify()itself remains for ingest-time shaping of NEW content.
Stored content is never rewritten in place.
Fixed
- Nyx loaders are namespace-scoped:
load_embeddingsand
load_memory_metaread only the active namespace (default: the same
resolution as every write path; both accept an explicitnamespace=).
Since v10.6.0 the write side was namespace-correct but reads saw every
namespace in the DB file, so on a multi-tenant deployment phase 2
could cluster tenant A's memories with tenant B's, merge them into
the current namespace and archive both originals. Found in the field
on a second deployment; single-namespace stores were never affected.
v10.19.0 doctor verifies content/vector coherence
Added
- Doctor check: every active memory's embed-text hash is re-derived from
current content and compared toembed_meta.text_hash(recorded at
embed time since v10.6 "so staleness is detectable later"; this is the
later). Catches content mutated without a re-embed: direct SQL writes
bypassing the API, or a write-path bug, both previously invisible to
retrieval and to every other check.doctor --migratere-embeds
flagged rows. Near-total mismatch on stores of 20+ checkable rows is
reported as an embed-text format change across versions rather than
row corruption; rows predating hash tracking are skipped.
v10.18.0 memoized NLI finder: the nightly sweep stops re-proving old negatives
Added
nli_scan_cache: the phase-4 finder's line-level score is a pure
function of the two contents, so it is memoized keyed on content
hashes. Unchanged pairs cost nothing on later runs; a changed hash
invalidates exactly that pair.MNEMOS_NLI_FINDER_MAX_PAIRSnow
budgets only NEW scorings, and never-scored pairs backfill across
subsequent nights. Measured before the cache on the production store:
~185 static pairs re-scored for ~31 minutes, nightly; steady state
after: seconds, proportional to the day's new memories. Dry runs read
the cache but never write it. Phase 6 drops rows referencing archived
memories (cleanup_scan_cache, mirroring stale-link cleanup).
v10.17.0 zero-LLM daily consolidation cycle
The daily Nyx cycle now runs with zero LLM calls: cosine nominates (by
rank), NLI decides (admission gate + line-level dedup), a mechanical union
executes merges (selection, never generation). LLM-requiring work (weave,
synthesize, contradiction judging, cemelify) is the optional enrichment
tier. Evidence: benchmarks/weave-bench (NLI weave classification refuted
at 3% agreement; the phase-2 gate validated on production noise clusters)
and benchmarks/merge-bench (mechanical merge: 24/25 exact recovery on
ground truth, 100% line coverage and digit integrity by construction).
Added
- Mechanical merge engine (
mnemos/consolidation/mechanical.py), the
phase-2 default (MNEMOS_MERGE_ENGINE=mechanical|llm). Line union with
bidirectional-entailment dedup atMNEMOS_MECH_MERGE_TAU(0.90; the one
observed semantic false-duplicate scored 0.851, true duplicates ~1.0).
Lines underMNEMOS_MECH_MERGE_MIN_LINE_CHARS(25) dedup by exact match
only (short enumerated list lines are an NLI failure class). Newer
phrasing wins; every output line is an input line verbatim, so fact
preservation is provable rather than auditable. - Phase-2 NLI admission gate (
MNEMOS_CLUSTER_GATE=nli|off, tau
MNEMOS_CLUSTER_GATE_TAU0.70): cluster members must share at least one
line-level bidirectional-entailment fact or they are ejected; clusters
without shared facts dissolve unmerged. Replayed on the 2026-07-03
production run: both cosine noise clusters dissolve entirely. - Mutual top-k candidacy (
MNEMOS_CANDIDACY=mutual-topk|threshold, k via
MNEMOS_CANDIDACY_TOP_K): phase-2 pair candidacy from mutual
nearest-neighbor rank instead of absolute cosine thresholds, which are
noise in the compressed e5 space (measured: 45% of all active-pair
similarities above 0.78). - Phase-4 judge queue mode (
MNEMOS_CONTRADICT_JUDGE=llm|queue|auto):
keyless runs record NLI-flagged pairs ascontradiction-candidate
links; the next llm-judged run consumes the queue.autoresolves by
key presence. - Weave staleness guard: memories with outgoing
superseded_by/evolves
links are excluded as weave sources (stale state was being woven into
fresh insights). - Weave novelty gate (
MNEMOS_WEAVE_NOVELTY_TAU, 0.85): a bridge insight
entailed by either source alone is a restatement; the link is kept, the
insight memory is not stored. - Useful-loop:
get()on a memory that retrieval logging recorded in the
last 24h marks thoseretrieval_logrowsuseful=1. Zero-friction
usefulness signal for measuring consolidation value. nli.line_max_duplicateandnli.p_entailmentpublic scorers.
Changed
- Bridge insights are stored on the episodic layer: derivative content
earns permanence through retrieval instead of squatting in the semantic
tier. - Phase 2B topic merge is retired under the mechanical engine
(aggregating distinct same-topic facts is generative LLM-tier work);
the legacy llm engine keeps both tiers. - A missing LLM key no longer fails the cycle (replaces the v10.4.0
loud-fail): LLM-tier phases are skipped with a grep-able WARNING and
the zero-LLM phases run.MNEMOS_DISABLE_LLM=1still opts into the
same skip silently. Phase 0.5 cemelify additionally requires a key.
Fixed
tests/test_v107_tier2.pyfixture wrote to the developer's default DB
whenever another test module imported mnemos first (DEFAULT_DB_PATH is
frozen at import time); 419 fixture memories had accumulated and the
tier-2 recall assert started flaking on KNN ties. The store is now
constructed explicitly.
v10.16.0 ONNX backend for the NLI layer; self-healing temperature rejection
Added
- ONNX backend for the NLI decision layer, preferred over torch when a local export exists. Runtime is onnxruntime (already in the dependency tree via FastEmbed) plus the transformers tokenizer, no torch: the
mnemos[nli]extra shrinks from a multi-GB torch pull toonnxruntime + transformers + sentencepiece. Models are exported once withscripts/export_nli_onnx.py(tooling extra:mnemos[nli-export]) intoMNEMOS_NLI_ONNX_DIR(default~/.cache/mnemos/nli-onnx/{en,multi}), or copied between machines.MNEMOS_NLI_BACKENDpinsauto/onnx/torch. - Parity gate results (114 nli-bench pairs, both models): ONNX fp32 is score-identical to torch, max probability drift 1e-05, identical AUC to 4 decimals, zero threshold flips. int8 dynamic quantization was REJECTED by the same gate: it collapses DeBERTa-v3 to chance (contradiction AUC 0.94 -> 0.51 English, 0.84 -> 0.48 multilingual) and was not even reliably faster on CPU. The layer ships fp32-only; the torch scorer remains as fallback (
mnemos[nli-torch]). chat()self-heals on temperature-rejecting models: a 400 namingtemperaturestrips the parameter, retries immediately, and remembers the (endpoint, model) pair for the process lifetime. Nyx phases with hardcoded temperatures now work against such models with no configuration;MNEMOS_LLM_OMIT_TEMPERATURE[_<PHASE>]remains as an explicit override that skips even the first probe.
v10.15.0 NLI decision layer: entailment-based dedup confirm, contradiction detection, Nyx phase-4 finder
Replaces the cross-encoder reranker for the store DECISION questions (is this a duplicate? does this contradict?) with natural-language-inference models. A reranker scores topicality ("same topic?"); NLI scores polarity ("same claim? opposite claim?"), which is the question the store layer actually asks. Backed by a 114-pair benchmark on real production memories (benchmarks/nli-bench): contradiction AUC 0.939 vs 0.69 for the reranker (which produced ~40 false positives of 96 negatives at its best threshold); dedup AUC 0.983 with 1 false positive vs 16-21 false blocks for the raw vec-distance blocker. The reranker keeps its search-ranking role, where topicality is the right signal.
Added
mnemos/nli.py: NLI scoring layer. Language-agnostic routing: content that reads as English (cheap stopword heuristic,is_english()) uses an English ANLI+FEVER-hardened checkpoint (strongest benched); everything else uses a multilingual XNLI checkpoint (~100 languages).p_contradiction()takes the max over both premise/hypothesis directions (real contradictions score asymmetrically: 0.44 one way, 0.99 the other on the bench);bidirectional_entailment()takes the min (a duplicate entails in both directions);line_max_contradiction()scores the top-k cosine-preselected line pairs of two records, rescuing conflicts that blob-level scoring buries (benched: a diagnosis conflict scored 0.58 blob-level, 0.9956 line-level).MNEMOS_DEDUP_CONFIRM=nli: store-path dedup confirm tier. Bidirectional entailment >=MNEMOS_NLI_DEDUP_THRESHOLD(default 0.85) on the topMNEMOS_NLI_DEDUP_MAX_CANDIDATES(default 3) candidates by vector distance blocks the store; below it the store proceeds with no fall-through to the coarser scorers. Legacy behavior unchanged when unset or when the NLI runtime is unavailable.MNEMOS_CONTRADICT_MODE=nli: contradiction detection asks the NLI question directly after the vec gate. Warn +contradictslink only at max-direction P(contra) >=MNEMOS_NLI_CONTRA_THRESHOLD(default 0.98). No relates band: WEAVE owns topical linking.MNEMOS_NYX_CONTRADICT_FINDER=nli: phase-4 candidate finder. Drops the legacy cosine-band CEILING (near-identical pairs are where real contradictions live) and scores floor-gated pairs with the line-level finder, recall-first (MNEMOS_NLI_FINDER_THRESHOLD, default 0.8, capped atMNEMOS_NLI_FINDER_MAX_PAIRSpairs); the existing LLM judge keeps precision. The three benched real contradictions the blob/band approach missed are all caught by this path.- Optional dependency extra
mnemos[nli](torch, transformers, sentencepiece). Every NLI entry point degrades gracefully when the extra is not installed. tests/test_v1015_nli.py: 19 tests (routing, direction aggregation, store integrations, phase-4 selection), model-free via stub scorers.
Changed
- Phase-4 cosine gates
CONTRADICT_MIN_SIM/CONTRADICT_MAX_SIMmoved fromconsolidation/phases.pytoconstants.py(env-overridable); all new NLI tunables live inconstants.pyas the single settings surface. - Phase-4 pair selection extracted into
select_contradict_candidates()(pure, testable). scripts/mnemos_sortkit.pyno longer defaults to a deployment-specific namespace.