Young's Mind
Tech

Cross-agent memory bus — spec

Young's directive 2026-09-28, Tam (spec) + Min (coordination), engineering with Atlas/Ace on solos-memory. Companion (service-level, already on the board as task-2040344e): 2026-09-28-spec-solos-memory-service.md.

Cross-agent memory bus — spec

Young's directive 2026-09-28, Tam (spec) + Min (coordination), engineering with Atlas/Ace on solos-memory. Companion (service-level, already on the board as task-2040344e): 2026-09-28-spec-solos-memory-service.md.


0. Preflight corrections (measured on LAB 2026-09-28, not taken on faith)

Directive saysActually true (verified)
"episodic table, 1471 rows"Table is memory_episodic (no table named episodic). 1548 rows, 2026-09-18 21:50 → 2026-09-28 12:42 UTC. Composition: conversation_turn 1443, goal_execution 105 — i.e. 93% raw turn text. Per-agent: sol 406, atlas 289, ace 182, mira 177, min 174, alpha 146, nova 90, echo 84.
"Qdrant 3 collections, long_term ~400"That is two hosts, not one. LAB 192.168.4.60:6333 = 1 collection (long_term, 400 pts). Prod 100.124.146.127:6333 = 3 (long_term 329, short_term 0, durable 0). Same split-brain class as board task fc043640. The bus MUST name one host; recommendation: LAB (loopback, where solos.db and the mesh live).
"episodic is flat, nothing reads it"Confirmed and worse. Zero live readers — memory_episodic and memory_episodic_archive are referenced only under .displaced-20260926-122825/. memory_episodic_archive already holds 566 rows (2026-07-24 → 2026-09-18), so eviction is already happening and the evicted half is also unread. The bus is not adding a read path to a flat table; it is adding the first one to a system that has been silently archiving for 5 weeks.
spec at ~/.solos/docs/SPEC-cross-agent-memory-bus.mdThat path does not exist on the laptop or on LAB. Phantom artifact. Real spec = this note + the board result field.

Also: memory_longterm = 321 rows, embedding non-null 0/321; 11 literal duplicate facts (e.g. "SolOS uses layered memory architecture" ×3). Verified board footgun: PATCH /tasks/a9fa41c4 returns {"updated":true} and writes nothing; the real id is task-a9fa41c4 (task 685ee1e1). Always re-GET after PATCH.


1. Write contract — agent-keyed, one writer per key

  1. Every write is agent-attributed. The agent field is mandatory on every tier. Reject any agent not present in agent_registry (11 rows live) with an explicit error naming the agent. Never create orphan rows.
  2. The write key is (agent, tier, key). No shared/global/multi-writer key, ever. Two writers on one record is the board-fork bug we already paid for; the key shape is what prevents it. Writes are upsert-on-conflict, not blind insert.
  3. Normalise the two payload schemas on read. Qdrant long_term currently holds both shapes: store_memory (memory_nodes.py:90) writes agent_id / memory_scope / importance_score, while 63/400 points (15.8%) carry agent / topic / content / fid and no agent field at all. The bus must resolve agent = coalesce(payload.agent, payload.agent_id) and treat a still-null attribution as a hard filter-out, never as a passthrough — otherwise 15.8% of the corpus arrives unlabelled.
  4. Fix or wrap the point-id before the bus writes. store_memory derives uuid5(NS_URL, f"{agent_id}:{topic}:{content[:100]}") and truncates payload to 2000 chars. Two distinct facts from the same agent+topic sharing a 100-char prefix collide into one point and the second silently overwrites the first. The bus must key on a full-content hash (or agent:tier:sha256(content)), not a truncated prefix.
  5. One canonical writer. There is no public write endpoint today; store_memory is the only writer and it is an internal LangGraph node. The bus becomes the first public write path, which makes it the de-facto canonical writer — so store_memory must be refactored to call the bus, not sit beside it. Two live writers on long_term is the same fork as fc043640. (Flagged for Ace/Atlas; not decided here.)

2. Read contract

  • One handler, two transports: POST /mcp (MCP StreamableHTTP) and GET /v1/recall (REST), same semantics.
  • Parameters: agent (caller), q, k=5, scope = own | shared | shared_except_self (default shared), tiers = [short, long, episodic, durable].
  • Every result carries {tier, agent, ts, score, source, foreign} — never bare text. Without agent on each row a caller cannot attribute a fact, which is how false consensus forms.
  • foreign = (result.agent != caller). Cross-agent rows are labelled, not hidden.
  • durable is excluded from the default tier set. A caller may read only its own persona/ row; cross-agent durable reads are refused. memory_durable is identity/persona and stays Sol-owned — the bus reads it, never writes it.
  • Failure behaviour: DB busy → retry ×3 then 503 with a reason, never an empty result set. Embedder down → degraded: true + keyword recall, no exception (header X-Memory-Degraded).
  • Ship-with-honesty: with 0/321 embeddings and 11 duplicated facts, recall from long is currently precision-poor. Report per-tier hit counts and duplicate-suppression stats in /v1/stats so a green 200 is not mistaken for a working recall.

3. Retention

TierHot lifeOn evictionNotes
shortTTL default 86400lazy expiry on read + daily purge on the CENTRAL cron registry (:8436) with delivery target + failure signal — not a local crontabno daemon
episodic120 min / 1000 turns, whichever firstsummarize-on-eviction → long, written as agent = <summarising agent>, source = episodic:, so the derivation chain is auditablemust also be readable back
longno hard deletescope field active → durable → subconscious + reinforcement_count (existing store_memory semantics)scope change, not data loss
episodic_archiveindefinite—must get a read path (admin scope) or we re-create the 566-row black hole

Retention is only meaningful if the archive is recallable. An unread archive is data loss wearing a retention policy as a disguise.

4. The episodic ↔ durable boundary (the rule, stated once)

  • Episodic = what happened. Turns and goal executions. Verifiable, time-stamped, TTL'd, shared-readable only if §5 resolves in favour of sharing. Never prescriptive, never quoted as canon.
  • Long = the distilled fact. The only tier that is both fleet-readable and treated as canonical. This is where summarize-on-eviction lands and where recall actually gets its answers.
  • Durable = who we are. persona/ and identity records. Sol-owned. The bus never writes it; other agents never read it.

Promotion is a one-way valve, and only one edge exists on the bus: episodic → long, via an explicit promote call that must name the source row ids. long → durable is Sol's lane, not the bus's. Any second edge is two writers on one record, and we have already lived through that with the board.


5. The design decision that will bite us

Does scope=shared include the episodic tier?

Everything about the bus's usefulness hangs on it, and the two answers are not a preference — they are different products:

  • Yes → the bus is genuinely valuable: 1443 of 1548 episodic rows are raw turn text, ~155 new rows/day, and today nobody can read them at all. But every agent's unfiltered turn history becomes fleet-readable, including the turns where Young talks to one agent in confidence. That is a one-way door: the moment one agent quotes a shared turn into a long fact, the confidentiality is gone forever and no retention rule can claw it back.
  • No → no breach, and the bus quietly becomes a fact-sharing channel over a 321-row corpus with 0/321 embeddings, 11 literal duplicates and 3% duplication. It would look live, answer "installed" in acceptance, and return near-worthless recall. That is the silent-failure mode already flagged in task-8993aedd: not under-retrieval, precision collapse.

Recommendation: default shared to [long, short], and gate episodic behind an explicit per-agent opt-in with an audit log, so the value is available to the fleet without making private counsel structurally shareable. Whichever way Young decides, it must be decided before the read path ships — retrofitting the boundary means rewriting 1500+ rows' worth of history, not adding a config flag.

🎯 Tam's Read: The bus is worth building, but its only interesting asset is 1443 raw turn rows nobody can read, and sharing them is irreversible — decide the episodic-sharing boundary first; everything else is plumbing.

Copyright © 2026