Viska Capital · Internal Technical Report

Mímir — Architecture, Capabilities & Roadmap The fund's AI research analyst: what we've built, how it runs, and where it's going.

Date 2026-06-23 Prepared for Operator · CIO Gulli · Óli Author Mímir Agent Basis 6-stream repo / deploy / board / docs / wiki scan

01Executive Summary

Mímir is Viska's AI research analyst. A simple Slack surface sits on a production system: Mastra agent framework, Supabase vector store, a separate n8n ingestion pipeline, ~1,200 passing tests.

Three things are true today

  • The platform is solid and live — clean staging→prod pipeline, graceful degradation on every optional dependency, full test coverage, observability.
  • We shifted from "a bag of tools" to "a skill platform" — capabilities now ship as self-contained, self-documenting, CI-enforced modules. First module live on prod today.
  • The next arc is "One Agent, Many Faces" — one Mímir safely serving Slack and the viska.gg web portal, with per-user data isolation enforced in the database. Designed; build on review-standby.

The strategic payoff

Mímir is positioned to become the single conversational interface to the entire Viska data estate — research corpus, the live $15.5M IBKR book, the 19-theme strategic grid, the quant conviction engine, and trading intelligence — from Slack and the web, for the whole team, with each person seeing only what they should.

$15.5M
NAV · 21 positions
~1,200
tests green
20→∞
tools → module rail
2
faces (Slack live · web next)

02What Mastra Is

Mastra (@mastra/core) is an open-source TypeScript framework for production AI agents. It provides the scaffolding we'd otherwise hand-build:

PrimitiveWhat it gives usIn Mímir
AgentLLM + system prompt + tool set; runs the tool-calling loopmimirAgent, gpt-5.2, ≤5 tool-calls/turn
ToolsTyped, schema-validated functions the LLM can invoke20 tools (Zod schemas)
MemoryPer-user working memory + per-thread history → Postgresworking (per Slack user) + thread (last 20)
Storage / ObservabilityPostgres-backed store + span tracingshared PostgresStore, mastra_ai_spans
RequestContextPer-request state through the tool chain, no globalsSlack handle, user, citations, face/JWT

Model-agnostic. Mímir uses OpenAI today — gpt-5.2 (reasoning), gpt-4o-mini (lightweight synthesis), text-embedding-3-small (1536-dim embeddings) — but is not locked to it.

03How Mímir Is Deployed

Runtime: Node 24, Fastify HTTP, Slack Bolt in Socket Mode (outbound WebSocket — no public inbound URL). Boots observability → advisory connectivity checks → routes → Slack. Every dependency degrades gracefully.

Deploy pipeline (Railway)

feature/* ──local──► develop ──auto──► mimir-staging   (Dev Slack, #viska-agent-dev)
                                          │ verify
                       main ◄── PR merge ─┘
                        │
                  auto-deploy (branch-tracked)
                        ▼
                     mimir  (PROD ← Viska Capital Slack)
Today's proof: last30days shipped PR #45 (develop→main) → prod deploy 95440512-… SUCCESS @ a81dfa5987a3, 11:06Z.

The Mastra-service vs n8n-pipeline boundary

Mastra service (interactive)

Slack events · the agent loop · real-time query embedding · vector search (hybrid_search_v3, RRF) · sector scoring · prefs · App Home · the SSE chat endpoint · error alerts.

n8n pipeline (batch)

Dropbox docs → chunk → embed → Supabase · the 6-stage daily report · error triage · a 15-min error-monitor cron that calls back into Mímir.

Live DB schema is owned by the ViskaDB repo's migrations. Embedding model + dimension are locked to match ingestion.

04Total Architecture Map

┌──────────────────────────── SURFACES ────────────────────────────┐
│  Slack (@mention, /mimir, App Home)        viska.gg /chat (next)   │
└──────────────┬────────────────────────────────────┬──────────────┘
               │ Bolt Socket Mode                    │ HTTPS/SSE (CF Worker — deferred)
               ▼                                      ▼
┌──────────────────── MÍMIR  (Mastra service · Railway) ────────────────────┐
│  Fastify + Slack Bolt                                                     │
│   mimirAgent  (gpt-5.2, maxSteps 5)                                       │
│    ├─ 20 tools (thin adapters → shared core)                              │
│    │   corpus search · live market/crypto · reports · prefs · Icelandic   │
│    │   sector sentiment · web search · custom HTML report                 │
│    └─ MODULE SYSTEM  (registry → manifest SSOT → 3 surfaces + self-explain)│
│          └─ last30days  (BASE: reddit/hn/github/web ·                     │
│                          FINANCE: edgar/gdelt/polymarket/+dormant tier-2) │
│   Memory: working (per user) + thread        RequestContext (per call)    │
│   RAG: embed → hybrid_search_v3 (vec .7 + kw .3, RRF) → synthesise        │
│   Face layer (built, not yet consumed): face-jwt · face-resolver          │
└──────┬──────────────────────────┬───────────────────────────┬────────────┘
       ▼                          ▼                           ▼
  ┌──────────┐        ┌────────────────────────┐     ┌──────────────────┐
  │ OpenAI   │        │ Supabase (pgvector+PG)  │     │ n8n pipeline      │
  │ LLM/embed│        │ embeddings · mimir_*    │     │ ingest · reports  │
  └──────────┘        │ viska_tickers · memory  │     │ error triage      │
  ┌──────────┐        │ sector scores · errors  │     └────────┬─────────┘
  │ Binance/ │        └────────────────────────┘    Dropbox (docs) ◄┘
  │ DeFi/    │
  │ Perplex/ │
  │ Miðeind  │
  └──────────┘

Four layers: external data sources → durable state (Supabase + n8n ingest) → the Mímir runtime (Mastra + tools + modules + RAG) → user surfaces. The face layer is the seam that lets one agent safely serve many surfaces.

05How We Build Skills Now — the Module System

Capabilities used to be 20 flat, hand-registered, inconsistently-named tools. No concept of "a skill," no slash commands, no guide, no self-explanation. The module system (live on prod) replaced that with a rail.

A module is a folder src/modules/<id>/ declaring everything through one manifest (the single source of truth). A central registry wires that one manifest into three user surfaces plus a docs plane:

Manifest-as-SSOT, CI-enforced

A module's prose lives in one place; the dashboard guide, /mimir help, and self-explain all read it. A conformance test fails the build on drift — docs can't go stale.

Boot-safe + DRY

One broken module can't crash startup. Chat and slash both call a shared logic/ core, so surfaces never diverge.

Proof module — last30days: "what was said about a topic in the last ~30 days, engagement-ranked, with sources." Fans out to many sources in parallel (one dead source never fails the call), normalises per-source, synthesises one cited brief. Finance mode adds SEC EDGAR, GDELT news, prediction-market odds, and auto-detects fund tickers. ~1,197 tests green, live on prod.

Net for Viska: adding an analyst capability is now a bounded, repeatable task — write a module, get three surfaces + a guide + tests for free.

06Face Agents — "One Agent, Many Faces"

One Mímir, multiple surfaces. A face controls what data the agent may see and do — enforced in three layers of increasing trust.

  1. Capability — which tools are mounted this request.
  2. Data — which DB rows it can read/write (Supabase row-level security via a face-scoped, short-lived signed token).
  3. Memory — which conversation history it recalls (per-shard).
Critical principle: the agent never picks its own face. A trusted gatekeeper at the door (Slack middleware now; a Cloudflare Worker for the web later) mints a short-lived ES256 JWT with the claims. Postgres enforces them — a prompt can be talked past; row-level security cannot. That's what makes a multi-user web portal safe, with zero data bleed.

Status — audited against live code

ArtifactStatus
JWT minter, Slack-lane resolver, identity bridgeLIVE on prod
Web chat endpoint (/api/chat SSE)LIVE (PR #42)
Slack↔web memory carry-overVERIFIED

The honest gaps (work remaining)

GapWhat's missingOwner
G2Request-scoped client factory. Today 17 places use the service key (RLS off). Goes first; gates everything.ViskaMimir
G1The face JWT is minted but never consumed — tools still run on service key. The no-bleed gate.ViskaMimir
G3Formalise three ingress lanes (slack / web / internal).ViskaMimir
G4Tenancy schema + identity unification + RLS.ViskaDB
G5 / G6Shard-namespaced memory IDs · purge 2 dead memory backends.ViskaMimir

Order: G2 → G1 → G4 (ViskaDB) → viska.gg edge-auth (ViskaFront) → G5 → G6. Feeds the larger per-user tenancy epic (board #7), gated on ViskaDB identity-unification (Slack text-IDs vs Supabase UUIDs) + OpSec JWT-revoke. Operator-review standby — not started unprompted.

Governance flag: the two specs defining this arc are currently in the Viska-Wiki only — not committed to git. They should be relayed into the repo (low-risk doc commit) so the design of record sits with the code.

07What This Unlocks for the Fund

Viska is a $15.5M macro fund (21 live positions — metals/mining, energy, grid/power, semis) run against a 19-theme × 16-quarter strategic grid (Gold 22.1% NAV, Copper 15%, Energy Infrastructure 14.6%, Aluminium 11.6%…). The edge is regime-aware theme rotation — so the scarcest resource is early, synthesised signal on theme shifts, not raw data volume.

Mímir is the interface that turns the fund's data estate into that signal, conversationally:

The trajectory: Slack research assistant (today) → multi-surface, finance-aware analyst over corpus + live data (in progress) → the single conversational front-end to the entire Viska estate — research, book, themes, quant signal — for the whole team.

08Work State — GitHub Boards (SSOT)

BoardGovernsHeadline
#7 Mimir Agentruntime, migration, sharding, tenancy#44 module system + last30days + face-chat DONE; #26 EPIC "One Agent, Many Faces"; per-user tenancy epic
#9 Councilagent-ensemble, quant enginecouncil substrate + face-agents in progress; engine v0 done
#10 OpsOpSec / credential hardeningJWT-revoke + key hygiene — gates the tenancy RLS work
#8 Trading IntelIBKR/Flex ingest + read path#35 Mímir read-only trading tool todo
#4 Dashboardviska.gg surfaces#40 Mímir Assistant on viska.gg (proposed)

Shipped (30d): migration to Viska org · sharding foundation S1/S3/S4/S5/S6/S8/S9 · module system M1–M5 · last30days finance-signal · prod ship today.

Active blockers: Phase-C cutover (drifted Phase-A apply) · sharding S2/S7 (needs operator relaunch + CI-gate call) · Tier-2 finance keys (dormant) · face/tenancy build (greenlight-gated).

09Logical Next Steps

Now — low-risk, unblocks signal

N1
Deposit the two Tier-2 keys (Alpha Vantage, StockTwits) → live-smoke last30days finance mode → attest. (Review Railway log level first — these APIs put keys in the URL.)
N2
Relay the two face-agent specs into git (low-risk doc commit) so the design of record sits with the code; file the 4 outstanding board cards.
N3
Run the full credentialed robotization test to close out last30days verification.

Next — the operator's decision

X1
Greenlight the face/tenancy arc — or explicitly defer. If go: first task G2 (client factory); critical path = ViskaDB identity-unification + OpSec JWT-revoke. The single biggest lever for "Mímir on viska.gg for the whole team."
X2
Resolve the T12 forecast-personas decision — should the 5-persona forecast engine back the face personas? Spec complete; build paused on this call.

Then — capability depth (mostly externally-gated)

T1
Trading-intelligence read path (#35) — read-only views over the live book to reason about positioning vs the theme grid.
T2
Visualisation arc (charts in Slack/reports) — gated on a chart-library verdict + design deliverables.
T3
Unblock Phase C (data-lake cutover) · last30days footer-gap fix (distinguish "empty" from "blocked").