Viska Memory System — design spec

Hermes lane: spec + route. Hermes researches, recommends, and specs the content/data contract. Build is owned by viska-n8n / proteus / mnemosyne per the lane table (§5). This document is a build-ready design, not a build order.


0. TL;DR

Viska does very heavy daily RAG retrieval + ingestion (financial documents, broker research, filings, market data). Today that is a pure vector-RAG pipeline (#86: pgvector + bge-m3, hybrid BM25/dense RRF, contextual retrieval, table-aware chunking). Vector-RAG's structural weakness — it re-derives every connection on every query and nothing accumulates — is exactly the weakness OKF's "AI-maintained wiki" pattern attacks.

The system has two tiers of knowledge, two indexes, one substrate:

Tier Holds Index Read by
Synthesis OKF bundle — AI-maintained, cross-linked concept pages (an issuer, a thesis, a metric, a holding) with hard provenance index.md-first progressive disclosure + graphify graph nav agents (MCP), humans (Git/Obsidian/dashboard)
Retrieval raw source chunks (the immutable documents themselves) pgvector hybrid (the #86 stack — kept, not retired) the Ingest/Query engine, for fine recall + citation backing

The net-new build is the engine that maintains the synthesis tier — Karpathy's three standing operations (Ingest / Query / Lint) that Google's OKF spec deliberately dropped. Everything else is adopt-not-build (rule #18): OKF is a one-page format, graphify exists, pgvector/bge/n8n/Supabase are already live in Viska.

Two forks closed this pass (were the blockers on Section 2):

  1. Does graphify scale as the sole index?No. Karpathy himself scopes index-first navigation to ~hundreds of pages and reintroduces hybrid keyword+vector search (qmd) past that; Viska is well past that at heavy daily volume. Resolution: graphify indexes the synthesis tier (small, ~10²–10³ concept pages); pgvector-hybrid stays as the retrieval tier for the raw corpus. pgvector does not fully retire — it retires as the synthesis store, persists as the raw-source retrieval index. This redefines the parity gate (§4).
  2. Who owns Lint?Two altitudes. The Lint engine (contradiction / staleness / orphan / provenance checks) is the one net-new fleet build → mnemosyne-owned, fleet-standard. Viska supplies a Lint policy config (financial-claim provenance thresholds, market-data staleness windows) → Viska-instance-owned. Adopt the engine, own the policy (rule #18).

1. Architecture (locked decisions)

These were settled in brainstorming (2026-06-24) and re-anchored on the fleet verdict; restated here as the frame for §2, not re-opened.

1.1 Concept model (the OKF bundle)

One file = one concept. For Viska the concept taxonomy (the type: vocabulary — OKF leaves this to the producer, §6 limitation 2; Viska fixes it because it controls its own vocabulary):

type: One page per Example
Issuer security / company issuers/novo-nordisk.md
Holding current position holdings/novo-nordisk.md (links its Issuer)
Thesis investment thesis / view theses/glp1-supply-constraint.md
Metric a defined number + how it's computed metrics/portfolio-twr.md
Event filing / earnings / rating change events/2026-q2-novo-earnings.md
Source an immutable raw document (pointer, not body) sources/novo-10q-2026q2.md

Reserved files per OKF: index.md (folder TOC — agent reads first) and log.md (dated changelog). Every concept page carries the optional-in-OKF-but-mandatory-in-Viska # Citations block (§3). Cross-links are ordinary markdown links; those links are the graphify graph.


2. The Ingest / Query / Lint engine ← this section is the deliverable

The engine is the maintenance machinery OKF dropped. It is not new infrastructure — it is three operations expressed on Viska's existing substrate: n8n (orchestration), Supabase (canonical state + provenance + the retrieval index), pgvector + bge-m3 (embeddings), graphify (synthesis-tier index), and the OCR/parse vendor from #86 (LlamaParse-Agentic lean, pending the bake-off — hermes-032/#86).

                        ┌─────────────────────── INGEST (per-source, n8n) ──────────────────────┐
  raw document  ──▶  parse/OCR (#86 vendor)  ──▶  chunk + embed ──▶ pgvector (RETRIEVAL tier)
   (filing,                                            │
    research,                                          ▼
    market data)                            maintainer pass (LLM, prompt-partitioned)
                                                       │   reads affected concept pages via Query
                                                       │   drafts/updates concept page(s) + index.md + log.md
                                                       ▼
                                            ┌── GUARDRAIL GATE (§3, deterministic) ──┐
                                            │  provenance? number-grounded? no       │
                                            │  un-superseded contradiction?          │
                                            └──────────────┬──────────┬──────────────┘
                                                   PASS ──▶│          │◀── FAIL ──▶ quarantine + Lint queue
                                                           ▼
                                                  OKF bundle (SYNTHESIS tier, Git + Supabase registry)
                                                           │
        QUERY (MCP, read-side) ◀───────────────────────────┤
          agents: index.md-first → graphify graph nav (synthesis)        LINT (nightly n8n cron)
                + pgvector hybrid retrieval (raw recall + citations)      contradiction / staleness /
          humans: Git / Obsidian / dashboard (same folder)               orphan / provenance audit

2.0 Where the engine runs

2.1 INGEST — turn a raw source into maintained knowledge

Trigger: a new raw document lands (n8n watcher on the source — filing feed, broker drop, market pull). Per document:

  1. Parse — OCR/structure-extract via the #86 vendor. Deterministic cell extraction for tables, not generative VLM (the #86 verify-not-ledger finding: a vision model hallucinated "PFOF on Pro tier" on a dense IBKR layout — never let a generative model invent table cells).
  2. Persist raw + retrieval-index — store the immutable document as a Source concept (pointer + metadata, never the mutated body) and embed its chunks into pgvector (bge-m3, table-aware chunking, contextual-retrieval prefixing — the #86 stack unchanged). This is the retrieval tier; it is always written, regardless of the synthesis pass.
  3. Maintainer pass (the OKF-wiki write) — an LLM reads, via Query (§2.2), the concept pages this source touches, then drafts the delta: update the Issuer/Holding/Thesis/Metric pages the document affects, append to log.md, refresh the affected index.md entries, and attach # Citations pointing at the Source + chunk offsets. The human does not write the wiki; the AI does (Karpathy's core inversion). Autonomous — no approval step (L3).
  4. Guardrail gate (§3) — every drafted delta passes the deterministic gate before it is committed to the bundle. Fail → the delta is quarantined (not silently dropped) and queued for Lint/operator review; the raw source + pgvector write still stand, so retrieval is never blocked by a synthesis-write rejection.
  5. Commit — passing deltas write to the Supabase registry → materialise to the OKF folder → refresh the graphify graph (incremental; the synthesis tier is small enough for cheap reindex).

Idempotency: dedupe on source content-hash (a re-delivered filing must not double-write). Concept pages are addressed by stable concept-id (type/slug); a second source for the same issuer updates the page, it does not fork it.

2.2 QUERY — read-side, two indexes, one router

Agents read over MCP; the query router picks the tier by question shape:

Question shape Tier Mechanism
"What is our thesis on X?" / "summarise our view of issuer Y" Synthesis index.md progressive disclosure → open the 1–2 concept pages → graphify get_neighbors / shortest_path for related concepts. A finished answer, synthesis paid once.
"Find the exact passage / number behind claim Z" / fine recall over the raw corpus Retrieval pgvector hybrid (BM25 + dense RRF, bge-reranker) over raw chunks — the #86 path, kept.
Mixed ("thesis + the filing line that supports it") Both synthesis page for the answer; its # Citations resolve into retrieval-tier chunks for the backing.

2.3 LINT — the periodic health-check (the one net-new fleet build)

Lint is what keeps the bundle from going stale and confidently lying — OKF's most-cited limitation (REPORT §6, limitation 1). It runs nightly (n8n cron) over the whole bundle.

Engine (mnemosyne, fleet-standard) — the checks:

Check What it catches
Contradiction two concept pages (or a page vs a fresh source) assert conflicting facts with no supersedes link
Staleness a page's youngest citation is older than its type's freshness window (market data: hours/days; thesis: weeks)
Orphan a concept page with no inbound links and not in any index.md
Broken link a markdown link / citation that resolves to nothing
Provenance audit any factual claim (esp. a number) on a page with no backing # Citation — the inverted-OKF hard rule

Output: a Lint report (Supabase row + dashboard surface) and, for hard-fail classes (provenance, fabricated number), auto-quarantine the offending page until repaired. Lint is the standing enforcement of the §3 guardrail over time (the gate enforces at write; Lint re-enforces against drift, supersession, and newly-arrived contradicting sources).

Policy (Viska-instance, viska-pm) — the config the engine reads:

Fork resolved: the engine is not rebuilt per-agent (rule #18 — it is the net-new fleet component named in the ratified verdict, mnemosyne's to build once). Viska is its first consumer and supplies only policy. If mnemosyne's fleet Lint is not yet shippable when Viska needs it, the fallback is a thin Viska-local Lint implementing the same check contract, to be retired into the fleet engine on parity — a strangler-fig within the strangler-fig, flagged as a sequencing risk (§6), not a license to fork the engine permanently.

2.4 Why two indexes is the correct reading of the fleet verdict

The verdict says index=graphify. Taken literally as "graphify is the only index" it contradicts the originator's own scale guidance and Viska's verified heavy-volume reality. The faithful reading: graphify is the index of the synthesis tier — the layer the verdict is actually about (the AI-maintained wiki). The raw-corpus retrieval index (pgvector hybrid) is a different layer the verdict never displaced; #86 built and verified it for exactly Viska's volume. This mirrors the fleet two-tier graph spec (graphify per-repo lens + a distinct master synthesis) and Karpathy's mature pattern: folder plus search, not folder instead of search.


3. Guardrail gate — the inverted-OKF hard rule

The deterministic gate L3 promises and §2.1 step 4 invokes. OKF SPEC §9 tells bundles to tolerate all mess and forgive everything because they are "partially agent-generated." A fund tool inverts that: a wrong number in a thesis is a liability, not acceptable noise. The gate rejects bad writes rather than forgiving them — and it is the same contract whether called at write-time (the maintainer pass, §2.1 step 4) or nightly (Lint, §2.3).

3.1 The reject predicates — one contract, two callers

A drafted delta PASSES only if every predicate passes; any FAIL quarantines the delta (§3.3). Each predicate is a pure function of (delta, current bundle, retrieval tier) — no model judgement, no network — so it is deterministic and re-runnable. Expressed once; the maintainer pass calls it write-time, Lint calls the identical set nightly over the whole bundle (drift re-enforcement).

# Predicate FAIL when Class
P1 Provenanced claim a factual assertion (esp. a number) on a concept page has no # Citation resolving to a retrieval-tier chunk from a real Source hard
P2 Grounded number a number does not trace to a pgvector retrieval chunk of a real Source (generative invention — recall #86: a VLM hallucinated "PFOF on Pro tier"; no generative pass may mint a value) hard
P3 Superseded contradiction the delta conflicts with an existing page claim and carries no explicit supersedes link to the claim it replaces hard
P4 Well-formed concept missing required type:, broken self-link, or dangling/unresolvable citation hard

One predicate set, two surfaces. The gate is not a write-time check and separately a Lint check — it is a single callable contract (proteus-owned, invoked from both n8n workflows). The maintainer pass enforces it before commit; Lint re-runs it against drift, supersession, and newly-arrived contradicting sources (§2.3 Provenance/Contradiction rows ARE P1/P3 over time). A predicate change lands in one place and both surfaces inherit it.

3.2 Untrusted-text posture

The maintainer LLM reads untrusted document text (filings, broker drops, scraped research) — treat every byte as adversarial, and treat downstream consumers of the bundle the same way. Carry the RSS-cleanup arc's mitigations verbatim (already named in §2.0):

Mitigation Where
Prompt-partition the document body — never concatenate untrusted text into instruction context maintainer pass input framing
Tool-free pass — the maintainer node has no tool access while processing untrusted input (it drafts; it cannot act) §2.0 AI-Agent node config
Strip zero-width Unicode before processing (defeats hidden-instruction injection) parse stage, pre-LLM
Source-authenticate where applicable — DKIM/DMARC for email-sourced docs; reject/flag unauthenticated Ingest watcher
Sanitize at write-time — escape/normalise before the delta touches the bundle gate, pre-commit

3.3 Quarantine mechanics

A rejected delta is quarantined, never silently dropped — a dropped delta is invisible drift; a quarantined one is auditable.

Question Answer
Where a quarantine Supabase row (delta payload + failed-predicate codes + source-id + timestamp), proteus DDL §5 — no new infra (rule #18), same registrar substrate
How surfaced the nightly Lint report (§2.3) + the Viska dashboard quarantine queue
Who clears operator / viska-pm review — repair-and-resubmit (re-runs the gate) or discard with reason

Invariant (reinforces §2.1 step 4): a synthesis-write rejection NEVER blocks the retrieval-tier write or raw-source persistence. Steps 1–2 (raw Source + pgvector embed) commit unconditionally; the gate stands only between the maintainer draft and the synthesis bundle. Retrieval stays fully available — citable, queryable — even while its synthesis is quarantined. Worst case degrades to "raw recall works, synthesis pending review," never "the source is lost."

4. pgvector → synthesis-tier migration + the parity gate

The strangler-fig cutover (§1 L1), now precisely scoped by the §2.4 two-index resolution: graphify indexes the synthesis tier; pgvector-hybrid stays as the retrieval tier. Migration is a re-routing of the synthesis read-path, not a teardown.

4.1 What migrates vs what stays

Layer Today After cutover Disposition
Synthesis ("what's our thesis on X", cross-concept answers) re-derived from vector hits per query, nothing accumulates read from AI-maintained concept pages (index.md-first → graphify) migrates
Retrieval (#86 stack: pgvector + bge-m3, hybrid BM25/dense RRF, bge-reranker, contextual retrieval, table-aware chunking) serves both synthesis and fine recall serves fine recall + citation backing only stays — DEMOTED, not deleted

The demotion is unambiguous: pgvector loses its role as the synthesis store and keeps its role as the raw-source retrieval index. The #86 retrieval stack is touched in zero ways — same embeddings, same hybrid scorer, same reranker, same chunking. The only thing that changes is the Query router (§2.2) ceasing to route synthesis-shape questions through it.

4.2 The parity gate — metric

This resolves the open question "what metric defines the parity gate" (devlog 2026-06-24). The gate is a labelled eval set of real Viska analyst questions with gold answers, run against both paths (synthesis tier vs current vector-RAG baseline). The synthesis tier must match-or-beat the baseline on (a)(b)(d) and score zero on (c) before vectors are demoted.

# Dimension Measure Pass threshold (proposal — viska-pm sign-off)
a Answer-correctness graded vs gold answer (0–2: wrong/partial/correct) synthesis mean ≥ vector-baseline mean
b Citation-correctness / provenance every claim resolves to a real # Citation → retrieval chunk that supports it ≥ baseline, and ≥ 0.95 of claims correctly cited
c Zero-fabrication any answer asserting a number/fact with no supporting source HARD FAIL — a single fabricated number fails the whole gate (target = 0)
d Latency wall-clock p50 / p95 per answer p95 ≤ 1.25× baseline p95

(c) is non-negotiable and not averaged: one fabrication blocks demotion regardless of (a)(b)(d).

4.3 Eval-set design

Property Spec
Size 50–100 questions (proposal — viska-pm to size against question volume)
Composition ~40% synthesis-shape ("what is our thesis on X", "summarise our view of issuer Y"), ~40% retrieval-shape ("find the exact number / passage behind Z"), ~20% mixed (thesis + the filing line backing it) — mirrors the §2.2 router shapes
Sourcing viska-pm-owned input — viska-pm sources the real analyst questions and gold answers (lane §5). Synthetic questions do not count toward the gate.
Grading rubric per-question score on the four §4.2 dimensions; (a) human/LLM-judge against gold, (b)(c) deterministic against the §3 provenance predicates, (d) measured. Stored as a Supabase eval-run row + dashboard surface.
Re-run cadence on every synthesis-engine change (maintainer-prompt, guardrail, graphify config) and mandatorily immediately before demotion; a passing run is valid only against the engine version that produced it.

4.4 Rollback

Because the retrieval-tier pgvector index is untouched, rollback is a router flip, not a restore: re-route synthesis-shape queries back through the vector path. Zero data loss, no reindex, no migration to reverse — the demoted-not-deleted invariant is the rollback guarantee.

Gated on §3. The (c) zero-fabrication and (b) provenance checks reuse the §3 guardrail predicates (unprovenanced claim; number not traceable to a retrieval chunk) — one predicate set, now a third surface (write-time gate, nightly Lint, and the parity eval). The parity gate cannot pass until §3 is implemented.

5. Lanes

Work Owner
This spec + content/data contract + routing Hermes (this doc)
Ingest + Query n8n workflows (clone PDF-feed pattern; maintainer AI-Agent node) viska-n8n / pantheon-ops
Supabase DDL — concept registry, provenance ledger, retrieval index, Lint reports proteus
Lint engine (fleet-standard check contract) + graphify Tier-1 install path mnemosyne
Lint policy config + parity-gate eval set + sign-off viska-instance (viska-pm)
OCR/parse vendor (feeds Ingest §2.1 step 1) resolved by hermes-032 / #86 bake-off

6. Open questions / sequencing risks


Status


Hermes · constellation-hermes · docs/superpowers/specs/2026-06-24-viska-memory-system-design.md · commit c7d98b5