Young's Mind
Tech

SPEC — Cross-Agent Memory Bus v0.1 (Tam, research/spec lane)

For: Atlas + Ace (solos-memory engineering). Review: Young. Coordination: Min. Status: spec only. No code, no registry schema changes proposed here.

SPEC — Cross-Agent Memory Bus v0.1 (Tam, research/spec lane)

For: Atlas + Ace (solos-memory engineering). Review: Young. Coordination: Min. Status: spec only. No code, no registry schema changes proposed here.

Board note: task-a9fa41c4.result is capped at 2000 chars by central_registry.py:925 (UPDATE tasks SET result=? ... u.result[:2000]). This note is the spec of record; the task field carries the pointer + headline.


0. Evidence base (verified on LAB 2026-09-28, read-only)

ClaimVerified state
Qdrant 127.0.0.1:6333green
Collectionsonly long_term — short_term and durable do NOT exist on this instance
long_term points400 (63 native + 337 legacy backfill)
episodic live rows1551 in runtime DB, 1129 in home DB; +566 in memory_episodic_archive
episodic readersquery_episodes() exists at core/memory.py:154 — no caller anywhere in ~/.solos
canonical writer.solos/langgraph/memory_nodes.py::store_memory (no public registry write endpoint)
registry readcentral_registry.py:537 unions memory_durable + memory_longterm + learnings per agent

0.1 long_term is two populations in one collection

  • 337 legacy backfill points — payload keys {agent, topic, content, fid, backfill}. No agent_id.
  • 63 native points — keys {agent_id, topic, content, memory_type, importance_score, memory_scope, reinforcement_count, last_seen_at, timestamp}. 9 each: atlas, mira, echo, ace, min, sol, nova.

TAM and EVA have zero points. The 400 is a seed, not traffic.

0.2 The bus is already running, by accident

recall_node → semantic_search(query, limit=5) with agent_id=None → no agent filter. Every agent already reads every other agent's long_term today. The cross-agent bus is not a new capability; it is an undeclared, unlogged, unbudgeted one.

0.3 Write identity is content-shaped

store_memory point id = uuid5(NAMESPACE_URL, f"{agent_id}:{topic}:{content[:100]}"). The first 100 characters are the identity: two different facts sharing a prefix collide, and a corrected fact becomes a new point rather than superseding the old one.


1. Agent-keyed writes

1.1 Every write MUST carry agent_id as a first-class indexed key. The 337 unkeyed legacy points are precisely why "agent-keyed" is not true today: they are invisible to any agent filter and they silently pollute unfiltered recall.

1.2 Writes are agent-scoped by construction. An agent may only write rows where agent_id == self, or agent_id == "" — a reserved fleet-shared namespace, not a wildcard. No agent writes another agent's row.

1.3 Upsert identity must change:

TierIdentitySemantics
durable(agent_id, key)unique, last-writer-wins + version bump
episodicappend-only rowidnever rewritten
vectorseparatevectors are an index, never the identity

1.4 The write surface does not change: store_memory stays the single canonical writer. No second writer, no registry write endpoint. The bus is a discipline over one writer, not a new service.

1.5 Provenance is mandatory on every write: source (who/what produced it), confidence, created_at. No source, no write.


2. Read contract

2.1 Reads are always scoped. agent_id is a required filter on every vector search and every table read. An unfiltered cross-agent read is a bug, not a feature — that is exactly what recall_node does today.

2.2 Fixed, versioned response shape: {id, agent_id, tier, key/topic, value/content, confidence, score, created_at, last_seen_at, source}. Callers must never depend on per-population payload key sets — that legacy/native split is the trap.

2.3 tier is a closed enum: episodic | long_term | durable | learning. Anything else is rejected at read time rather than passed through as a string.

2.4 Two read modes, both scoped:

  • agent_mode — agent_id=self plus the fleet-shared namespace. The default.
  • fleet_mode — explicit, opt-in, budgeted: N results, M agents max, never silent.

fleet_mode is how the cross-agent bus reads. It must never be the path of least resistance.

2.5 Graceful degrade is preserved: Qdrant or registry down → empty context, turn proceeds. A bus outage must never block an agent.

2.6 Every fleet_mode read is logged with (requester, result ids, scores). Without a read log there is no way to answer "why did agent B believe X" — and it is the only evidence that the episodic table is ever consumed.

2.7 Read budget stays at limit=5. fleet_mode does not raise it; it splits the same budget across agents. Context is the scarce resource, not storage.


3. Retention rules

3.1 Episodic (turns): 7-day rolling as consolidate.py already does (SHORT_TTL = 7*86400), plus a size cap (rows and bytes) so a quiet week does not become a permanent growth curve. Archive is a move, never a silent delete of the only copy.

3.2 Long-term: never hard-deleted. Demotion is a scope change (active → durable → subconscious), matching the existing store_memory docstring. Deletion requires an explicit admin action and is recorded.

3.3 Promotion is the contested rule. reinforce_memory promotes active → durable at 3+ recall hits. Under a fleet bus, recall hits include hits generated by other agents, so an agent's promotion would be driven by someone else's read traffic. Promotion must count hits from the owning agent only. Cross-agent reads may log; they may never promote.

3.4 Retention is per-namespace: episodic TTL applies to turns, never to durable facts. A 7-day TTL that can reach a durable fact is a data-loss bug, not a cleanup.

3.5 Retention state lives with the data (payload/columns), never in a process-local counter, so a restart cannot silently reset or inflate a tier.


4. Episodic vs durable — the boundary

4.1 Episodic = what happened. Turn-level, agent-scoped, time-bounded, high volume, append-only, cheap to lose. Read by humans/debuggers and by summarisation jobs.

4.2 Durable = what is true. Key-value facts, agent- or fleet-scoped, versioned, time-independent, low volume, expensive to lose. Read on every turn.

4.3 The promotion test (episodic → durable) must pass all four:

  • (a) standalone assertion — no pronouns, no "as discussed", no turn references;
  • (b) an explicit key;
  • (c) a source and a confidence;
  • (d) survives a re-read by a different agent without rewriting to make sense.

A turn-transcript fragment fails all four and stays episodic.

4.4 The demotion test: if a durable fact's supporting episodic rows are all gone and it has not been re-confirmed inside its review window, it moves down a tier — never out.

4.5 Neither tier is a cache of the other. If the bus ever needs "read durable, else compute, then write episodic" as a workaround for the flat table, the boundary has failed.


5. Sequencing (proposal — Atlas/Ace to confirm)

StepContent
S1Close the flat-table asymmetry: give episodic a real reader + the read log from §2.6. 1551 written / 0 read is the largest single gap, it is cheaper than the bus, and it is what makes the bus verifiable.
S2Key hygiene: backfill or quarantine the 337 unkeyed points. Until then "agent-keyed" is aspirational.
S3Write identity per §1.3 — one collection first.
S4fleet_mode reads, budgeted + logged.
S5Episodic→durable promotion with the four-point test.

Retention (S1/S2) before fan-out (S4). Fan-out over an unkeyed, unlogged corpus just makes a mess faster and harder to attribute.


6. Open questions for Young

  • Q1 Fleet-shared durable facts: one shared namespace, or per-agent with a federation rule? (Spec assumes a reserved agent_id="" namespace, not a wildcard read.)
  • Q2 Does any agent get fleet_mode by default? Spec says no — opt-in + budgeted.
  • Q3 Who owns promotion authority once reads are cross-agent: writer's own hits only (§3.3), or an explicit promote call? Spec says own-hits only.
  • Q4 Is the 7-day episodic TTL still right now that episodes may be a shared corpus, or does cross-agent episodic need a longer floor?
  • Q5 task-a9fa41c4 cites a spec at ~/.solos/docs/SPEC-cross-agent-memory-bus.md — that file does not exist on LAB. This note is the spec of record unless Young says otherwise.

7. The decision that will bite

§3.3 — promotion authority under cross-agent reads. Today reinforce_memory promotes on recall hits, and the bus makes reads cross-agent. The instant a shared read can promote, an agent's durable memory is promoted by someone else's traffic. Nobody is attacked, no alert fires, no test fails — but the durable tier slowly fills with facts that were never confirmed by their owner, and every downstream "why does the fleet believe this?" becomes unanswerable because promotion provenance is gone. It is the one decision that is retroactively invisible: you cannot detect it after the fact from the data, only before, from the rule.

The second-order bite is the fix ordering. The tempting fix — "let the writer explicitly promote" — turns promotion into a second write path, which contradicts §1.4 (one canonical writer). So the cheap fix and the architectural fix point in different directions, and that tension will resurface at every retention review.


🎯 Tam's Read: the bus already runs undeclared — recall_node reads cross-agent today with no filter, no log and no budget; the real gaps are 337 unkeyed points and 1551 episodic rows with zero readers; and the decision that bites is letting shared reads promote durable memory, because it corrupts tier provenance in a way that is undetectable after the fact.


Provenance / fold note (Tam, 2026-10-21)

Rule-4 fold: the root-level sibling 2026-09-28-cross-agent-memory-bus-spec.md lived at the tam/ root too (both created as separate files by consolidation commit e629473, 2026-10-12). The tech/ copy is the spec of record; the root copy was deleted 2026-10-21. Its unique content vs. this copy — the fuller delivery-note provenance — is recorded here verbatim in substance:

Delivery note (verified 2026-09-28). tasks.result is truncated to 2000 chars server-side — /home/lab/.solos/services/central_registry.py:925 (UPDATE tasks SET result=? with u.result[:2000]). The board field therefore holds a dense 1993-char digest; this note is the full text. GET /tasks/{id} does not return result — read-back was done against the live store /home/lab/SolOS/runtime/home/.solos/data/solos.db, sha 0e360... matching what was sent. GET /tasks also omits result. The board task description still points at a spec path (~/.solos/docs/SPEC-cross-agent-memory-bus.md) that does not exist on either box — that description is stale; this file is the spec.

Copyright © 2026