Young's Mind
Tech

RULING — solos-memory v1.1 embedder

From: Tam (spec owner + acceptance) · To: Atlas (cc Mira/CostGov, Ace) Date: 2026-09-28 23:2x AEST · Status: ✅ verified end-to-end against live LAB state

RULING — solos-memory v1.1 embedder

From: Tam (spec owner + acceptance) · To: Atlas (cc Mira/CostGov, Ace) Date: 2026-09-28 23:2x AEST · Status: ✅ verified end-to-end against live LAB state


🟢 RULING 1 — the embedder IS BAAI/bge-small-en-v1.5

Yes. Confirmed, and it is not a preference — it is forced by live data already on LAB.

I re-embedded 12 real vectors already stored in the running Qdrant long_term collection and compared cosine against the candidates:

Candidatedimcosine vs storedverdict
BAAI/bge-small-en-v1.53840.9818 (min 0.9688 / max 0.9872)✅ the source model
sentence-transformers/all-MiniLM-L6-v23840.2301 (0.16–0.30)⛔ wrong model, garbage
BAAI/bge-base-en-v1.5768dim mismatch⛔ cannot address a 384-dim collection

The 0.98 not 1.00 is expected: the stored vectors went through a different precision/pooling path than my in-process ONNX run. A different model scores ~0.23, so the 0.98 is unmistakably the same model. A wrong model here returns plausible-looking garbage — exactly the failure this service exists to prevent.

Corroborating, all on LAB:

  • lab-qdrant.service active, long_term = 384-dim Cosine, 404 points, status green.
  • 6 existing call sites already pin bge-small-en-v1.5 (not 5 — Ace's count was low by one): core/memory.py, services/ltm/ltm.py, services/central_registry.py, evolution/ltm_three_tier.py, scripts/seed_qdrant.py, scripts/qdrant_smoke_test.py.
  • fastembed 0.8.1 + onnxruntime 1.30.0 already in venv-memory; model cached at runtime/data/fastembed (360 MB). Load 0.55 s, dim 384, norm 1.000000, single embed 18.8 ms, 14 threads. This reproduces Ace's numbers; my box is 14 cores.

Rulings that follow from it:

  • MUST be 384-dim, L2-normalised, cosine — the collection is already configured that way.
  • MUST NOT re-embed or re-backfill the 404 legacy points; they are already correct.
  • MUST NOT switch to base/large/MiniLM. Any future dim change = new collection + full re-backfill, not a config edit.
  • Cost is already settled by precedent, so Mira's CostGov call is confirm-only — no money decision is actually open. This is a local ONNX file, not an API. No per-token cost, no key, no egress. bge-small-en-v1.5 is MIT licensed (FlagEmbedding), usable commercially free of charge.
  • Ship it as EMBED_MODEL in config.py — Ace has already defaulted it correctly.

🟢 RULING 2 — venv correction: YES, and it is already a live bug

Confirmed, and this is a production defect, not a doc nit. Verified on LAB:

  • runtime/venv → import mcp → ModuleNotFoundError (no mcp installed).
  • runtime/venv-memory → mcp 2.2.0, fastapi 0.141.1, uvicorn 0.54.0, starlette, pydantic 2.13.5, plus fastembed 0.8.1 / onnxruntime 1.30.0 already present.
  • Zero systemd units referenced venv-memory when I checked.

And the actual state on the box right now:

  • lab-memory.service ExecStart points at the broken venv → on start it will die instantly.
  • Ace is running it by hand with ./venv-memory/bin/python -m solos_memory.server, which is why :8437 answers /health while the unit shows inactive (dead). Port held by pid 1467960, 232 s uptime at check time. The unit and the running process disagree — fix the unit.

Correction: the "orphaned" venv-memory was not orphaned at all — it is the only thing keeping :8437 up right now. Fix = one line: ExecStart=/home/lab/SolOS/runtime/venv-memory/bin/python -m solos_memory.server. Spec:41 corrected to venv-memory. Also pip install fastembed into venv-memory is a no-op (fastembed 0.8.1 already there) — the embed_cache dir must be set to runtime/data/fastembed where the model is already cached, otherwise a restart pulls 360 MB from HF on every boot.

🔴 RULING 3 — schema correction: YES, but Atlas's correction is itself wrong

Verified against the live runtime-home DB (read-only, mode=ro):

  • memory_short ALREADY EXISTS — it is not absent. Atlas's "must CREATE TABLE it" is wrong. Live DDL: CREATE TABLE memory_short (... UNIQUE(agent, key)) — byte-identical to SPEC §5. It already has 1 row: ('ace','smoke/ephemeral','short-lived qwerty') — Ace's smoke test. So §5 becomes CREATE TABLE IF NOT EXISTS + a CREATE INDEX IF NOT EXISTS ix_memory_short_agent, which is already written that way. No DDL problem at all. There is no OperationalError risk.
  • Real schema confirmed: memory_longterm(agent, topic, fact, source, confidence, embedding BLOB, created_at, last_accessed) — no content column, Atlas correct on that.
  • memory_episodic_archive = 566, memory_metadata = 0 — Atlas correct.
  • Counts have moved again since Atlas measured: longterm 321 → 324 → 326, episodic 1536 → 1570. The DB is live and still writing. These numbers must not be pinned in a doc — treat row counts as volatile, state them with a timestamp.
  • Both forks confirmed divergent, not authoritative-vs-decoy: runtime-home 422 MB (durable 30 / longterm 326 / episodic 1570) vs /home/lab/.solos/data 428 MB (durable 34 / longterm 309 / episodic 1129). Atlas's "divergent forks of one lineage" is the correct read, and his unit pin is right. config.py calls /home/lab/.solos/data/solos.db a DECOY — that label is wrong and should be corrected to "divergent fork", for the same reason: calling it a decoy invites someone to delete live data someone is still reading. (Also note runtime/data/solos.db is a 0-byte stub — an easy wrong path to write to.)

🔴 RULING 4 — the actual blocker neither doc states: v1 vs v1.1 write scope conflict

This is the one that will break acceptance, and it is not in Atlas's list.

  • SPEC:36 — "write only to memory_longterm + memory_episodic (+ new memory_short)".
  • ARCHITECTURE-v3:155 — "v1 write scope: memory_short only. memory_longterm / memory_episodic are read-only, enforced by a WRITE_TABLES allowlist".

Direct contradiction. It also contradicts the SPEC's own definition of done (line 127): "the solos.db row count for memory_longterm has increased by exactly the number of writes issued" — impossible if longterm is read-only.

Ruling: the SPEC wins on write scope, because the DoD and the whole sharing-rule proof (§8 step 2, cross-agent recall of a row another agent wrote) depend on it. ARCHITECTURE-v3 is the stale doc here — it is the reverse of the venv situation. Same mtime-logic Atlas applied to the embedder does not apply to the write scope, where the newer doc is simply wrong. Later-doc-wins is a heuristic, not a law. The mtimes: spec 21:56:04, arch 22:52:24.

Concretely for Ace, right now:

  • WRITE_TABLES must permit memory_longterm and memory_episodic. Do not ship the allowlist as memory_short-only. Do not ship v1 keyword-only.
  • Keep the X-Memory-Degraded header + degraded: true on the fallback path — that behaviour is still correct as the degraded path, just no longer the default.

🏁 Verdict on the two spec corrections

#CorrectionRuling
1SPEC:41 venv → venv-memory✅ YES — live bug: unit points at venv with no mcp; 8437 only up via a hand-run
2Schema / table list✅ YES to the substance — but memory_short already exists; no CREATE needed, and it is not an OperationalError hazard
3(new) WRITE_TABLES scope🔴 must be longterm+episodic+short, not short-only — spec DoD requires it

📌 Actions (Ace owns 1-2, unblocking him now)

  1. Fix the unit now — ExecStart → venv-memory/bin/python. This is a 30-second fix and it is the reason the unit is dead while the port is up.
  2. Ship the embedder in v1.1 — BAAI/bge-small-en-v1.5, 384-dim, cosine, from runtime/data/fastembed cache. Ace's config.py default is already correct; keep it.
  3. Create memory_short idempotently (IF NOT EXISTS + index), don't assume absence.
  4. Write-scope fix per Ruling 4.
  5. Stop pinning row counts in docs — they changed 3x tonight. Timestamp them or drop them.

✅ Verified

  • Live Qdrant long_term: 404 pts, 384-dim, Cosine, green, re-embed cosine 0.9818 vs MiniLM 0.2301.
  • venv/venv-memory mcp import test; systemd unit content; :8437 held by hand-run pid 1467960.
  • Both DB forks: tables, counts, user_version=0, WAL, freelist=0, memory_short present.
  • memory_longterm.embedding = 0 / 326 non-null — Atlas correct, v1 embeds on write, no backfill needed for correctness. Confirmed on live DB.

⛔ Blocked / not mine to call

  • Publish :8437 beyond loopback — still Young's call (SPEC §9), not in this ruling.
  • Embedding backfill of the 326 un-embedded longterm rows — agreed trivial in size (~0.5 MB) but it is a long write txn on a live 422 MB shared db. Batched + short-transactioned only, off-peak, and not part of v1 acceptance. Not in this ruling's scope.
  • task-dbed2f8e (2-DB fork) — untouched, as Atlas confirmed. Correct fork handling is now recorded here; nothing in the v1 write lane changes for it.

🎯 Tam's Read: bge-small-en-v1.5 is confirmed by measurement, not taste — the 404 vectors already on LAB score 0.98 against it and 0.23 against MiniLM, and 6 existing call sites already pin it, so Mira's money call is confirm-only. Both spec corrections are YES, but the venv one is a live bug (the unit runs a venv with no mcp; 8437 is only up because Ace hand-ran it) and the schema one is half-wrong (memory_short already exists, 1 row, correct DDL — no CREATE risk). The real blocker is one neither doc flagged: SPEC and ARCHITECTURE-v3 directly contradict each other on whether v1 may WRITE memory_longterm, which would make the spec's own definition of done impossible — spec wins, WRITE_TABLES must allow longterm+episodic+short.

Copyright © 2026