Skip to content

feat(v2): Obsidian memory vault — GUI service + file-based memory-vault MCP#76

Merged
AlienWalker1995 merged 1 commit into
mainfrom
feat/obsidian-memory
Jul 9, 2026
Merged

feat(v2): Obsidian memory vault — GUI service + file-based memory-vault MCP#76
AlienWalker1995 merged 1 commit into
mainfrom
feat/obsidian-memory

Conversation

@AlienWalker1995

Copy link
Copy Markdown
Owner

Design

A shared markdown memory substrate for the stack — plain .md files, no hidden store — usable by both a browser GUI and the agents:

  • Vault data/memory-vault (seeded): README.md, CONVENTIONS.md (frontmatter schema name/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/obsidian digest-pinned (@sha256:b8a0b254…), SUBFOLDER=/obsidian/, no host port (the GUI embeds a passwordless-sudo terminal → SSO-edge only, never published), /config app-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).
  • Edge: /obsidian/*obsidian:3000 in the SSO-protected :443 block (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.py now 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 from MEMORY_VAULT_PATH by gateway-wrapper.sh (mirrors the existing CODE_ROOT placeholder pattern); compose.py adds MEMORY_VAULT_PATH to the gateway env.

Also reconciles a pre-existing drift surfaced during the work: the tracked auth/caddy/Caddyfile was 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)

  • (a) obsidian healthy; container-to-container GET obsidian:3000/obsidian/HTTP 200 (SUBFOLDER honored); through the edge https://ultracam…/obsidian/302 → /oauth2/start (Google SSO). Operator does the visual login check.
  • (b) Gateway log: Those servers are enabled: memory-vault, qdrant-rag, searxng and memory-vault: (15 tools), spawned --network none -v C:/dev/ordo-ai-stack/data/memory-vault:/vault. Real round-trip through the gateway: write_noteSuccessfully wrote note: memories/reference/roundtrip-canary.md; the file exists on host disk (data/memory-vault/memories/reference/roundtrip-canary.md, frontmatter intact); read_note returns it; search_notes "OBSIDIANMEMORYPROOF" finds it; seeded CONVENTIONS.md readable via read_note.
  • (c) Vault visible inside the obsidian container at /config/vaults/memory/ (README/CONVENTIONS/tree present).
  • (d) llamacpp (c8ba78363863) + agent (ac79a12620ef) + comfyui/voice container IDs unchanged; 24→25 services (only +obsidian); /dash/ & /grafana/ still 302 SSO; /healthz 200.

Render discipline: re-rendered in a --gpus all CUDA container from out/ordo.yaml; diff vs live out/ = ONLY the new obsidian service + MEMORY_VAULT_PATH gateway 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 .env runtime-appended keys preserved.

Pins: obsidian linuxserver/obsidian@sha256:b8a0b2542f9c…2919024; mcpvault @bitbonsai/[email protected]; base node:22-bookworm-slim@sha256:53ada149….

Tests: 172 → 181 passed, 2 skipped; ruff clean (throwaway python:3.11-slim container).

Deliberate follow-ups (not in this PR)

  • Dashboard services-catalog entry (services_catalog.py + index.html SSO_ROUTES) — needs a rebuild of the shared ordo-v2/dashboard-v1 image (changes its container ID); deferred to keep blast radius minimal. Routing/health/MCP all work without it.
  • Hermes SOUL/skill adoption of the memory-vault tools (agent-side guidance; also: pass trashMode: local so deletes go to the vault's .trash/ instead of permanent).
  • Qdrant semantic indexing of the vault (phase-2).

🤖 Generated with Claude Code

…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]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant