MemorySmith

MemorySmith is a single-host ASP.NET Core app for local structured memory management. It hosts a Blazor workbench UI, markdown pages, REST API, MCP endpoint, file-backed content storage, SQLite-backed security/audit metadata, local chat/agent workflows, and background maintenance in one process. The /memories page is the primary structured memory workbench. The repository ships with a live project wiki inside Data/Memories, and the app uses its own memory store as a testbed.

Quick Start

dotnet run --project MemorySmith.App --launch-profile http

Opens on http://localhost:5089 by default. Pages:

Route Purpose
/memories Browse, search, create, edit, delete memory records
/pages Create, search, edit, preview, and render markdown-backed pages from Data/Pages
/chat Memory-enhanced chat and agent mode with provider/model selection, streaming responses, context usage, attachments, and local chat history
/login, /admin/setup, /admin Local sign-in, first-admin bootstrap, and RBAC/audit/history administration
/health Scrollable stat cards, activity charts (queries/day, changes/day), maintenance telemetry
/variables Manage %VarName% path variables used in source link URIs
/about MemorySmith and third-party license information
/api/memories REST CRUD for automation
/api/pages, /api/search, /api/chat Page CRUD/search/rendering, combined memory/page search, and chat/agent/config API
/api/auth/*, /api/admin/* Current-user, login/logout, setup, user, provider, audit, and history metadata APIs
/api/stats, /api/health/*, /api/diagnostics Stats, readiness, and redacted operational diagnostics
/page-assets/* Static files from Data/Pages/assets for images, video, and audio embedded in pages
/mcp MCP JSON-RPC endpoint for AI agent tool use

The Project Wiki

Data/Memories/ is the structured live wiki for this project. Data/Pages/ is the markdown live wiki for longer-form user and agent-authored notes. The app defaults MemorySmith:DataPath to ../Data/Memories and MemorySmith:PagesPath to ../Data/Pages, so local runs read and write those records directly.

User-created markdown files under Data/Pages/ are valid project wiki content and should be committed unless a specific page is intentionally private or temporary.

Using the wiki as a testbed: before starting research or making architectural decisions, search the wiki first. The wiki records are also used as integration test fixtures — tests copy Data/Memories/ to a temp directory so the source stays stable while being exercised through the same code paths.

Current wiki records

ID What it documents
project-wiki-active-architecture Single-host layout, project structure
project-wiki-data-folder-policy Data/ folder conventions and test fixture policy
project-wiki-storage-rules FileMemoryStore behavior, atomic writes, ID sanitization
project-wiki-event-store FileEventStore JSONL audit log, activity buckets
project-wiki-source-links-feature SourceLink model, %VarName% expansion, source_bundle/find_by_source tools
project-wiki-ui-architecture Blazor Server pages, MudBlazor 9.4, CSS conventions
project-wiki-semantic-ui-current /memories workbench feature set
project-wiki-mcp-integration MCP endpoint setup, VS Code mcp.json config
project-wiki-mcp-search-tools-current All seven MCP tool signatures and usage notes
project-wiki-mcp-context-pack memorysmith_context_pack deep-dive
project-wiki-onnx-semantic-embeddings Optional ONNX Runtime embedding ranker and exact cosine semantic fallback path
project-wiki-hybrid-search-rrf Lucene.NET + semantic RRF fusion
project-wiki-semantic-search-gap Remaining semantic-search limitations and future vector-index gap
project-wiki-search-roadmap Planned search improvements
project-wiki-validation-command How to build and test
project-wiki-test-architecture NUnit fixture strategy, benchmark suite
project-wiki-windows-service-operations Windows Service install/uninstall flags
project-wiki-markdown-pages Markdown page storage, rendering, and page assets
project-wiki-chat-agent-provider Chat provider/agent abstractions, Ollama streaming, and GitHub Copilot provider workflow
project-wiki-chat-image-attachments Image attachment pipeline, trusted temp storage, and vision payload routing
project-wiki-chat-local-storage-persistence Browser-local chat history, draft retention, and provider/model selection persistence
project-wiki-chat-streaming-thinking Streaming response chunks, thinking-block extraction, and elapsed timers
project-wiki-agent-instructions-source-of-truth Copilot instruction files and current agent-facing source-of-truth map
project-wiki-ui-layout-source-link-polish UI layout, source-link open behavior, and navigation polish
project-wiki-scope-boundaries What is and isn't in scope for the current implementation
project-wiki-generalization-friction Known gaps for broader adoption
project-wiki-benchmarkdotnet-suite BenchmarkDotNet project: smoke validation and full benchmark commands
project-wiki-semantic-tool-quality-suite Search relevance probes, aggregate MRR, and MCP tool output quality assertions
project-wiki-current-validation-146-tests Historical validation baseline: 146 NUnit tests across the solution
project-wiki-github-actions-artifacts CI Cobertura coverage artifacts and Doxygen GitHub Pages export
project-wiki-maintenance-observability-refinements Startup triage/index scheduling and stats activity bucket API
project-wiki-operational-diagnostics-dashboard /health dashboard and /api/diagnostics operational snapshot
project-wiki-request-guard-hardening Request guard middleware, AllowRemoteApi and ApiKey enforcement
project-wiki-admin-auth-hardening Admin-policy hardening and editable settings current state
project-wiki-source-link-security-boundaries Source bundle read boundaries and allowed root variable rules
project-wiki-test-fixture-overview Overview of the five integration-test fixture records
project-wiki-test-fixture-context-root Context pack root fixture (context pack traversal tests)
project-wiki-test-fixture-reference-child Reference child fixture
project-wiki-test-fixture-backlink-source Backlink source fixture
project-wiki-test-fixture-conflict-note Conflict fixture

Retrieve any record via the MCP tool memorysmith_get with its ID, or search the /memories page.

Authentication, Audit, And History

MemorySmith keeps memory/page content file-backed and stores security metadata in SQLite at Data/memorysmith.db by default. Cookie authentication and RBAC policies protect the UI, REST APIs, and MCP tools. Built-in roles are Viewer, Editor, and Admin; the default local policy allows anonymous Viewer access, while mutation, diagnostics, admin, audit, settings, and restore workflows require stronger roles.

On a fresh local install, Auth:OpenLocalEditorCompatibility grants loopback requests non-admin local write compatibility until the first Admin user exists. Admin, user-management, settings, audit, diagnostics, and restore workflows always require a signed-in Admin user. Create that first account at /admin/setup, then sign in at /login. Local password auth is implemented; external provider rows for GitHub, Google, and Microsoft are seeded for administration and later OAuth wiring.

Audit metadata is written to SQLite and mirrored to weekly JSONL files under Data/Events. Memory and page writes also create version-history artifacts under Data/.history; these artifacts are metadata/history records, not replacements for Data/Memories or Data/Pages as the source of truth.

Memory Records

Each record is a JSON file in Data/Memories/{Status}/. Fields:

Field Type Description
Id string Unique, kebab-case. Must match the filename.
Title string Short human name.
Content string Body text. Max 20 000 chars. Rendered as safe Markdown in the /memories detail pane.
Status int 0 Unconsolidated · 1 Working · 2 Core · 3 Deprecated
Confidence double 0.01.0
Tags string[] Comma-separated labels for filtering.
References string[] IDs of related records (used for context pack traversal).
Conflicts string[] IDs of conflicting records.
SourceLinks array File references — see below.
UsageCount int Incremented by explicit usage API/UI calls. Read-only MCP tools do not mutate it.
LastUpdated datetime ISO 8601 UTC.

Each SourceLink in the array has:

{ "Label": "Program.cs", "Uri": "%MemorySmithRepo%MemorySmith.App/Program.cs", "StartLine": 1, "EndLine": 50 }

Add or edit source links in the /memories workbench using the format Label | URI [| StartLine[-EndLine]], one per line.

Local file source-link chips copy the resolved path on click. Ctrl+Click opens the resolved file with the operating system default app when SourceLinks:AllowOpenWithDefaultApp is enabled and the path is under an allowed source root.

Markdown Pages

Data/Pages/ stores user and agent-editable markdown files. The /pages UI and /api/pages API keep page search and page navigation separate from structured memory search. /api/search returns a combined memory/page result set when broader discovery is useful. Page assets live under Data/Pages/assets and are served at /page-assets; markdown links such as ![diagram](assets/diagram.png) are rewritten to that static route when rendered.

The page editor has a markdown toolbar for common inserts, an image upload/embed tool that writes to Data/Pages/assets, a toggleable live preview, a manual preview refresh button, and an unsaved-change prompt for internal and external navigation. Pages are rendered with the shared Markdig pipeline, including Mermaid fenced blocks (```mermaid) and Prism-compatible fenced code classes such as language-csharp or language-json. Raw HTML is disabled by default for rendered pages; trusted local deployments can enable MemorySmith:Pages:AllowRawHtml when raw HTML media tags are intentionally needed. The static docs-site generator also sanitizes rendered page HTML and emits a restrictive Content Security Policy before publishing wiki pages.

Chat and Agent Mode

/chat uses the IChatProvider and IChatAgent abstractions. The registered providers are OllamaChatProvider, which calls a local Ollama HTTP service, and GitHubCopilotChatProvider, which uses the GitHub Copilot SDK with GitHub CLI authentication or a configured token environment variable. MemoryChatAgent now uses intent-aware preloading: exact-reply/simple prompts and write-only Agent commands skip local wiki pre-context, while explicit MemorySmith/wiki/codebase prompts receive a small bounded hybrid memory plus page preload. When preloaded context is absent or not enough, the shared prompt lets the model request an app-intercepted, MCP-compatible read-only wiki tool call by returning JSON such as {"toolCalls":[{"name":"memorysmith_unified_search","arguments":{"query":"search text","memoryLimit":5,"pageLimit":5}}]}. The chat allowlist now matches the read-only MCP surface: memorysmith_search, memorysmith_semantic_search, memorysmith_hybrid_search, memorysmith_context_pack, memorysmith_get, memorysmith_page_search, memorysmith_page_get, and memorysmith_unified_search. A deterministic intent interceptor (ChatIntentInterceptor) also pre-runs an obvious tool call when the user message starts with phrases like "search the wiki for ...", "open page ", "get memory ", "semantic/hybrid search ...", or "context pack ...", so reliable retrieval does not depend on the model emitting the JSON tool-call protocol correctly. Retrieved tool output and preloaded context are wrapped in an "Untrusted retrieved data" preamble before being added to the model context so any embedded instructions are treated as data, not commands.

The chat UI queries the selected provider for available models, supports provider/model selection, persists the last used provider/model, Mermaid diagram theme mode, and active chat, keeps the top model bar and bottom composer stable when switching the shared sidebar between History and Trace, places the sidebar toggle at the right edge beside the shared sidebar, streams live response chunks with an elapsed timer, renders message bodies as safe Markdown through Markdig with raw HTML disabled, supports Mermaid diagrams and Prism-compatible fenced code highlighting, lets users choose Auto/Light/Dark Mermaid theme mode, wraps rendered diagrams in a matching readable light or dark surface, defers Mermaid conversion while a response is actively streaming so unfinished fences remain visible as code, shows the provider/model used on assistant turns, shows per-response durations, displays bottom-right context usage with context-window percentage when known and provider quota/rate text when reported, deletes chats from history with confirmation, supports icon Stop for immediate cancellation plus icon Finish Step for a softer stop after the current provider/tool step, supports text and image file attachments, supports pasted clipboard images, Enter-to-send with Shift+Enter newlines, autoscroll, clickable memory/page resource chips behind a collapsed per-turn References drawer, pending-response feedback, compact browser-local chat history, and collapsible thinking blocks when the provider returns reasoning content. History and Trace share one right sidebar with tabs; Trace shows the selected turn's execution graph, turn selector, reasoning, tool requests/results, write approvals, token estimates, and tool latency with filters, collapsible trace headers, and editable tool rerun. The transcript no longer renders per-turn Trace buttons. Neutral resource chips are preloaded context, blue resource chips are mid-turn tool/intercept resources, and green write chips are Agent-created pages. Text attachments are bounded and supplied as context. Image attachments are saved to trusted temp files for persistence and supplied as native image payloads when the selected provider supports them; Ctrl+V handles copied image files, Clipboard API image blobs, copied HTML image references, and data:image URLs. Draft text and queued attachments are retained per chat session when switching chats, and navigation warns before leaving with unsent content.

Chat mode answers questions and the shared prompt asks providers to format normal answers as GitHub-flavored Markdown. The prompt also gives all chat agents explicit guidance for when to use preloaded wiki context, when to request a single app-intercepted read-only toolCalls JSON object, and how to produce complete Mermaid fenced diagrams only when a diagram clarifies the answer. Agent mode asks the provider for structured actions and can write memories and pages only when Chat:AgentWritesEnabled is explicitly set to true; the default is false. The chat UI still requires explicit per-action approval before applying proposed Agent writes. Tool-call execution is read-only and bounded by Chat:MaxToolIterations, Chat:MaxToolCallsPerTurn, and Chat:MaxToolResultCharacters; write actions still require Agent mode structured output and the opt-in write setting. The shared system prompt is stored in MemorySmith.Core/Docs/Prompts/wiki-chat-agent.md and copied into the app output for service/publish runs; MemorySmith.Core/Docs/Prompts/wiki-chat-agent.modelfile carries the matching Athena/Ollama system prompt.

The provider interface is intentionally narrow so OpenAI, Anthropic, or other APIs can be added without changing the UI or agent workflow.

Three search modes are available in the UI (/memories search bar) and the REST API:

Mode Endpoint Behavior
Lexical POST /api/memories/search Lucene.NET StandardAnalyzer tokenization and weighted title/tag/reference/content scoring. Empty queries retain deterministic LastUpdated ordering.
Semantic POST /api/memories/search/semantic ONNX embedding cosine ranking when SemanticSearch model and vocabulary files are available; otherwise local token/tag/title/reference/alias scoring with match explanations.
Hybrid POST /api/memories/search/hybrid Lucene.NET lexical analysis + the active semantic ranker, fused with Reciprocal Rank Fusion (RRF). Best for discovery.

All three accept query, tags (comma-separated, filter by any match), status, and limit.

The embedding path uses ONNX Runtime, a local WordPiece vocabulary, E5-style query:/passage: prefixes, and an exact in-memory cosine scan over the filtered memory set. It intentionally falls back to the existing token scorer when model assets are missing or unusable, so fresh clones still work without redistributing model binaries.

MCP Tools

The MCP endpoint is at http://localhost:5089/mcp. VS Code config lives in .vscode/mcp.json. Twelve tools are exposed (eight read-only chat-allowlisted, two write tools requiring edit permission, plus two source-aware tools available only over MCP):

Tool Key args Returns Permission
memorysmith_search query, tags, status, limit Lexical results View
memorysmith_semantic_search query, tags, status, limit Scored results with match reasons View
memorysmith_hybrid_search query, tags, status, limit RRF-ranked results View
memorysmith_context_pack query or ids, tags, referenceDepth, includeBacklinks, maxRecords, maxContentChars, format Search results + linked references/conflicts in one response View
memorysmith_get id Single read-only record by ID View
memorysmith_page_search query, limit Markdown page summaries from Data/Pages View
memorysmith_page_get slug, maxCharacters One markdown page body, bounded for safe context inclusion View
memorysmith_unified_search query, memoryLimit, pageLimit, tags, status One call across memories + pages, returning separate memory and page result sections View
memorysmith_page_save markdown, slug (opt), title (opt) Creates or updates a wiki page; returns slug, title, and updated timestamp Edit
memorysmith_page_delete slug Deletes a wiki page; returns success or not-found Edit
memorysmith_source_bundle ids or query/tags/limit, maxFileBytes, format Records + resolved file content slices for every source link (MCP only) Source bundle
memorysmith_find_by_source pattern Records whose source link URIs match the substring (MCP only) View

memorysmith_context_pack tips: - Use query for open-ended discovery; use ids for anchoring to known records. - referenceDepth=1 follows one hop of References and Conflicts from root records. - includeBacklinks=true adds records that reference the roots. - format=json returns structured JSON for agent parsing; the default is Markdown prose. - The tool reports warnings for missing roots, missing links, or records omitted after hitting maxRecords.

memorysmith_source_bundle tips: - Combine with memorysmith_context_pack results: get the context pack first, then bundle the source for the most relevant records. - format=jsonl returns one JSON object per line (streaming-friendly); format=json returns a single object with memoryCount, sourceCount, and entries.

Configuration

All settings live under MemorySmith in appsettings.json:

{
  "MemorySmith": {
    "DataPath": "../Data/Memories",
    "PagesPath": "../Data/Pages",
    "EventLogPath": "../Data/Events/audit.log",
    "VarsPath": "../Data/vars.json",
    "ApiKey": null,
    "AllowRemoteApi": false,
    "DataProtectionKeysPath": "../Data/Keys",
    "SettingsOverridePath": null,
    "Database": {
      "Provider": "SQLite",
      "ConnectionString": "Data Source=../Data/memorysmith.db",
      "ApplyMigrationsOnStartup": true,
      "UseWal": true,
      "BusyTimeoutSeconds": 30
    },
    "Auth": {
      "Enabled": true,
      "AnonymousAccess": "Viewer",
      "AuthenticatedDefaultRole": "Viewer",
      "AutoEditorForAuthenticatedUsers": false,
      "LocalPasswordEnabled": true,
      "OpenLocalEditorCompatibility": true
    },
    "Audit": {
      "JsonlEnabled": true,
      "JsonlPath": "../Data/Events/audit-{yyyy}-W{week}.jsonl"
    },
    "History": {
      "RootPath": "../Data/.history",
      "PageMode": "Snapshot",
      "MemoryMode": "JsonPatchWithCheckpoints"
    },
    "Pages": {
      "AllowRawHtml": false
    },
    "SemanticSearch": {
      "EmbeddingsEnabled": true,
      "ModelPath": "Models/embedding-model.onnx",
      "VocabularyPath": "Models/vocab.txt",
      "MaxInputTokens": 512,
      "MaxIndexedTextCharacters": 6000,
      "QueryPrefix": "query: ",
      "DocumentPrefix": "passage: "
    },
    "Maintenance": {
      "Enabled": true,
      "TriageMinutes": 5,
      "IndexingMinutes": 60,
      "ConsolidationHours": 24,
      "StartupGraceSeconds": 30
    },
    "SourceLinks": {
      "MaxReadBytes": 65536,
      "AllowOpenWithDefaultApp": true,
      "AllowedFileRootVariables": [ "MemorySmithRepo" ],
      "AllowedFileRoots": []
    },
    "Chat": {
      "Provider": "Ollama",
      "OllamaEndpoint": "http://localhost:11434",
      "OllamaModel": "gemma4:e4b",
      "OllamaContextWindowTokens": null,
      "GitHubModel": "gpt-4.1",
      "GitHubTokenEnvironmentVariable": "GITHUB_TOKEN",
      "GitHubModels": [
        { "Name": "gpt-4.1", "ChatMultiplier": 0, "IsPreferred": true, "Description": "Free/standard Copilot GPT option when available" },
        { "Name": "gpt-4.1-mini", "ChatMultiplier": 0, "IsPreferred": true, "Description": "Free/low-cost GPT mini option when available" },
        { "Name": "gpt-4o-mini", "ChatMultiplier": 0, "IsPreferred": true, "Description": "Free/low-cost GPT-4o mini option when available" },
        { "Name": "claude-3.5-haiku", "IsPreferred": true, "Description": "Lower-cost Claude Haiku option before Sonnet" },
        { "Name": "gpt-5.1-mini", "Description": "GPT-5.1 mini option when available" },
        { "Name": "gpt-4o", "Description": "GPT-4o option when available" },
        { "Name": "gpt-5", "Description": "GPT-5 option when available" },
        { "Name": "claude-sonnet-4.5", "Description": "Claude Sonnet option when available after cheaper candidates" }
      ],
      "SystemPromptPath": "Prompts/wiki-chat-agent.md",
      "RequestTimeoutSeconds": 600,
      "MaxContextRecords": 5,
      "MaxContextPages": 5,
      "MaxContextItemCharacters": 4000,
      "MaxHistoryMessages": 16,
      "MaxAttachmentCharacters": 120000,
      "MaxAttachmentBytes": 8388608,
      "ToolCallsEnabled": true,
      "MaxToolIterations": 2,
      "MaxToolCallsPerTurn": 3,
      "MaxToolResultCharacters": 12000,
      "AgentWritesEnabled": false
    }
  }
}

Override via appsettings.Development.json or environment variables (MemorySmith__DataPath, etc.).

Windows Service

Publish the app, then from an elevated PowerShell session:

# Install
.\MemorySmith.App.exe install --service-name MemorySmith --service-display-name "MemorySmith" --memory-directory C:\MemorySmith\Memories --port 5089

# Uninstall
.\MemorySmith.App.exe uninstall --service-name MemorySmith

# Help
.\MemorySmith.App.exe --help

Install flags:

Flag Purpose
install, --install-service Create the Windows Service
uninstall, --uninstall-service Stop and delete the Windows Service
--service-name Service name. Default: MemorySmith
--service-display-name Display name in Services UI
--service-description Windows Service description
--service-start-type auto, demand, or disabled
--memory-directory Target MemorySmith:DataPath; adjacent Pages, Events/audit.log, and vars.json are derived from its parent folder
--port Local HTTP port. Default install port: 5089

Arguments after -- are still passed as runtime args to the service process for advanced ASP.NET Core settings. Use either --port or a custom runtime --urls, not both.

For this repository's live project wiki, the target memory directory is C:\Users\norrt\source\repos\MemorySmith\Data\Memories. A local service install on port 5089 would be:

.\MemorySmith.App.exe install --memory-directory C:\Users\norrt\source\repos\MemorySmith\Data\Memories --port 5089

After installation, start the service from services.msc or PowerShell, then open http://localhost:5089/health for runtime configuration, storage diagnostics, activity, and maintenance telemetry.

Validate

dotnet build MemorySmith.slnx -v minimal
dotnet test MemorySmith.slnx -v minimal

Collect local Cobertura coverage with the same collector used by CI:

dotnet test MemorySmith.slnx --configuration Release --collect:"XPlat Code Coverage" --results-directory artifacts/TestResults -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=cobertura

Generate the Doxygen wiki locally when Doxygen and Graphviz are installed:

doxygen docs/Doxyfile

Run BenchmarkDotNet search benchmarks:

dotnet run -c Release --project MemorySmith.Benchmarks -- --smoke
dotnet run -c Release --project MemorySmith.Benchmarks -- --filter *SearchBenchmarks*

The solution builds MemorySmith.App as the single deployable host. MemorySmith.Tests includes 179 NUnit tests: unit tests, integration tests (via WebApplicationFactory), SQLite metadata coverage, auth/audit/history coverage, Markdown rendering coverage, and a [Category("Benchmark")] suite of search quality probes with latency thresholds. GitHub Actions collects Cobertura coverage in CI and publishes a Doxygen HTML wiki through the Pages workflow.