The architecture that ends mid-conversation amnesia — Canon · Recall · Spine · Auto-recall. v3 · spine-first
RECALL is the spine-first operation (centre). It embeds the query once and with that single vector it reads two stores: the SPINE (required obligations) and the vector store (similar hits) — then places required above similar. The yellow edge is literally recall reads the spine.
| Layer | Question it answers | Mechanism | Live |
|---|---|---|---|
| Canon | "What is the verified, deterministic fact?" | Gitea reference cards (/opt/gaios/canon), edited only via canon_set | — cards |
| Recall | "What is similar to what I'm doing?" | ChromaDB vector search (nomic-embed-text 768-dim) over 4 collections | — docs |
| Spine | "What is required but dissimilar?" | Per-project dependency-validity DAG, hint-scoped | — spines · — gaps |
| Auto-recall | "How do I never forget to look?" | Per-turn enrichment + get_context bootstrap (spine-first) | always-on |
Dense ≤8 KB markdown reference cards (/opt/gaios/canon/*.md, Gitea branch main), one per fact/domain/incident — the single source of truth for anything deterministic (ports, IPs, paths, rules, incidents). — cards today. Edited only via canon_set (commit + push); searchable in Recall within ~60 s. The forcing function: pull the card and cite it inline before stating an infra fact.
ChromaDB on 127.0.0.1:8000, four collections, all embedded by nomic-embed-text (768-dim) served VPS-local at 127.0.0.1:11434 (one embedder for index and query). Queried via the recall MCP tool — embed → search each collection → merge, sort L2 ascending.
| Collection | Source | Live docs | Holds |
|---|---|---|---|
| canon | /opt/gaios/canon/*.md | — | verified reference cards |
| sessions-v2 | memory/conversation_sync/*.json | — | synced session summaries / decisions |
| projects-v2 | memory/projects/*.json | — | project state + next steps |
| ideas | memory/idea_pipeline/*.json | — | idea backlog |
Distance scale (probe-derived): ≤300 good · 300–400 loose · >400 noise (a random query scores ~443).
gaios_indexer.py (gaios-indexer.service) polls every 60 s: hash-change → re-embed + upsert; deleted → remove; failure → log + skip. Live: — documents tracked, status —. Every durable write becomes searchable within ~60 s automatically.
Vector recall returns what is similar, never what is required. The Project Spine closes that gap: a curated, machine-verified, per-project dependency-validity DAG (/home/ubuntu/api/spines/<project>.json, engine spine.py). A node = a validity-critical obligation whose absence invalidates downstream output; status is computed by running the node's typed checks — no passing verifier, never "done". Hint-scoped (semantic, bind threshold 0.6); >1 match → MULTIPLE SPINES MATCHED — confirm rather than guess. Live: — spines (—) with — open gaps.
Recall is the workhorse (hundreds/day) and used to be spine-blind. Now recall starts from the spine — and the two are coupled through a single embedding:
Conservative by design — false authority (a wrong spine injected as "required") is worse than a missing one, so binding needs a margin and never silently picks on a tie. Single-embed; cached profile embeddings + TTL gaps cache → warm cost is a few cosines. Fail-safe: if the spine engine is down, recall still returns its hits. Per-token auto-recall stays spine-blind by design (spine injection at machine speed would bloat and stall). Engine: /home/ubuntu/api/recall_spine.py.
inference_proxy is hard-injected with a <gaios_recall_context> block (embed user text → top-K → prepend) before the model sees the prompt.hint it assembles (now spine-first): SPINE gaps, recall hits, identity profile, active projects, pending tasks, recent sessions/ideas. One call and the agent is grounded.canon_set→canon · sync_conversation→sessions · save_task/project→projects/tasks · log_idea→ideas. All re-embed ~60s.
Cowork-Claude · 5 Pantheon ministers · HESTIA · HQ workers — all bound by recall-before-action + spine-first.
JSON under /home/ubuntu/memory/{projects,conversation_sync,idea_pipeline,task_queue} — durable truth the index mirrors. Tasks: —.
recall-before-action · spine-first · precommit before any state change · recall before declaring a limitation.
| Component | Where | Service / tool |
|---|---|---|
| Canon store | /opt/gaios/canon (Gitea) | canon_set / canon_get |
| ChromaDB | 127.0.0.1:8000 | chromadb.service |
| Embedder | 127.0.0.1:11434 (nomic-embed-text, keep-alive) | Ollama (VPS-local) |
| Indexer | /home/ubuntu/api/gaios_indexer.py | gaios-indexer.service (60s) |
| Recall library | /home/ubuntu/api/recall_lib.py | recall MCP tool (gaios-ext) |
| Spine engine | /home/ubuntu/api/spine.py + spines/*.json | spine.py · gaps-json |
| Spine-first layer | /home/ubuntu/api/recall_spine.py | recall (Cowork + ministers + HESTIA) |
| Auto-recall / bootstrap | inference_proxy.py · gaios_mcp.py get_context | per-turn + session-start |
| Live stats feed | memory_stats.py → /data/memory-stats.json | memory-stats.timer (2 min) |
A local agent (e.g. HESTIA, qwen3.5:9b on the GPU via relay) does not get the brain by default — its built-in memory_search hits only its own near-empty local store. The GAIOS brain is wired in through a three-tier bridge, designed so a small model reliably chooses to use it.
A tiny per-agent FastAPI (<agent>_tools.py, systemd, loopback, X-<Agent>-Key) exposing only safe verbs: /context (recall + SPINE), /recall, /canon_get, /save_task, /log_idea, /delegate(+result). gaios_exec / db_query / vault are not implemented — the security boundary is the absence of code, so even a prompt-injected agent cannot reach VPS root.
A stdio JSON-RPC server mcp_gaios.py wraps that service and is registered with openclaw mcp set gaios. This turns recall into real named tools, not a curl recipe. The primary, gaios_recall_context, is described as “call FIRST for any memory / recall / projects / canon / facts / decisions request” — plus gaios_recall_search, gaios_canon_get, gaios_save_task, gaios_log_idea, gaios_delegate.
Three redundant layers point the model at the brain: the tool name + description (primary), the SOUL imperative (“call gaios_recall_context first; local memory_search is NOT the GAIOS brain”), and an embedded MEMORY.md guide. A 9b model routes by tool name/description — not by buried recipes.
Heavy or risky work goes through /delegate → a sandboxed, root-refusing, audited HQ job. The agent holds the request lever, never direct shell on the fleet. Telegram runs dmPolicy: pairing.
The lesson that drove this design: the first build wired GAIOS recall as an exec+curl recipe in the agent’s soul. The model ignored it, called its native (empty) memory_search, and concluded “nothing found.” The fix was to make memory a native, well-named, well-described tool and to verify behaviourally — ask the live model to “dig the memory and list unfinished projects” and confirm it calls gaios_recall_context and names real projects. Full step-by-step in canon: skill-create-local-agent-with-gaios-memory-2026-06-06.
| Layer | Where | Role |
|---|---|---|
| Curated tool service | /home/ubuntu/api/<agent>_tools.py · <agent>-tools.service :89xx | safe verbs only; no exec/vault by construction |
| Native MCP server | /opt/gaios/fleet/v3/<agent>/mcp_gaios.py | recall as named tools (gaios_recall_context primary) |
| SOUL.md + MEMORY.md | <agent>/workspace/ | imperative routing & mandatory guide |
| Escalation valve | /delegate → HQ worker (hq_worker.py) | sandboxed, audited; replaces gaios_exec |