feat(v2): Obsidian memory vault — GUI service + file-based memory-vault MCP#76
Merged
Conversation
…lt MCP Add a shared markdown memory substrate: a host vault dir seeded with a README/CONVENTIONS schema (name/description/type frontmatter, wikilinks, one-fact-per-note) that both a browser Obsidian GUI and a file-based MCP read and write — plain .md files, no hidden store. Plugins: - obsidian (kind=service): linuxserver/obsidian DIGEST-pinned, SUBFOLDER=/obsidian/, no host port (SSO-only — the GUI embeds a passwordless-sudo terminal), /config app state + the shared vault bind-mounted at /config/vaults/memory. - memory-vault (kind=mcp): @bitbonsai/mcpvault version-pinned (docker/mcpvault-mcp, node base digest-pinned), CMD `mcpvault /vault`, 15 tools (read/write/patch/ search/frontmatter/tags/...). Verified stdio-clean offline before wiring. Root-cause render fix (not a per-artifact workaround): the v2 mcp render dropped volumes/command/longLived/disableNetwork, so a file-based MCP could not WRITE its data dir. render.py now passes those upstream gateway-catalog fields through when a manifest declares them (opt-in — existing image+env MCP entries render byte-identically). The vault volume is a READ-WRITE host bind (no :ro) whose source is substituted from MEMORY_VAULT_PATH by gateway-wrapper.sh (mirrors the CODE_ROOT placeholder pattern); compose.py adds MEMORY_VAULT_PATH to the gateway env. Edge: /obsidian/* -> obsidian:3000 in the SSO-protected :443 block (handle, no strip — SUBFOLDER makes the app subpath-aware). Also reconciles a pre-existing drift: the tracked auth/caddy/Caddyfile was missing the live /grafana/ route (PR #71 deployed on a branch, never merged to source) — added so source matches the running runtime copy. Tests: +test_obsidian_memory.py (service digest/SUBFOLDER/no-ports/profile + MCP RW vault volume/longLived/disableNetwork/passthrough-opt-in). Parity fixtures updated for the two net-new plugins (13 service / 3 mcp). Suite 172->181 passed, 2 skipped; ruff clean. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Design
A shared markdown memory substrate for the stack — plain
.mdfiles, no hidden store — usable by both a browser GUI and the agents:data/memory-vault(seeded):README.md,CONVENTIONS.md(frontmatter schemaname/description/type: user|project|feedback|reference, wikilinks, one-fact-per-note),memories/{user,project,feedback,reference}/,research/,inbox/, plus one worked example note.obsidian(kind=service):linuxserver/obsidiandigest-pinned (@sha256:b8a0b254…),SUBFOLDER=/obsidian/, no host port (the GUI embeds a passwordless-sudo terminal → SSO-edge only, never published),/configapp-state + the shared vault at/config/vaults/memory.memory-vault(kind=mcp):@bitbonsai/[email protected](node:22-bookworm-slim digest-pinned base,docker/mcpvault-mcp),CMD mcpvault /vault, 15 tools.longLived+disableNetwork(pure fs)./obsidian/*→obsidian:3000in the SSO-protected:443block (handle, no strip — the app is subfolder-aware).Root-cause render fix (not a per-artifact workaround)
The v2 MCP render emitted only
image+env, so a file-based MCP could not write its data dir.render.pynow passes the upstream gateway-catalog fields (volumes,command,longLived,disableNetwork) through when a manifest declares them — opt-in, so existing image+env MCP entries render byte-identically. The vault volume is a read-write host bind (no:ro) whose source is substituted fromMEMORY_VAULT_PATHbygateway-wrapper.sh(mirrors the existingCODE_ROOTplaceholder pattern);compose.pyaddsMEMORY_VAULT_PATHto the gateway env.Also reconciles a pre-existing drift surfaced during the work: the tracked
auth/caddy/Caddyfilewas missing the live/grafana/route (PR #71 deployed on a branch, never merged to source) — added so the source matches the running runtime copy.Validation evidence (live)
GET obsidian:3000/obsidian/→ HTTP 200 (SUBFOLDER honored); through the edgehttps://ultracam…/obsidian/→ 302 → /oauth2/start (Google SSO). Operator does the visual login check.Those servers are enabled: memory-vault, qdrant-rag, searxngandmemory-vault: (15 tools), spawned--network none -v C:/dev/ordo-ai-stack/data/memory-vault:/vault. Real round-trip through the gateway:write_note→Successfully wrote note: memories/reference/roundtrip-canary.md; the file exists on host disk (data/memory-vault/memories/reference/roundtrip-canary.md, frontmatter intact);read_notereturns it;search_notes "OBSIDIANMEMORYPROOF"finds it; seededCONVENTIONS.mdreadable viaread_note./config/vaults/memory/(README/CONVENTIONS/tree present).c8ba78363863) + agent (ac79a12620ef) + comfyui/voice container IDs unchanged; 24→25 services (only+obsidian);/dash/&/grafana/still 302 SSO;/healthz200.Render discipline: re-rendered in a
--gpus allCUDA container fromout/ordo.yaml; diff vs liveout/= ONLY the new obsidian service +MEMORY_VAULT_PATHgateway env + the memory-vault mcp registry entry; llamacpp block byte-identical (GPU-97fe65ee preserved).docker compose config(both env files, all profiles incl. obsidian) → exit 0. Live.envruntime-appended keys preserved.Pins: obsidian
linuxserver/obsidian@sha256:b8a0b2542f9c…2919024; mcpvault@bitbonsai/[email protected]; basenode:22-bookworm-slim@sha256:53ada149….Tests:
172 → 181 passed, 2 skipped; ruff clean (throwawaypython:3.11-slimcontainer).Deliberate follow-ups (not in this PR)
services_catalog.py+index.htmlSSO_ROUTES) — needs a rebuild of the sharedordo-v2/dashboard-v1image (changes its container ID); deferred to keep blast radius minimal. Routing/health/MCP all work without it.trashMode: localso deletes go to the vault's.trash/instead of permanent).🤖 Generated with Claude Code