Viska Capital · Engineering

Face Agents — deployment & persona roadmap

How the Mímir agent runs in production today, what a "face" is, and the staged buildout that takes the platform from a single shared backend to per-user, per-context faces — plus the forecast personas planned on top of it.

Prod live · Slack Socket Mode First face iteration on viska.gg T12 personas in-flight Per-user tenancy: fast-follow

1 · Current Deployment

A single Node.js host on Railway serves both surfaces — Slack (Socket Mode) and the viska.gg web edge — over one Mastra agent backed by Postgres conversation memory and a Supabase pgvector corpus.

Surfaces / Edge
Slack — Viska CapitalSocket Mode · app_mention, IM, App Home
viska.gg web edgeViskaFront · route → face · Bearer MIMIR_API_SECRET
n8n workflowsdaily report · Dropbox link
Host — Mastra agent
Fastify :3000node src/index.js · /health
Mastra agent (EN+IS)OpenAI GPT · 15 tools
/api/chat (SSE)face-aware · carry-over
/mimir/forecastT12 · in-flight
State / Services
Mastra PostgresStoremastra_threads/messages · per-user resourceId
Supabase pgvectorembeddings · hybrid_search_v3
OpenAI · Cohere · PerplexityLLM · rerank · web fallback

Runtime

Node.js · Fastify HTTP + Slack Bolt started in parallel from src/index.js. Health on /health, 120s timeout, restart-on-failure.

Deploy

Railway project viska-mimir-agent. Prod mimirmain (branch-tracked); mimir-stagingdevelop. Pre-push gate guards main.

Memory

Mastra PostgresStore keyed by resourceId. Slack↔web carry-over verified via email→slack identity bridge.

LayerComponentStatus
SlackSocket Mode — app_mention / IM / App HomeLIVE
Web/api/chat SSE, face-awareLIVE
MemoryMastra PostgresStore (per-user isolation)LIVE
IdentitySlack↔web carry-over (email bridge)LIVE
CorpusSupabase pgvector · hybrid_search_v3 + Cohere rerankLIVE
ForecastPOST /mimir/forecast (T12 personas)IN-FLIGHT

2 · Face Agents — what shipped first

face = f(route / page context), derived at the edge from a verified session. One backend, but each page context drives its own instructions(face) — the same user gets an analyst posture on a research page and an orientation posture on the homepage, with conversation carried over from Slack.

First iteration — deployed & responding on the viska.gg homepage

The web-home face is live: the homepage routes a verified visitor through the edge into the shared Mímir backend, the host validates the face, injects the homepage instruction posture, and streams a response — carrying over any prior Slack/web conversation for the mapped user. Behaving as expected.

operator-confirmed 2026-06-27 · v1 scope = face-chat only (no new per-user write surface) · per-user write tenancy is fast-follow (§4)

v1 request contract — edge → host

POST /api/chat   // SSE, Bearer MIMIR_API_SECRET
{
  "message":    string,   // required
  "userId":     string,   // verified id / email
  "threadId":   string,   // optional
  "role":       "client", // operator via edge only
  "face":       string    // route-derived, NEW
}

Face taxonomy — starter set (extensible)

facecontext
web-homelanding / overviewLIVE
web-researchresearch / corpusplanned
web-portfolioholdings / positionsplanned
defaultunmapped fallbackLIVE
Honest status: v1 is intentionally scoped to face-chat. The data path is still shared service-key; per-user write isolation (JWT-consume, tenancy tables, dead-layer purge) is the staged buildout below — not yet in production.

3 · Buildout Roadmap — G1 → G6

The path from "shared backend with face-chat" to "per-user, per-face, zero-bleed." Ordered by gating dependency; ownership split across three repos.

live / shipped in-flight planned
v1Face-chat on viska.gg (web-home)LIVEViskaFront · ViskaMimir

Edge derives face from route, host validates + injects instruction + falls back to default. Conversation carry-over verified.

G2Request-scoped client factoryplannedViskaMimir

Replace 17 inline service-key clients with one central factory. Prereq for G1 — without it, the JWT-consume is 17 divergent edits.

G1Consume the face JWT (no-bleed gate)plannedViskaMimir

JWT is already minted (ES256, S5/PR#38) but never consumed — shard jurisdiction is metadata enforced nowhere. Wire Lane-A per-user writes to build from the per-request user JWT.

G3Split service-key into three lanesplannedViskaMimir

Lane A per-user writes (5 sites) → user JWT · Lane B shared corpus (7) → restricted read role · Lane C system (5) → narrow service role off the request path.

G4Per-user doc / artifact tenancyplannedViskaDB

New tenancy tables with RLS default-deny auth.uid() = user_id, consumed via G1's JWT client. Lets users build own docs/reports with zero cross-user bleed.

G5Stable resourceId cutoverplannedViskaMimir · ViskaDB

Unmapped portal users are keyed on mutable email (GDPR/merge hazard). Cut memory resourceId over to a stable user id; keep email lookup only for carry-over.

G6Purge dead memory layersplannedViskaMimir

Stop writing the write-only pgvector mimir_conversation_memory (rot); retire the host-files web memory (shared-host files = bleed surface).

4 · Forecast Personas — planned deployment

On top of the face layer, T12 deploys distinct forecast personas behind POST /mimir/forecast. Each persona pre-declares the variables it watches — so the same event hitting two personas cannot honestly cite the same drivers, forcing genuine lens divergence. Model: claude-haiku-4-5, temperature 0.

Macro Bull

macro-bull

Bullish macro lens — equity upside, policy stimulus, growth surprises.

watches: equity_upside · policy_stimulus · growth_surprises

Macro Bear

macro-bear

Sober / hawkish-hold lens — stagflation risk, credit-cycle stress.

watches: credit_spreads_hy · dxy · real_yield_10y · vix · recession_prob

Commodities Cycle

commodities-cycle

Commodity rotation — supercycle, supply shocks, storage / backwardation.

watches: supply_shocks · backwardation · inventory

Geopolitics Risk

geopolitics-risk

Geopolitical tail-risk lens — sanctions regimes, supply-chain disruption.

watches: geo_tail_risk · sanctions · supply_chain

Portfolio Mechanics

portfolio-mechanics

Portfolio dynamics — rebalancing flows, factor rotations, leverage cycles.

watches: rebalance_flows · factor_rotation · leverage

Extend-to-7 (optional)

energy-fundamentals · central-bank-econ

Two further lenses held in reserve — energy fundamentals and central-bank economics — deployed if forecast coverage needs them.

status: optional · not in core 5

Endpoint shape — POST /mimir/forecast

// request
{ event_id, event_title,
  event_type: "macro|price_threshold|portfolio|narrative",
  resolution_criteria, resolves_at,
  persona_slug, context }
// response
{ p_value: 0..1, cited_vars[≤8],
  rationale, model, tokens_in, tokens_out }

Deployment budget

Modelclaude-haiku-4-5
Determinismtemp 0 · structured output
Per call~$0.0031 · 0.8–1.5s p95
Ceiling50 calls/day ≈ $5/mo
StatusIN-FLIGHT · P1

5 · Persona ↔ Face Convergence

Faces and personas are the same instruction-switching mechanism at different altitudes. The open design question is how they unify before either resumes — to avoid shipping two instruction systems into production at once.

Option 1 recommended

Persona as a dimension inside buildInstructions(face, persona) — one builder, persona threads through runtimeContext.

Option 2

Each persona is its own face + shard set. Rejected — registry sprawl, identical shard sets, wrong altitude.

Option 3

T12 ships standalone, reconcile later at P3. Rejected — guaranteed rework, two systems live simultaneously.

Decision pending: a short design note (builder signature + runtimeContext persona key) reviewed by viska-pm at next-arc planning — before P3 or T12 resumes. Surfaced 2026-06-12.