Canonical application guide · 13 August 2026

Mímir Agent Application

How the live agent works, what it remembers, what it can retrieve, where it excels, where its boundaries are, and how the product evolves safely.
32 registered tool IDsSlack + portalStructured + semantic retrievalPostgres memoryRailway production

Mímir Agent Application#

Purpose of this document#

This is the canonical guide to the Mímir application as it exists now: how a request enters the service, how the Mastra agent is assembled, what it can retrieve, what it remembers, what it cannot see, what each tool does, how answers reach Slack and the portal, how the service is tested and deployed, which capabilities have been proven in production, and which parts remain roadmap rather than runtime.

The document deliberately separates five states that older architecture notes sometimes mix:

StateMeaning
LiveThe path is registered in the production application and available when its binding is healthy.
Production-provenA live control exercised the deployed path and left a durable receipt.
Implemented, not primaryCode exists but the live entry point does not currently route through it.
Open riskThe current implementation works but has a known security, completeness, or operating limitation.
PlannedA ratified spec or open GitHub issue describes it; it is not current behavior.

Source code is the authority for runtime behavior. GitHub issues and production receipts establish work state and proof. Specifications establish intended future architecture. Historical docs are useful context but do not override current code.


1. Product definition#

Mímir is Viska Capital's internal research agent. Its primary interface is Slack; it also exposes a server-sent-events chat API for the Viska portal. It is not designed as a general chatbot. Its core job is to turn Viska's research corpus, fund positioning, strategy data, and selected live-market sources into a concise investment view.

The live agent is intentionally opinionated:

Mímir's current strongest workflow is:

LIST → select up to three documents → DIVE into complete bodies → connect the evidence to positions, strategy, and market data → optionally generate a report.

2. System boundary#

2.1 What this repository owns#

This repository is the Mastra application layer. It owns:

It does not own the upstream research ingestion pipeline or database schema. Ingestion and normalization are owned by ViskaN8N; schema, grants, RPCs, and RLS are owned by ViskaDB. GitHub and credential bindings are operated under the Viska fleet ownership model.

2.2 External systems#

SystemRole
SlackPrimary client surface through Bolt Socket Mode; mentions, DMs, threads, buttons, commands, and App Home.
RailwayHosts production and staging Node services; production tracks main.
Supabase/PostgresResearch corpus, structured parcels and bodies, fund/strategy surfaces, user settings, query logs, feedback, and Mastra persistence.
OpenAI / configured Mastra model providerMain answer model; OpenAI also supplies embeddings and OpenAI-only secondary synthesis surfaces.
CohereOptional reranking for vector/hybrid corpus results.
n8nResearch ingestion, report workflows, Dropbox link resolution, and selected operational webhooks.
Market APIsMarket candles, crypto, derivatives, DeFi, sentiment, and optional web/recent-community research.
Miðeind and BÍNIcelandic grammar, translation, and morphology.

2.3 What the runtime agent cannot access#

The deployed Mímir model has no tool for:

The coding seat uses the Viska Wiki to maintain the application, but that is a separate agent environment. The client-facing runtime does not inherit the coding seat's memory or tools.


3. Runtime topology#

Slack app_mention / DM / participating thread
                    │
                    ▼
          Slack Bolt Socket Mode
                    │
                    ▼
       handlers/mention-mastra.js
                    │
      auth + date + language + request context
                    │
                    ▼
          lib/mastra-agent.js
        static system prompt + model
          32 registered tool IDs
                    │
       ┌────────────┼──────────────┐
       ▼            ▼              ▼
 structured DB   vector corpus   external/n8n
   retrieval       retrieval       services
       └────────────┼──────────────┘
                    ▼
         lib/mastra-stream.js
                    │
     rotating Slack stream messages
                    │
                    ▼
       source links + action buttons

The same mimirAgent also serves the portal:

viska.gg edge → POST /api/chat → handlers/chat-http.js
             → SSE text / sources / actions / meta / done
             → same agent, tools, model, and Mastra memory

3.1 Process startup#

src/index.js starts two services in parallel:

  1. Fastify, exposing health and application endpoints.
  2. Slack Bolt, using an outbound Socket Mode WebSocket.

If Slack credentials are absent, Fastify still starts. This supports local development and health checks, but it also means /health proves process liveness only; it does not prove Slack, corpus, or memory readiness.

At boot:

3.2 Slack ingress#

The live Slack handler accepts:

The handler:

  1. strips the Slack mention;
  2. checks the viska_chat_users allowlist;
  3. adds today's date and language guidance;
  4. creates a request-local Mastra RequestContext;
  5. posts one temporary progress line;
  6. invokes the agent with maxSteps: 5;
  7. streams answer text into Slack;
  8. enriches eligible broker sources with Dropbox links;
  9. adds deep-dive, report, follow-up, and feedback actions;
  10. records query telemetry and semantic conversation copies;
  11. removes the progress line.

The progress mechanism updates one status message rather than flooding a thread with one message per tool call.

3.3 Portal ingress#

POST /api/chat is an authenticated SSE endpoint. It accepts a message, user identity, optional thread, caller metadata, role, and page-derived face. It emits:

The portal maps email identities to Slack user IDs where possible so both surfaces can share the same user-scoped working memory. If no mapping exists, the verified portal identity becomes the memory resource key.

3.4 Response delivery#

Slack's stream API rejects oversized individual messages. Mímir now rotates a generated answer into additional messages in the same thread before 3,500 characters. The exact generated text is preserved across those messages.

Non-streamed environments fall back to agent.generate() and a normal Slack reply. Empty generated streams fall through to an explicit no-answer message rather than leaving the user with silence.


4. Agent construction and instructions#

4.1 Agent singleton#

src/lib/mastra-agent.js creates one Agent named Mimir with:

Changing MIMIR_MODEL and restarting changes the answer model without a code deployment. OpenAI-only secondary tools continue to use an OpenAI model even when the main agent is routed through another provider.

4.2 Main instruction hierarchy#

The system prompt gives the agent the following priorities:

  1. Be Viska's analyst, not a search-results relay. Take a position and explain what Viska should do.
  2. Lead with the call. Open with one bold action-oriented conclusion.
  3. Use evidence compactly. Hard figures, ratings, prices, and source claims must come from retrieved evidence.
  4. Use at most one short verbatim quote. The rest is synthesis.
  5. Check current positioning and strategy before asset advice.
  6. Never present a research price as current. Run it through price_context.
  7. Search the internal corpus first. Web search is a last resort.
  8. Choose structured retrieval when the query maps to known fields. Use vectors for conceptual or paraphrased questions.
  9. Read every ordered body part for long articles. Do not analyze only the first excerpt.
  10. Never reproduce a private newsletter body in full. Synthesize it and quote only briefly.
  11. Use reader language. Internal terms such as “OKF,” parity, watermark, and indexed-through do not appear unless operational details are requested.
  12. LIST before DIVE. Index listings are metadata-only; full analysis and validated links follow after the user selects up to three titles.
  13. Be honest about empty results and coverage. A clean empty query does not prove an empty corpus if the read surface may be unavailable.
  14. Keep Slack concise. No markdown headers, no default bullet dump, and no AI-fingerprint filler language.

4.3 Request-local context#

The application augments the static instructions with request facts:

The model cannot choose or rewrite these request facts.


5. Memory architecture#

“Memory” in this application refers to several different stores. Only some are read back into the model. Keeping these categories separate is essential.

5.1 Active Mastra conversation memory — live#

The active runtime memory is @mastra/memory backed by one shared @mastra/pg PostgresStore.

MemoryKeyScopeWhat the model receives
Thread historySlack thread timestamp or portal thread IDOne conversationUp to the last 20 messages.
Working memorySlack user ID, or mapped portal identityOne user across that user's threadsA compact profile containing research focus, preferred organisations, sectors, recurring topics, query patterns, date habits, language, and context notes.

The agent automatically reads and updates working memory through Mastra's updateWorkingMemory mechanism. Thread history enables follow-up questions without requiring the user to restate the document or topic.

Slack and portal can share working memory when the portal identity bridge resolves to the same Slack user. Thread history remains thread-specific.

Storage behavior#

5.2 Query logs — active, not model memory#

mimir_queries stores the user query, last-used tool, answer length, and up to 4,000 characters of response text. App Home uses this for recent-query features.

This table is operational history. It is not automatically recalled into an answer.

5.3 Semantic conversation copies — write-only in the current primary path#

After a Slack response, mention-mastra.js writes user and assistant messages with embeddings to mimir_conversation_memory.

The current primary handler has no corresponding read path. Therefore:

5.4 Legacy file-based adaptive memory — implemented, not primary#

The repo contains a second memory subsystem under:

It can maintain per-user JSON profiles, session summaries, team goals, and theme frequencies in a git-tracked memory/ directory. The legacy handlers/mention.js reads and writes it.

The live src/index.js routes Slack to mention-mastra.js, not mention.js. No memory/ files are currently tracked. This subsystem is therefore code present in the repository, not active memory for the deployed agent.

5.5 Preferences and identity#

mimir_user_prefs and viska_chat_users provide user configuration and access control. Preferences can include watchlists, preferred organisations, language, and digest choices. They are explicit user settings, not free-form agent memory.

5.6 What memory does not contain#

The runtime does not remember:

Research documents are retrieved on demand through tools. They are knowledge sources, not conversation memory.

5.7 Current memory limitations#

5.8 Planned memory model#

The ratified sharding spec proposes:

That architecture is planned under epic #26 and phase #31. It is not the current runtime.


6. Research and data architecture#

6.1 Two complementary retrieval systems#

Mímir uses two research paths:

Structured research#

Structured retrieval reads stable research parcels and private article bodies through scoped PostgREST calls.

Use it when the request contains a field the database understands:

Advantages:

Vector/hybrid retrieval#

Conceptual questions use embeddings, keyword search, and optional reranking over research chunks. Structured searches also fall back to vectors when the concept does not map cleanly to controlled fields or when the structured surface cannot answer.

Advantages:

The two paths are complementary. Structured parcels supersede vectors for exact contracts; vectors remain the conceptual fallback.

6.2 LIST and DIVE#

LIST#

okf_corpus_page and okf_corpus_totals operate on the research index. They provide:

A page is not mistaken for the total. Requests up to 50 rows should use one page call. The client sees a concise coverage note rather than internal parity language.

The index is metadata-only. It does not imply that article bodies or links were read.

DIVE#

okf_corpus_search retrieves a named private body or searches body text. Long bodies are split into ordered parts of at most 12,000 characters, at paragraph boundaries where possible, with no discarded text.

Body results include normalized links stored with the stable parcel. Mímir may surface validated links inline beside the claim they support. Missing or rejected links are not reconstructed.

6.3 Stable revisions#

Structured body, parcel, and visibility-probe reads filter to status=stable. Deprecated revisions remain historical database records but cannot contaminate normal answers.

6.4 Source coverage language#

The research index is a live view of current database rows. A newer visible article proves that article is present; it does not prove that every source item has been reconciled.

The reader-facing default is:

Current articles are visible, but complete source coverage has not yet been independently verified.

Operational parity, watermark, and indexed-through values remain available for engineering diagnosis but are not normal client vocabulary.

6.5 Access pattern#

Structured research and market/fund reads use the pgGet()/pgRpc() helper:

The broad SUPABASE_SERVICE_KEY still exists in other runtime call sites. Replacing those reads and writes with explicit RO/RW identities is open issue #126.


7. Tool inventory#

The current agent map contains 32 registered tool IDs:

The example_skill module is a live reference/template, not a meaningful client capability. Client value comes from the remaining 31 surfaces.

7.1 Research corpus and document tools#

ToolCapabilityBest useImportant limit
okf_corpus_searchStructured parcel search, named private-body retrieval, and body full-text search; vectors on conceptual fallback.Fresh research by source/ticker/date; full article analysis.Metadata max 20; body results max 3; explicit body misses do not vector-fallback.
okf_corpus_pageComplete cursor-paginated research index.Recent documents, publisher/source lists, exhaustive metadata navigation.50 rows per page; no bodies or validated links.
okf_corpus_totalsComplete totals by kind and publisher, publisher collisions, and coverage state.“How many reports?” and coverage inventory.Counts database rows; coverage status may remain unverified.
corpus_searchGeneral hybrid/vector search across all organisations.Broad conceptual or paraphrased research questions.Quality depends on chunking, embeddings, and optional reranking.
search_by_orgHybrid search constrained to one named organisation.“What does Goldman say about oil?”Requires one explicit organisation.
scan_doc_matchesFinds reports whose titles or narrative mention a company, ticker, or term.Coverage and mention discovery.Entity ambiguity can trigger weak or repeated searches.
system_two_briefingDetailed briefing on a specific document.Broker-note deep dives.Secondary synthesis is OpenAI-only.
librarian_scanMeasures how often a term appears across document days.Recurrence and breadth checks.Lexical occurrence is not conviction or topicality.
doc_manifest_listLists documents ingested for a specified date.“What came in today?”Uses ingestion date conventions; not a content search.
discoveryLists publishing organisations or publication frequency.Corpus activity and source coverage.Organisation activity is not article analysis.
doc_link_lookupResolves a specific document's Dropbox link.Direct access to a known broker document.Requires a known document identity and link workflow.

7.2 Portfolio, strategy, and market tools#

ToolCapabilityBest useImportant limit
get_positionsReads the current Interactive Brokers book, value, weight, cost basis, and unrealized P&L.Ground any recommendation in current exposure.Accuracy follows the latest available position snapshot.
get_position_historyReads one ticker's EOD position trajectory.Explain adds, trims, and exposure changes.One ticker; limited retained history.
get_theme_transitionsReads entered, exited, scaled-up, and scaled-down themes.Explain portfolio rotation by theme.Depends on upstream transition production.
get_strategyReads strategy reports and conviction verdicts.Test whether an idea fits Viska's intended themes.Scope and freshness follow the strategy surface.
get_market_dataReads cached candles and invokes the authenticated fetch path on a miss.Mark research against current trading data.Vendor coverage and daily budget may return typed gaps.
price_contextCompares a research price with latest close and calculates delta/staleness.Mandatory before advice based on a report price.Requires a valid referenced price and available market data.
sector_sentimentReads computed sentiment, direction, deltas, rolling averages, and drivers for tracked sectors.Sector risk and trend questions.Limited to the configured sector set and scoring pipeline.

7.3 Live, web, and digital-asset tools#

ToolCapabilityBest useImportant limit
crypto_priceLive crypto pricing through exchange data.A current crypto quote or 24-hour context.Public exchange availability.
market_sentimentFear/greed and top funding-rate context.Crypto market mood.Indicator, not research evidence.
derivatives_dataFunding rates, open interest, and perpetual-futures positioning.Derivatives positioning by symbol.External API coverage and bindings.
defi_statsProtocol TVL, chain rankings, stablecoins, and DeFi overview.DeFi market structure.External API semantics and freshness.
web_searchExplicit web/internet search fallback.Off-corpus or explicitly requested current information.Deliberately not the default for financial research.
last30daysMulti-platform recent discussion across Reddit, Hacker News, GitHub, web, SEC/news/prediction markets for finance topics.“What are people saying lately?”Optional sources depend on API keys; engagement is not truth.

7.4 Workflow and personalization tools#

ToolCapabilityBest useImportant limit
trigger_reportStarts the daily-report workflow.Explicit report-generation requests.Depends on authenticated n8n workflow availability.
custom_reportBuilds a report from the current Slack thread.Turn a completed research discussion into a durable deliverable.Requires enough thread content and report pipeline health.
user_prefsReads and updates watchlist, preferred organisations, language, and digest choices.Personalize App Home and agent behavior.Explicit preferences only; not free-form memory.

7.5 Language and presentation tools#

ToolCapabilityBest useImportant limit
bin_lookupIcelandic morphology lookup through BÍN.Correct inflection and word forms.Morphology, not broad translation.
icelandic_translateEnglish-to-Icelandic translation.Explicit translation requests.Depends on Miðeind binding.
humanize_textRemoves formulaic AI phrasing and makes supplied text more natural.Rewrite an existing passage.It does not fact-check the passage.

7.6 Module-system tools#

ToolCapabilityStatus
last30daysClient-facing recent multi-platform research module.Active.
explain_skillExplains registered module capabilities from their manifests.Active registry tool.
example_skillEcho/reference module proving tool, slash-command, and App Home wiring.Active template; not a client research feature.

8. Proven production capabilities#

The following claims have live or process receipts. They are narrower than “the code exists.”

CapabilityProof statusReceipt
Production service boots, connects Slack Socket Mode, and serves health checks.Production-provenRailway deployment 4824852e succeeded on 2026-08-13.
Complete corpus totals and cursor pagination reach the registered Mímir tools.Production-provenIssue #124 / PR #129: 1,969 total at the time of control; 89-row Substack walk, two pages, zero duplicates. These are historical control figures, not current totals.
Structured source, date, ticker, and narrative filtering reaches live parcel rows.Production-provenIssue #117 / PR #118 and Railway controls.
Named private Substack bodies are retrievable through the Mímir application path.Production-provenIssue #122 / PR #123.
Long articles are delivered without application truncation.Production-proven at tool boundaryPR #140: 48,920-character control returned in five ordered parts; focused retrieval suite 53/53.
Stable revisions exclude deprecated parcel versions.Production-provenPR #145 and stable-v4 audit.
Stored normalized body links reach the application result.Production-proven at tool boundaryPR #142.
Stable Substack link surface contains no unsafe redirects or navigation links in the audited set.Production-proven for the audited snapshotFinal stable-v4 audit: nine retained related links, zero unsafe/chrome/unclassified remainder.
Real Mastra tool calls are observable by tool name.Production-provenPR #134 fixed nested ToolCallChunk.payload handling.
Slack can deliver long answers over multiple thread messages.Production-proven4,087 characters delivered across two messages after PR #148, with no msg_too_long.
Empty-stream and progress-flood failure classes do not leave users with silent or erratic replies.Regression-proven; prior production incidents reproducedResponse-delivery and think-post regression suites.
Mastra thread and working memory initialize in production.Production-observedCurrent startup logs show PostgresStore connected and memory enabled.

8.1 Acceptance still open#

The tool boundary has proven complete five-part article delivery. The final client-facing acceptance for issue #122 remains a fresh, fully delivered model synthesis that demonstrably uses evidence across all parts after the Slack split fix. A partial answer before the fix proved synthesis was occurring, but the delivery ceiling prevented a clean end-to-end acceptance receipt.

Validated links are proven in body results. Broad inline-link presentation across multiple selected articles remains a reader-level behavior to continue testing; metadata LISTs intentionally carry no links.


9. Where Mímir excels#

9.1 Named-document analysis#

A named title maps directly to a private body. This avoids broad retrieval noise, supports complete ordered long-body analysis, and can carry validated stored links.

9.2 Structured research discovery#

Questions that specify publisher, source type, ticker, narrative, or date can use indexed filters. This is faster, more complete, and more interpretable than pretending every question is vector similarity.

9.3 Cross-source synthesis with bounded scope#

Mímir is effective when a question names a theme, time window, and manageable source set. It can compare agreements, disagreements, catalysts, and risks across broker, internal, and Substack research.

9.4 Research-to-portfolio reasoning#

The combination of positions, history, strategy, market data, and price context lets the agent answer “what should Viska do?” rather than “what did the report say?”

9.5 Provenance and refusal#

Mímir is designed to withhold unsupported figures, false totals, invented links, and unavailable citations. Stable-only retrieval and explicit coverage notes make uncertainty visible.

9.6 Slack-native interaction#

Thread memory, progressive streaming, long-message rotation, buttons, report generation, and App Home make the agent useful without requiring analysts to leave Slack.


10. Current limitations and failure boundaries#

LimitationUser effectCurrent handling / owner
Index LISTs contain metadata, not bodies or links.A broad list cannot display every article's validated links.User selects up to three titles for DIVE.
Source parity is not independently verified.Mímir cannot promise every possible upstream article is present.Plain coverage note; source reconciliation is upstream.
Five tool steps per request.An exhaustive query combining discovery, many bodies, positions, prices, and synthesis may run out of steps.Split into a thread: LIST, DIVE, portfolio check, report.
Body search returns at most three rows.Large cross-article body sweeps need multiple turns or a purpose-built batch tool.Use index selection and bounded DIVE.
Structured metadata search returns at most 20 rows.Use the index tool for larger lists.okf_corpus_page supports 50 rows and cursor continuation.
Entity ambiguity.Short names such as “MYR” can route to the wrong company or empty searches.Entity-resolution protocol is open issue #61.
External API and webhook dependencies.Optional market, web, rerank, language, or report features can degrade.Typed errors and graceful fallback; bindings monitored separately.
Broad service credential remains in some call sites.The application has more database authority than the final design permits.Scoped RO/RW cutover is issue #126.
Public HTTP hardening remains open.Some current gates and health semantics are weaker than the intended final posture.Security issue #113; must ship red auth cases.
Slack authorization currently fails open on DB errors.A database error can bypass the allowlist check in current code.Explicitly in #113 scope; not a settled security posture.
Missing channel configuration currently warns rather than fully refusing all paths.Misconfiguration can weaken channel isolation.#113 changes this to fail closed.
/health proves process liveness only.A green health response does not prove Slack, corpus, or memory.Startup logs carry advisory checks; #113 proposes degraded health state.
Active memory is not face-sharded.Future page-specific agents would share user working memory.Epic #26 / phase #31.
Semantic conversation copies are not read.No long-term semantic recall despite embeddings being written.Either wire scoped recall or remove the misleading write path in dedicated work.
Legacy file memory remains in the repo.Documentation can mistake inactive code for current behavior.Treat mention-mastra.js as primary; retire or isolate legacy path later.
Documentation has drifted historically.Counts, models, handlers, and pipeline descriptions can become stale.This guide cites code and labels roadmap separately; update it with architecture-changing PRs.

11. High-value client queries#

The following query shapes make the best use of current capabilities.

11.1 Discover, then select#

What are the 15 newest Substack articles? Group them by publisher and give each a one-sentence thesis.

Follow with:

Read these three in full: “[title],” “[title],” and “[title].” Compare their conclusions and include validated links inline.

11.2 Cross-source investment thesis#

Across recent broker, Substack, and internal research, what is the strongest investable view on European defense-production capacity? Show agreement, disagreement, catalysts, named securities, and risks.

11.3 Research against the live book#

What does recent research imply for our current defense exposure? Check our positions and strategy first, then identify where we are overexposed, underexposed, or missing the theme.

11.4 Reprice an old call#

Find the latest research on Rheinmetall, compare every cited research price with the latest available market price, and tell us whether the original upside still exists.

11.5 Explain portfolio changes#

How has our position in [ticker] changed over the last month, and does the latest research support the adds or trims?

11.6 Find consensus and disagreement#

Compare the latest views from Goldman, Pareto, Citrini, and Doomberg on AI capital expenditure and credit creation. Where do they disagree, and which evidence should Viska trust most?

11.7 Locate evidence#

Which documents mention missile shortages, interceptor depletion, or defense-production bottlenecks? Group results by publisher and date.

11.8 Full long-article analysis#

Read “[article title]” in full. Reconstruct its argument in order, identify every investment category and named company, separate evidence from inference, and display validated links inline.

11.9 Monitor change#

What changed in the research corpus over the last seven days regarding European energy security? Focus only on genuinely new evidence or changed conclusions.

11.10 Produce a deliverable#

Turn this thread into a client-ready research report with the investment call, evidence, risks, catalysts, and sources.

12. Extension architecture#

12.1 Adding a core tool#

Core tools use Mastra createTool() with a Zod input schema. The preferred pattern is:

  1. define a narrow capability and trust boundary;
  2. reuse an existing data helper before adding a client layer;
  3. expose only structured arguments the model is allowed to choose;
  4. return both machine-readable fields and concise slackText;
  5. record source metadata when post-answer link enrichment applies;
  6. add one focused red/green test;
  7. register the tool in mastra-agent.js;
  8. update the system-prompt routing rule;
  9. prove the arriving production identity, not only a direct helper call.

A tool should not expose arbitrary SQL, arbitrary HTTP, or credential material.

12.2 Adding a module#

The module system is the preferred front door for a self-contained capability with multiple surfaces. A module can provide:

A manifest is validated on every registry read. Invalid or inactive modules are skipped rather than breaking agent startup. New modules begin through /spec-module, then add one manifest to the explicit registry.

The last30days module demonstrates the pattern. The example module is the reference template.

12.3 Adding a face#

Current portal faces are instruction addenda only. A new v1 face requires:

A face currently does not change tools, data grants, or memory. Those hard boundaries belong to the future sharded architecture.

12.4 Adding a data surface#

A data surface needs three owners:

Before wiring:

  1. inspect live schema;
  2. define exact columns and operations;
  3. assign RO versus scoped RW identity;
  4. add positive and denial controls;
  5. prove the request through Mímir's actual transport;
  6. disclose freshness and completeness semantics.

12.5 Adding a report or visual artifact#

Research conversation reports use the existing custom-report flow. New application-maintenance documents use OKF frontmatter in repo docs and render through /mockup for review. Client /skjol reports follow the separate report-bucket and projection contract.


13. Maintenance and operations#

13.1 Source-of-truth order#

For application maintenance:

  1. GitHub issue/project — work state and acceptance;
  2. Viska Engineering Wiki — decisions, directives, and cross-seat context;
  3. current repo code — running contracts;
  4. this guide and task-specific docs — orientation.

When prose and code disagree about runtime behavior, code wins and the prose is updated.

13.2 Development lifecycle#

  1. Open or use a board-backed issue.
  2. Read the live PRD and relevant vault decisions.
  3. Work on a Mímir-owned branch.
  4. Reuse existing helpers and patterns.
  5. Add the smallest test that fails on the reproduced defect.
  6. Run focused tests, then broader tests where appropriate.
  7. Commit as Mímir Agent <mimir@viskasjodir.is>.
  8. Push with the declared credential helper and verify the remote ref landed.
  9. Open a linked PR.
  10. Merge with rebase when ready.
  11. Production deploys from main; no local-tree production deploy.
  12. Verify Railway deployment state and the live behavior relevant to the issue.
  13. Persist the receipt on the issue and update architecture docs when contracts changed.

13.3 Testing#

The repository uses Node's built-in node:test runner. Tests cover:

A green helper test is not enough for a deployed capability. Production acceptance should exercise:

13.4 Deployment#

Railway tracks:

ServiceBranchPolicy
mimirmainProduction; branch-tracked only.
mimir-stagingdevelopStaging; local-tree deploys allowed when needed.

railway.toml starts node src/index.js, checks /health, and restarts on process failure up to three times.

Rollback is a previous successful deployment or a reverted commit on main. Production should never be repaired by pushing an unreviewable local tree directly to the service.

13.5 Observability#

Current observability includes:

Known limitation: tool telemetry records argument names, not argument values. This protects sensitive inputs but means logs can prove that sourceKind was available, not that it equaled substack on a specific call unless a separate receipt captures the result.

13.6 Credentials and bindings#

Documentation records environment variable names only. Credential values are injected through the approved environment and never enter repository docs or agent context.

Critical binding families include:

Changes to credential bindings are owned by ViskaOps; application call sites and probes are owned here; roles and grants are owned by ViskaDB.

13.7 Documentation maintenance rule#

Update this guide when a PR changes any of:

Do not append session narratives. Replace the affected primitive and cite the issue or specification that changed it.


14. Roadmap#

Roadmap items are grouped by value and risk, not presented as current behavior.

14.1 Near-term hardening#

Scoped database identities — #126#

Replace remaining broad service-key call sites with explicit RO and scoped RW paths. Preserve overlap until positive and denial controls pass. This is the most important security/operability improvement because it reduces what a prompt-injected or defective path can reach.

Public HTTP hardening — #113#

Add one timing-safe pre-handler, fail closed on missing auth/channel configuration, restrict the page proxy, expose memory degradation in health, remove obsolete routes, and check in unauthenticated red cases.

Entity resolution — #61#

Rank candidate entities, ask for confirmation only when confidence is low, and connect the resolved identity to canonical tickers. This improves recall and prevents repeated empty tool loops.

Complete issue #122 reader acceptance#

Run a fresh long-article prompt after explicit token-spend consent and verify evidence from all ordered parts arrives completely after the Slack split fix.

14.2 Memory and face architecture — #26#

Planned phases:

  1. face/shard registry and SQL helpers;
  2. request-scoped face client replacing broad credentials;
  3. dynamic agent instructions, tools, model, and memory by face;
  4. schema-per-shard memory and migration;
  5. identity facts promoted to a governed shared table;
  6. webhook auth audit and soak verification.

This changes faces from presentation context into real capability, data, and memory boundaries.

14.3 Portal expansion#

The portal v1 contract already supports page-aware framing and shared identity mapping. Future value includes:

14.4 Retrieval improvements#

Potential extensions that fit the current architecture:

14.5 Memory improvements#

Before adding more memory, decide what class each fact belongs to:

Future memory work should add:

14.6 New modules#

High-value modules that fit the registry contract include:

Each should be built only when there is a board-backed client use case, a data contract, and one clear acceptance query.


15. Recommended product sequence#

The shortest path to a stronger client product is:

  1. finish scoped RO/RW identities;
  2. close HTTP/channel fail-closed hardening;
  3. complete long-body reader acceptance;
  4. add entity resolution;
  5. expose LIST → selected DIVE → portfolio-context → report as a first-class portal and Slack flow;
  6. add memory inspection/deletion before increasing recall;
  7. implement true face capability/data/memory sharding only when additional faces are ready to ship.

This sequence improves trust and client utility without creating a second agent, a second corpus, or a second memory system.


16. Quick reference#

Best current use#

Avoid as one giant prompt#

Canonical implementation files#

ConcernFile
Entry point and routessrc/index.js
Slack request lifecyclesrc/handlers/mention-mastra.js
Portal SSE lifecyclesrc/handlers/chat-http.js
Agent registrationsrc/lib/mastra-agent.js
Main instructionssrc/config/system-prompt.md
Core toolssrc/lib/mastra-tools.js
Structured/fund/market toolssrc/lib/market-data-tools.js
Conversation memorysrc/lib/mastra-memory.js
Shared storagesrc/lib/mastra-storage.js
Streamingsrc/lib/mastra-stream.js, src/lib/slack-stream.js
Modulessrc/modules/registry.js
Deploymentrailway.toml, docs/DEPLOYMENT.md
Environment namesdocs/ENV-REFERENCE.md
Future shardingdocs/specs/SPEC-runtime-memory-sharding.md
Portal face contractdocs/specs/SPEC-viska-gg-face-chat-v1-contract.md

17. Final assessment#

Mímir is already a capable internal research application, not merely a chat wrapper. Its strongest differentiated asset is the combination of structured and semantic research retrieval, complete private article access, fund positioning, strategy, market context, and Slack-native delivery.

Its next stage should not be “more tools” by default. The highest-return work is to tighten identity boundaries, complete reader-level acceptance, resolve entities cleanly, make memory inspectable, and turn the existing LIST → DIVE → portfolio → report sequence into an obvious product workflow.

That preserves the current strengths while removing the places where a capable system can still appear uncertain, over-broad, or operationally opaque.