Give Mímir a source registry that lists every Supabase knowledge store the way the module system already lists capabilities, and put a one-line menu of it in front of the model every turn. Make the default action for any knowledge question a single tool that fans out across the whole registry — so coverage is guaranteed by registry membership, not by the model remembering. A CI test makes any unregistered table a build break. Merge the mixed results with rank-fusion then one rerank pass so a tweet, a research chunk, and a database row can finally be compared. Carry a provenance tag on every result so every answer cites its source and a human can click through. And fix the conversation memory that is currently written but never read. New sources — including the X-post analysis shards — join all of it by dropping one file into the registry.
The single live chat path (mention-mastra.js) injects no knowledge up front — only today's date. The large language model sees nothing from Supabase unless it chooses to call a tool. So "which sources get consulted" is an unbounded guess by the model, with no menu and no safety net. That guess leaks. Here is exactly what falls through:
mimir_conversation_memory — and nothing ever reads it back. No query, no tool, no reader exists anywhere in the code. We pay to build a searchable memory of every conversation and then throw it away. This is also why Mímir can't yet "chat about content later."blind_spot_clusters, mimir_reports, and doc_flags are populated and useful — but no chat tool touches them. Mímir literally cannot answer "show me last week's report" in conversation.Five components, color-coded to the research lane that designed each. Read it top to bottom — it's the life of a single question.
@Mímir ─► mention-mastra.js │ (1) cheap pre-parse: detect query MODE + extract filters (date / org / entity) │ (2) the SOURCE-CATALOG DIGEST rides in the system prompt ──┐ the router's menu ▼ one line per source: title · kind · what-it-answers · what-it's-NOT-for gpt-5.2 agentic loop ◄── KEEP it, strengthen it, don't replace it │ ├─ utility ask ("BTC price?") ───────► route to ONE tool (unchanged) │ └─ knowledge ask ──► search_knowledge(query, filters) ◄── the recall-critical default │ fans out across EVERY registry source whose "answers" match │ corpus · entity-posts(X/OKF) · sector-data · reports · memory · … ▼ each source runs its own search, over-fetching ~20 candidates ▼ FUSION Stage 1 · rank-fusion across sources (database rows pinned) Stage 2 · ONE rerank pass — the moment mixed sources become comparable ▼ SAFETY NET coverage audit ("did I check every eligible source?") + weak-result fallback → web search ▼ synthesis over TYPED structure + provenance ─► Slack answer WITH citations
This is the part that was too condensed in the spec. Each component below has a plain-English job and a concrete example.
The job: a single list of every knowledge store Mímir has, where each entry is a small "label" describing the source — what it answers, where it lives in Supabase, how to query it, how fresh it is, how to cite it. It is the exact twin of the module registry Mímir already ships and trusts.
Think of it as: a Module is a verb (a capability); a Source is a noun (a place knowledge lives). Mímir already has the verb list. This adds the noun list, reusing the same proven machinery.
research-corpus: answers "what analysts say about a ticker/sector"; not for "real-time chatter"; lives in embeddings_main; cite as "{org} — {title} ({date})".The job: a compact, one-line-per-source rendering of the registry is placed in the system prompt on every turn. The model can't forget a source exists if it's reminded of all of them, every time.
When the model picks a source, a second tool — describe_source(id) — hands it the full query details on demand, so the prompt stays small even as sources grow.
mimir_reports was queryable." After: impossible — it's on the menu every turn.The job: for any knowledge question, the default action is one tool — search_knowledge() — that automatically queries every registry source whose description matches the question, in parallel.
This is the key move: coverage stops being "did the model remember source X?" and becomes "is source X in the registry?" — a membership fact, not a judgment. Add a source to the list and it's instantly part of the coverage guarantee, for free.
The problem: a research chunk scores by similarity (0.82), a tweet by recency, a database row is an exact match. These numbers cannot be compared — you can't sort them into one list.
The fix: two stages. First, rank-fusion combines the lists by position, not score (sidestepping the comparison problem). Then one rerank pass reads the actual text against the question and produces a genuinely comparable relevance score. Database rows that exactly answer the question are pinned, not ranked.
The job: every retrieved fact carries a small provenance tag from the moment it's fetched — a stable anchor, a clickable source link, and a pre-rendered citation. Fusion and reranking can reorder or drop results but never alter the tag. At the end, each claim in Mímir's answer maps back to a clickable Slack citation. A fact with no resolvable source renders as plain citation text — never a fabricated link.
top_drivers already stores this) → the source document → a Dropbox link. The X-post shard cites the tweet; the tweet's analysis cites the underlying report. Two-level provenance.Coverage is defended at four independent layers. No single layer is trusted alone — if one slips, the next catches it.
Knowledge questions fan out to all matching sources by default. Route-to-one is the exception, reserved for unambiguous utility asks.
The full source menu is in the prompt every turn. The model can't omit what it's continuously shown.
The fan-out is driven by registry membership in code. A CI test makes an unregistered table a build break.
After retrieval, a check computes "every eligible source minus those actually queried." Non-empty → retry. Weak results → web search.
Over-fetch on purpose. Pull more than you need from each source so the ranking stages have room to work. (The live path regressed to 10; the dead code already knew 20 was better.)
Combine the lists by rank position, not raw score, using reciprocal-rank-fusion. This is the whole trick: positions are comparable even when scores aren't. Each source gets a small priority weight. Exact database rows skip ranking entirely — they're pinned as ground truth.
Take the top ~20 fused results and run a single cross-encoder rerank that reads (question, text) together. Because it never sees which source a result came from — only the text — it produces one relevance score that's genuinely comparable across all sources. Hand it the structured shard as JSON and it ranks on the fields, not just prose.
The final 8 or so, each still carrying its source tag, go to the model. The model writes the answer and cites each claim; the citations render as clickable Slack links.
The data is already there — every conversation, already embedded. It just has no reader. Ship now: add one database function to search it and register it as a normal source. ~30 lines unlocks "what did I conclude about copper last week."
Longer term: turn on the agent framework's native cross-conversation memory (a config change — the storage and embeddings already match). Run the quick fix now; converge on the native one later; never run both as permanent.
A raw tweet retrieved as a paragraph forces the model to re-extract "is this a signal or a risk?" every time. An OKF shard stores that structure up front — signal, risks, action items, proposals, citations — as typed fields.
So "show me the risks on copper" filters to risk-typed shards then ranks — far sharper than hoping similarity separates risk-prose from signal-prose. Retrieval returns structure, not a blob. This is exactly where the X-post / event-watch pipeline plugs in: it's the producer of these shards; this architecture is the consumer.
| Knowledge source | Today | After |
|---|---|---|
| Research corpus | Reachable, but raw top-10, no rerank | Over-fetch + reranked + cited |
| Conversation memory | Write-only · never read | Searchable source — recall past answers |
| Tracked-account posts (X/OKF) | Doesn't exist yet | First-class structured source |
| Sector sentiment | One narrow tool | In the fan-out, provenance-chained |
| Past reports | No chat access | Registered + queryable |
| Emerging-topic clusters | No chat access | Registered + queryable |
| A brand-new table | Silent blindspot until noticed | CI fails until registered |
| Phase | Deliverable | Risk |
|---|---|---|
| P0 · Verify | Confirm the keyword index exists; confirm live row counts. Hard gate — everything depends on this. | read-only |
| P1 · Registry rails | The source registry, descriptor shape, the no-blindspot CI test. Register existing sources. No behavior change. | Class C |
| P2 · Memory unblock | Make conversation memory readable + registered. Stops the waste, adds recall. | Class C |
| P3 · Fusion + rerank | Cross-source rank-fusion + revive the rerank + provenance plumbing + over-fetch. | Class C |
| P4 · Routing | The fan-out tool + the catalog menu + pre-parse + coverage audit + weak-result fallback. | Class C |
| P5 · OKF source | The tracked-post shard table + search function + descriptor. Consumes the event-watch producer. | Class C |
| P6 · Eval | A labeled query set; measure routing recall (the no-blindspot metric), precision, answer quality. | Class S |
If the keyword index is missing, keyword search silently scans the whole table — fine now, a latency cliff as the corpus grows. Verify in P0 before anything else.
"No blindspots" stays an assertion until the labeled eval set exists. The coverage-audit log makes it measurable in production.
Fusion weights and fallback thresholds are starting defaults, not optimized. Tune them with the feedback table that already exists.
A mis-described source misroutes silently. No test catches semantic quality of the descriptions — only per-source eval queries do.
Adds latency, cost, and a single point of failure. Mitigated by reranking only the top 20 and falling back gracefully.
Recommendation: registry lives in code (gives the CI gate), with an optional read-only mirror to a Supabase table for dashboards. Needs operator sign-off since it touches "everything in Supabase."