SPEC — Cross-Agent Memory Bus v0.1 (Tam, research/spec lane)
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.resultis capped at 2000 chars bycentral_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)
| Claim | Verified state |
|---|---|
| Qdrant 127.0.0.1:6333 | green |
| Collections | only long_term — short_term and durable do NOT exist on this instance |
long_term points | 400 (63 native + 337 legacy backfill) |
| episodic live rows | 1551 in runtime DB, 1129 in home DB; +566 in memory_episodic_archive |
| episodic readers | query_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 read | central_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}. Noagent_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:
| Tier | Identity | Semantics |
|---|---|---|
| durable | (agent_id, key) | unique, last-writer-wins + version bump |
| episodic | append-only rowid | never rewritten |
| vector | separate | vectors 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=selfplus 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)
| Step | Content |
|---|---|
| S1 | Close 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. |
| S2 | Key hygiene: backfill or quarantine the 337 unkeyed points. Until then "agent-keyed" is aspirational. |
| S3 | Write identity per §1.3 — one collection first. |
| S4 | fleet_mode reads, budgeted + logged. |
| S5 | Episodic→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_modeby 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
promotecall? 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-a9fa41c4cites 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.
Architecture evidence pack — 2026-09-28
Purpose: the evidence reference for Sol's ARCHITECTURE.md v3 rewrite. Every row was probed live by Tam (laptop + LAB on-host) and, where noted, cross-checked by Sol on-host. Three of my own claims are recorded here as OVERTURNED — a note that hides its own errors is worse than no note.
LAB Hermes persona + memory alignment — default profile is Sol, not a duplicate
Requested by: Young (DM 2026-09-28): "Sol, Mira and Tam their personas and memory is not matching. Hermes default profile can you remove it and replace with sol." Host: LAB lab@192.168.4.60 (lab@ is the user — not sol@; the laptop key already authenticates, 8 accepted logins 09-26 21:02–21:18). SSH works with the existing laptop key; no new key or sudo was needed. Method: file:line inspection of ~/.hermes + ~/.solos/personas on LAB, guarded single-match edits with per-file backups, then gateway restart + read-back.

