Skip to content

Releases: draca-glitch/Mnemos

v10.24.0 archive-side embedding lifecycle: no more tier-2 leaks

Choose a tag to compare

@draca-glitch draca-glitch released this 07 Jul 22:03

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_vectors remains
    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 via datetime(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 at reindex-archived.

Added

  • reindex-archived re-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_arch is 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_col detection 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_conn shared 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

Choose a tag to compare

@draca-glitch draca-glitch released this 07 Jul 22:03

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

Choose a tag to compare

@draca-glitch draca-glitch released this 07 Jul 22:03

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) and doctor recommended 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

Choose a tag to compare

@draca-glitch draca-glitch released this 07 Jul 22:03

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

Choose a tag to compare

@draca-glitch draca-glitch released this 03 Jul 14:16

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,6 run on a
    key-configured host still fired LLM calls (observed in the field:
    28x 401 against a stale key). MNEMOS_NYX_CEMELIFY is 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_embeddings and
    load_memory_meta read only the active namespace (default: the same
    resolution as every write path; both accept an explicit namespace=).
    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

Choose a tag to compare

@draca-glitch draca-glitch released this 03 Jul 12:17

Added

  • Doctor check: every active memory's embed-text hash is re-derived from
    current content and compared to embed_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 --migrate re-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

Choose a tag to compare

@draca-glitch draca-glitch released this 03 Jul 12:17

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_PAIRS now
    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

Choose a tag to compare

@draca-glitch draca-glitch released this 03 Jul 12:17

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 at MNEMOS_MECH_MERGE_TAU (0.90; the one
    observed semantic false-duplicate scored 0.851, true duplicates ~1.0).
    Lines under MNEMOS_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_TAU 0.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 as contradiction-candidate
    links; the next llm-judged run consumes the queue. auto resolves 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 those retrieval_log rows useful=1. Zero-friction
    usefulness signal for measuring consolidation value.
  • nli.line_max_duplicate and nli.p_entailment public 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=1 still opts into the
    same skip silently. Phase 0.5 cemelify additionally requires a key.

Fixed

  • tests/test_v107_tier2.py fixture 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

Choose a tag to compare

@draca-glitch draca-glitch released this 03 Jul 12:17

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 to onnxruntime + transformers + sentencepiece. Models are exported once with scripts/export_nli_onnx.py (tooling extra: mnemos[nli-export]) into MNEMOS_NLI_ONNX_DIR (default ~/.cache/mnemos/nli-onnx/{en,multi}), or copied between machines. MNEMOS_NLI_BACKEND pins auto/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 naming temperature strips 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

Choose a tag to compare

@draca-glitch draca-glitch released this 03 Jul 12:17

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 top MNEMOS_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 + contradicts link 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 at MNEMOS_NLI_FINDER_MAX_PAIRS pairs); 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_SIM moved from consolidation/phases.py to constants.py (env-overridable); all new NLI tunables live in constants.py as the single settings surface.
  • Phase-4 pair selection extracted into select_contradict_candidates() (pure, testable).
  • scripts/mnemos_sortkit.py no longer defaults to a deployment-specific namespace.