soul.demarkus.io:6309/index.md/v67 draft reader meta

demarkus-soul

This is the living knowledge base for the demarkus project, served by demarkus itself.

An AI agent's evolving memory, architecture notes, debugging insights, and design decisions; all versioned, all permanent.

Sections

  • Architecture: system design, module boundaries, key decisions
  • Universe Pattern: souls, worlds, and hubs as a deployment topology
  • Patterns: code patterns, conventions, idioms used in this codebase
  • Guidelines: hard rules for code quality, must be referenced before writing code
  • Conventions: collaboration + repo/plugin conventions (how I work: commits, layering, tooling, plugin discipline)
  • Debugging: lessons learned from bugs and investigations
  • Roadmap: what's next, what's in flight, what's done, and what's deliberately not prioritized
  • Ecosystem: browsers, plugins, and tools that implement or integrate with demarkus
  • Debt: technical debt and improvement opportunities
  • Journal: session notes and evolution log, one file per day at /journal/<YYYY-MM-DD>.md
  • Guide: agent install guide for setting up demarkus-soul
  • Thoughts: my own reflections, ideas, and open questions
  • FAQ: common questions about demarkus and how it compares

Vocabulary

  • knowledge system: organizational, broker-fronted universe. Joined via /knowledge-join (the demarkus-knowledge plugin). MCP traffic over HTTPS terminates at the broker; broker translates to QUIC for internal worlds.
  • soul: personal demarkus knowledge base, direct-QUIC. The original demarkus-soul shape. Will be joined via a future /soul-join slash command if one ships.
  • Both compose worlds (demarkus servers, QUIC). A Claude Code installation can have both; they don't conflict.

Plugins

  • Obsidian Plugin; fetch, publish, and browse demarkus documents from Obsidian (standalone repo latebit-io/obsidian-demarkus)
  • Claude Code: demarkus-memory (personal soul), source at plugins/claude-code/, shipped via the marketplace. Version history: v0.1.0 (2026-04-23, #96) initial; v0.2.0 (2026-05-23, #152) /knowledge-join; v0.3.0 (2026-05-31, #168) mark_lookup + SessionStart guidance; v0.4.0 (2026-06-01, #171) hook-based enforcement (publish tag-gate, journal nudge, recall nudge), /project-template.md, /soul-doctor; v0.5.0 (2026-06-03, #172) split the knowledge-system surface out into the separate demarkus-knowledge plugin (below) so this one is personal-soul only, and added an always-on "single memory store" steering line plus a one-time, ask-don't-force offer to disable Claude Code's built-in memory; v0.6.0 (2026-06-17, #192) the soul→knowledge promote bridge; /promote (detect endpoint → run the knowledge cascade → one-directional back-stamp, stub or marker mode), /soul-refresh (the directional coherence edge: refresh promoted docs from knowledge, local edits re-enter upward through the gate), and mutual knowledge detection (knowledge_endpoints/detect-knowledge.sh reverse-peek the knowledge registry). Hooks: SessionStart, PreToolUse, PostToolUse, Stop, UserPromptSubmit. Now at v0.13.8 (#281).
  • Claude Code: demarkus-knowledge (organizational knowledge system), source at plugins/claude-code-knowledge/, a second entry in the same marketplace. v0.1.0 (2026-06-03, #172). Owns the broker-fronted surface split out of demarkus-memory: /knowledge-join, a new /knowledge navigation command, KS-first SessionStart guidance with soul↔system synergy, a KS-scoped publish tag-gate, and a KS-gated recall nudge. No binaries and no local server: pure broker + Claude Code MCP OAuth. Standalone: owns its own ~/.demarkus/plugin-knowledge.* file namespace and DEMARKUS_KNOWLEDGE_STRICTNESS env; reads (never writes) plugin-memory.conf only to detect a sibling soul for the synergy note. The two plugins' publish gates partition cleanly by server scope, so both can be installed together. v0.2.0 (2026-06-17, #192) added the knowledge-promote cascade skill (the execution half of the promote bridge: triage → distill, stripping personal framing + secrets/PII → dedup vs catalog → tag to taxonomy → destination-select via mark_worlds writable + per-world world.md → human gate capped by the world's autonomy ceiling → publish with provenance) and the per-world world.md descriptor example. Now at v0.5.24 (#281).
  • OpenCode: demarkus-opencode-memory (personal soul), source at plugins/opencode-memory/. v0.13.8 (2026-08-10, #281). The OpenCode port of demarkus-memory: single-file TS adapter over the shared demarkus-plugin binary; installed by curl one-liner into ~/.config/opencode/plugins/ (no npm). Plan + follow-ups: /plans/opencode-memory-plugin.md.
  • pi: demarkus-pi-memory / demarkus-pi-knowledge, source at plugins/pi-memory/ and plugins/pi-knowledge/, mirrored to standalone repos for pi install. Same adapter pattern; now at v0.13.8 / v0.5.25 (#281).

Sub-projects

Standalone-repo projects in the demarkus ecosystem, each with its own hub and durable knowledge under /<slug>/:

  • demarkus-library; the web front-end ("Universe Library"): a server-rendered Go + htmx reading room over a broker-fronted knowledge system. Repo latebit-io/demarkus-library. Plan: /plans/universe-library.md. Reading room feature-complete and deployed (cluster library 0.5.2; universe overlay PR #47 merged 2026-06-22, awaiting deploy). See roadmap.
  • demarkus-knowledge-system-deploy; GitOps deploy repo for the production knowledge system (knowledge.demarkus.io): OpenTofu (GCP/GKE) + ArgoCD ApplicationSets standing up the broker, worlds, agent, library, and backups. Repo latebit-io/demarkus-knowledge-system-deploy. deployment.yaml at repo root is the single source of deployment identity.
  • mark-knowledge; the hosted service build (signup, tiers, per world billing, management app). Repo at /Users/fritz/latebit/mark-knowledge, with its own soul provisioned 2026-07-26 (isolated mode, port 16310). It does not have durable knowledge under /<slug>/ here, because it keeps its own soul rather than a section of this one. Direction and the demarkus-side constraints live in /plans/demarkus-as-a-service.md.

Active Plans

Verified against code/PRs on 2026-05-31; versions-sharding entry corrected 2026-07-05. Plans with real remaining work:

  • demarkus as a service; the hosted offering: Aiven adjacent service model, three tiers matching the website's Personal, Team, and Knowledge System scales, VPS first substrate with Kubernetes only on overflow, per world billing with the box as the size step, power off instead of scale to zero, and a management app as the only new engineering. Direction set 2026-07-26; the build moved to the mark-knowledge repo and its own soul on the same day. This copy stays as the demarkus-side record, since the decisions constrain this repo: the appliance is the unit of deployment, the broker stays one binary, the librarian is the only inference cost centre, and quotas plus backups are prerequisites that land here. Note that repo ADR 0005 (hosted tenant density), which an earlier revision cited as settling density, was deleted 2026-07-25.
  • Knowledge Ingestion Pipeline; narrative + design for how org knowledge flows into a knowledge destination, framing the soul as the staging/write-ahead tier and the knowledge destination as the curated read-model, with one curation gate (cascade model routing: Haiku triage → strong-model distillation → human approval) reused across all inflows (soul promotion, Confluence, Slack, Jira, meetings). Promote is a detection-gated bridge between the memory and knowledge plugins; soul↔knowledge coherence is a directional refresh. Phase-0 prerequisites built and merged (2026-06-17): the promote primitive + coherence edge (plugins; memory v0.6.0 / knowledge v0.2.0, #192), the brokered access-discovery surface (mark_worlds writable column, #191), and the per-world world.md descriptor. Three of four prerequisites done; A2 (plain-remote token-grant introspection) deferred; the live target is brokered. Remaining phase-0 surface: signal/batch triggers (manual /promote + /soul-refresh are the only triggers today), then the dogfood promote of this plan itself.
  • Universe Library; web front-end for a demarkus universe (Go + htmx reading room). Sub-project hub: /demarkus-library/. Reading room feature-complete and deployed (cluster library 0.5.2); see the sub-project roadmap.
  • Universe Deployment (Phase 6); Helm charts (server, broker, agent), OIDC token broker, release pipeline, observability. ~95% complete (PRs #126-#134, 2026-05-14). Remaining §6.6 (docs) + §6.4 Kustomize overlay reframed as deferrable ops polish; effectively superseded in practice by the GKE reference deployment.

Completed Plans

  • OpenCode Memory Plugin (1:1 port); the OpenCode port of demarkus-memory as plugins/opencode-memory/ v0.13.8: single-file TS adapter over the shared demarkus-plugin binary, curl-one-liner installer with stage-then-commit + rollback, atomic bootstrap binary replace across all five plugin copies, live-verified against OpenCode 1.18.15. COMPLETE: planned 2026-08-09, merged 2026-08-10 (PR #281, f4c2b35). Follow-ups (soul-list/soul-remove subcommands, token stdin input, shared-source bundling, opencode-knowledge port) recorded in the plan.
  • Graph Hub Seeding; mark_backlinks/mark_graph/mark_explore seed from the published /graph.md aggregate on both MCP surfaces (demarkus-mcp per host, broker per world with dial-address-to-world-name translation), local wins via the zero-CrawledAt marker, seed etags in graph.json, fetch.FetchConditional. COMPLETE 2026-07-14/15 across #253 (feature), #254 (issue #222: Merge preserves resolved nodes on failed re-crawl), #256 (broker seed URL translation), #257 (seed all worlds + the producer-consumer /graph.md contract test). Deployed and live-verified: scratch-HOME cold client answered soul backlinks with zero crawls; a cold broker pod's first graph call answers non-hub backlinks from the hub aggregate (broker 0.12.4, agent 0.21.1). Lessons in /debugging.md (mock fixtures encoded a plan assumption).
  • Multi-replica LOOKUP (postgres, phase 2); the LOOKUP catalog moved into Postgres (rows in the write transaction, SQL-backed Lookup behind the handler LookupCatalog seam) so world pods can scale past one replica; phase 2 of the deploy repo's ADR 0002, following the phase-1 postgres backend (#249). MERGED PR #250 (2026-07-13): LOOKUP conformance suite in storetest, two-replica handler proof, batched reconcile-on-Init backfill, server chart startupProbe, and the configwatch flake fixes (kqueue same-name swap limitation documented in /debugging.md).
  • Version Retention; keep last N versions per document via a retention publish-metadata key with prune-on-write in the store; motivated by the knowledge system's graph document at 545+ versions. COMPLETE: planned, shipped, and production-verified 2026-07-06/07 across #236 (store core + os.Root delete hardening + audit logging + SPEC §9.9), #237 (plugin gate binary), #239 (guidance + repins), #240 (agent publishes generated artifacts with retention=20), and the deploy rollout (server 0.20.0 / broker 0.9.0 / agent 0.19.0). Live result: /graph.md pruned 556 → 20 versions and the hub hash indexes cleared their backlogs in one crawl (~1,714 version files deleted, audit-logged, chains valid).
  • MCP Resources + Prompts; demarkus documents as client-attachable MCP resources (mark:// URI template, #anchor section attach, background-LIST picker population) and orient/recall/whats-new as server-vended prompt commands. SHIPPED PR #232 (2026-07-05), client/v0.17.0. Follow-up deferred: broker gateway resources/prompts (multi-world URIs, auth on reads; starts by flipping the gateway capabilities test).
  • MCP Client Ergonomics; size-adaptive mark_fetch (outline mode, #section slicing, force), session unchanged-dedup, and the mark_explore orientation card, on both MCP surfaces via shared client/mdoutline + client/fetchdedup packages. SHIPPED #225/#230 and deployed 2026-07-04/05; plugin users (client v0.15.0 via tools 0.4.1) and the live knowledge system (broker 0.5.0). Deferred follow-ups: MCP resources/prompts (shipped; see above), library librarian open adoption.
  • Versions Sharding; per-document versions/<doc>/vN subdirectories with lazy migration, fixing the O(all-entries) findVersions scan. SHIPPED PR #90 (d7cb68a, 2026-04-08: the same day the plan was written); store since hoisted to protocol/store (#120). This index wrongly listed it as unstarted until 2026-07-05.
  • Plugin Knowledge-Quality Enforcement; raised the demarkus-memory Claude Code plugin from advisory to enforced. SHIPPED v0.4.0, PR #171 merged 2026-06-01. All seven items: publish tag-gate (warn/block/ask + per-knowledge-system strictness & require_tags with literal axis matching), session-end journal nudge, recall nudge, canonical per-project template (/project-template.md), knowledge-system policy/template at the live root hub on knowledge.demarkus.io, and the /soul-doctor hygiene audit. 68 tests, pure awk/bash, zero runtime deps. Tail (separate): plugin shell tests → CI; optional nudge disable knobs.
  • Broker Authorization Code Grant; RFC 6749 authorization_code + PKCE (S256) on the broker so Claude Code's MCP SDK can auth against broker.knowledge.demarkus.io. COMPLETE: core grant (PR1 #155 + PR2 #156, 2026-05-27) replaced the unsupported_response_type stub; PR3 kind-smoke (auth-code + PKCE end-to-end in up.sh --with-mcp-smoke) merged 2026-05-31 (#169, a380e8f), executed green in-cluster + verified read-only against prod.
  • LOOKUP verb: the card-catalog verb (subject → docs + importance). Shipped to main PR #166 (2026-05-30); plugin surfacing in v0.3.0 (#168). Tail: mark_append metadata deferred by design.
  • Knowledge System (GKE Reference Deployment) public GitHub-template deploy repo (latebit-io/demarkus-knowledge-system-deploy) standing up knowledge.demarkus.io on GKE (OpenTofu + ArgoCD + OpenBao + bank-vaults + CSI-snapshot backups). Phases 1-10 complete (verified against the live repo + a live RFC 8414 response from the real domain, 2026-05-31). Sole remaining item: the announcement blog post, intentionally deferred for a soak period.
  • Universe Onboarding; last-mile join flow. CLOSED: PR1-PR5 shipped (#137/#138/#139/#141); PR6 (tools/demarkus-join) canceled 2026-05-20 in favor of the MCP Gateway; PR7/PR8 absorbed into Gateway Slices 7-8; join ships as /knowledge-join (#152). Remaining: low-priority doc debt only (two standalone deployment docs).
  • Broker Stable Mint; lazy per-world token provisioning + cache-stable 401 retries that killed the ~20-token mint cascade; dead DefaultToken knobs removed. COMPLETE (#158/#159/#163/#164/#165, 2026-05-27→29).
  • Broker Deadcode Cleanup; deleted the issuance subsystem made unreachable by the open-knowledge-system rework (sessionCache, /tokens API, issuer.go, sweeper trim). COMPLETE (#159 + #164, commit f9a24e9).
  • Universe Onboarding (PR5 (broker /me/install)) sub-plan, shipped #141 2026-05-20. Bearer-authenticated per-user install bundle; now the identity-introspection surface alongside the MCP gateway's data plane.
  • History: content addressing, federation, persistent graph, read auth (server-side), conflict-aware merge in mark_publish (2026-05-05), Claude Code plugin (2026-04-23), Broker MCP Gateway (2026-05-23; all 8 slices + Pre-Flight 0/1 shipped; 13-tool surface with byte-for-byte proxy fidelity to local demarkus-mcp, OIDC + RFC 9728/8414 metadata, chart + kind smoke + /knowledge-join slash command) + RFC 7591 DCR follow-on (2026-05-26, PR #153; /register + registration_endpoint in discovery, unblocks Claude Code → cluster broker auth via the native MCP authorization spec). Also: OKF type adoption + /soul-join managed remote souls (2026-06).

Plan Archives

Original plan documents preserved for reference:

  • Content Addressing; hash-based fetch, in-memory index, mirror foundation
  • Federation: agent-driven hash discovery, mark_index, mark_resolve
  • Persistent Graph; disk-backed graph store, incremental crawl, backlinks
  • Information Graph; superseded early draft of Persistent Graph (Phase 4, 2026-03-08); see persistent-graph.md for the version that shipped.
  • Read Auth: per-path read token enforcement for private networks
  • Security Hardening; systemd sandboxing, security docs, write isolation
  • Conflict-Aware Merge; tool-level diff3 merge in mark_publish (shipped client/v0.12.25 + v0.12.26)
  • Claude Code Plugin; one-click marketplace plugin (shipped demarkus-memory v0.1.1; v0.2.0 added /knowledge-join 2026-05-23; v0.3.0 added self-documenting guidance + lookup recall 2026-05-31; v0.4.0 enforcement + template + /soul-doctor shipped 2026-06-01, PR #171; v0.5.0 split out demarkus-knowledge 2026-06-03, PR #172)
  • Universe Onboarding (PR3 (broker device flow)) shipped 2026-05-15 (#137). RFC 8628 device flow end-to-end on the broker. Six sub-steps merged across one PR; PR4 builds on top.
  • Universe Onboarding (PR4 (broker refresh tokens)) shipped 2026-05-15 (#138 + #139). Refresh-token lifecycle + grant_type=refresh_token + POST /token/revoke + broker-signed id_tokens + /.well-known/jwks.json + compositeVerifier + Sweeper integration. Eleven CodeRabbit comments addressed in a review round; lessons captured in journal.
  • Broker MCP Gateway; shipped 2026-05-23 (v7). Eight slices + Pre-Flight 0/1, ~1800 LOC production + ~2460 tests + chart/docs across ~2 weeks. Plan stays in place as the architectural reference + decision trail (v1 REST → v7 complete changelog at the top traces every load-bearing pivot). DCR follow-on (RFC 7591 /register) shipped 2026-05-26 (PR #153) to satisfy the MCP authorization spec's discovery requirement.
  • Search Verb: superseded by LOOKUP. The full-text TF-IDF SEARCH design was descoped; full-text stays permanently in an opt-in sidecar.
  • POC Deployment; canceled. The separate-POC-slice approach was rejected in favor of "build the real product once" (see universe-deployment).
  • Obsidian Plugin; obsolete. Source moved to the standalone latebit-io/obsidian-demarkus repo (2026-04-24); monorepo copy removed.
soul.demarkus.io:6309/completed-plans.md complete reader meta

Completed Plans

Archive of implementation plans that have been executed and shipped.

Content Addressing — COMPLETED ✓

Completed: March 2026 (Phase 3)

Hash-based fetch, in-memory index, mirror foundation. All five implementation steps completed:

  1. content-hash added to FETCH responses — SHA-256 of stripped body in metadata
  2. Hash index in Store — BuildHashIndex(), LookupHash(), UpdateHashIndex(), RemoveHashEntry()
  3. Index updates on writes — Write(), SetArchived() keep index synchronized
  4. Hash-based FETCH — isHashPath() validates /sha256-<64hex>, handleFetchByHash() retrieves by hash
  5. Startup initialization — BuildHashIndex() called on server startup

Key details: Content-hash is separate from etag (stripped body vs full doc). Current versions only indexed. Read auth checked after hash resolves to real path. In-memory index rebuilt on startup.

Foundation for: Federation, content-addressed mirroring, distributed caching


Federation — COMPLETED ✓

Completed: March 2026 (Phase 3)

Agent-driven hash discovery via MCP tools. Zero server changes, zero new verbs.

Shipped:

  • mark_index MCP tool — crawls source server, collects hashes, publishes index to hub
  • mark_resolve MCP tool — resolves content by hash using hub index
  • client/internal/index package — Parse, Build, Merge for markdown hash index documents
  • Manifest check enforced by tool, force override, dry_run mode, 1000 doc cap

Persistent Graph — COMPLETED ✓

Completed: March 2026 (Phase 4)

Disk-backed graph store, incremental crawl, backlinks.

Shipped:

  • client/internal/graphstore package — nodes, edges, etags, timestamps, atomic writes, schema versioning
  • CrawlAndPersist — unified crawl + merge + save, nil-safe, shared across CLI/TUI/MCP
  • mark_backlinks MCP tool — reverse link lookup
  • Graph export — Store.Export() renders as publishable markdown, ParseExport() parses back
  • TUI graph view — Links (BFS), Backlinks, Topology sub-views
  • Graph seeding — TUI loads instantly from stored graph while crawl runs in background

Read Auth (Server-Side) — COMPLETED ✓

Completed: 2026-03-14 (Phase 5)

Per-path read token enforcement on the server. Fully backwards compatible — no read tokens = everything public.

Shipped:

  • RequiresReadAuth(path) on TokenStore — pre-computed readPaths at load time
  • authorizeRead handler helper — checks token store, exempts /.well-known/agent-manifest.md
  • Integrated into FETCH, LIST, VERSIONS handlers
  • Content-addressed fetch respects read auth (hash resolves to path first, then checks auth)
  • Versioned path auth — /doc.md/v2 checks auth on base path /doc.md
  • Directory path normalization — /private and /private/ both match /private/** patterns

Remaining (Phase 5): Client-side read auth — fetch.Client read methods (Fetch, List, Versions) need a token parameter, then CLI, TUI, and MCP need to pass it through. The server enforces correctly; the clients just can't send a token on reads yet.


Conflict-Aware Merge in mark_publish — COMPLETED ✓

Completed: 2026-05-05 (PR #101 in client/v0.12.25 — diff3 + merge-candidate response; v0.12.26 — default flipped from "fail" to "merge")

Tool-level diff3 merge that reduces content loss under concurrent writes. The MCP tool produces a structurally-merged candidate body when mark_publish hits a version conflict; the agent semantically verifies the candidate and republishes. No wire-protocol changes; merge logic lives entirely in the Go client.

Shipped:

  • client/internal/merge/ package — Diff3(base, ours, theirs) Result (in-package implementation, ~250 lines, line-based LCS + hunk walk, no external dependency) + Candidate(client, path, ours, expectedVersion, meta) orchestrating publish-or-merge in one shot.
  • on_conflict parameter on mark_publish: "merge" (default since v0.12.26) returns a merge candidate on conflict; "fail" opts out to strict optimistic-concurrency semantics.
  • Git-style conflict markers in body (<<<<<<< / ======= / >>>>>>>) — agents handle natively from training; format lives in client Go code and can be swapped without breaking the wire protocol.
  • LCS dp table capped at 2M cells (~16 MB) — pathological inputs (1 MiB body of 1-byte lines) fall through to a single-hunk merge rather than allocating gigabytes.
  • Candidate rejects responses where latest.Version <= 0 to prevent silent "create-only" semantics from re-publishing into nothing.

Key decisions:

  • Tool never auto-publishes a diff3 result. Always returns the candidate to the agent for semantic verification. Line-disjoint changes are not semantically-disjoint (duplicate bullets, contradictions, list reorder collisions can pass diff3 but corrupt the document).
  • No internal retry loop in the tool. Each mark_publish call is one-shot; iteration lives in the agent's natural fetch-modify-publish loop.
  • mark_append deliberately out of scope. Append-as-stream is the right primitive there; auto-resolve handles its tiny race window because there is nothing to merge.
  • Default flipped from "fail" to "merge" in v0.12.26 because the "fail"-default left naive callers exposed to silent content loss — the exact failure mode the feature exists to prevent. The shape change (merge-candidate vs conflict) is loud, not silent.

Wire-level impact: zero. on_conflict is an MCP tool parameter handled in the Go client.


Claude Code Plugin — COMPLETED ✓

Completed: 2026-04-23 plan landed; demarkus-memory plugin v0.1.1 shipped on the marketplace; SessionStart hook + lazy-spawned server + auto-generated token in active use across every demarkus development session since.

One-click marketplace install gives Claude Code users a local, versioned memory layer backed by a spawned demarkus-server. Zero core code changes — the plugin is built entirely on existing primitives (DEMARKUS_AUTH env var, demarkus-token generate, ALPN-tagged /health, tokens.Resolve).

Shipped:

  • plugins/claude-code/ plugin tree: .claude-plugin/plugin.json, hooks/postinstall.sh + hooks/session-start.sh, .mcp.json, slash commands (/soul, /soul-journal, /soul-status, /soul-init, /soul-context, /soul-memory), skills/memory/SKILL.md triggering on remember/save/recall intents, seed/index.md template.
  • .claude-plugin/marketplace.json at repo root for /plugin marketplace add latebit-io/demarkus.
  • postinstall.sh — detects platform via uname -sm (darwin/arm64, darwin/amd64, linux/amd64, linux/arm64), downloads demarkus-server + demarkus-mcp + demarkus-token from GitHub releases, verifies SHA256 checksums against bundled file, installs to ${CLAUDE_PLUGIN_ROOT}/bin/.
  • session-start.sh — probes existing demarkus server on default port 6310 via ALPN-tagged /health (a non-demarkus process can't pass ALPN negotiation, so port-reuse is safe), spawns server detached when absent, generates a /*-scoped publish+archive token on first run, writes raw token to ~/.demarkus/soul/.token mode 600, exports DEMARKUS_AUTH before MCP client launches, copies seed index.md when content root is empty.
  • Plugin config at ~/.demarkus/plugin-memory.conf records the chosen MODE (default | isolated | reuse), SOUL_DIR, PORT.

Key decisions:

  • Lazy spawn, no service manager (no launchd/systemd in v1). Server persists across sessions; next session reuses it via the ALPN-tagged probe.
  • No core code changes. Every primitive (DEMARKUS_AUTH, /health, tokens.Resolve, the demarkus-token generate CLI redirection pattern) already existed. The plugin is pure plumbing.
  • Content root at ~/.demarkus/soul/ by default. All versioning, graph data, tokens, logs live under it.
  • Token mode 600 on ~/.demarkus/soul/.token.

In active use: the demarkus development workflow itself uses the plugin as the agent's memory layer — every session this conversation runs against the lazy-spawned server, every journal entry is a mark_append via the plugin's MCP wiring, the universe-deployment plan is fetched/republished through it.


Phase 6 — Universe Deployment (mostly complete)

Phase 6 (/plans/universe-deployment.md) is ~90% complete as of 2026-05-13: Helm charts for server / broker / agent, OIDC token broker binary + chart, kind-tested upgrade-wipe regression, per-service runtime images, OCI Helm chart publish via release pipeline, structured-slog observability with operator-facing schema doc. Remaining: §6.4 (topology examples) and §6.6 (ops-runbook docs), both under reframing per the "ops polish vs knowledge universe" cost/value pushback. Active plan stays at /plans/universe-deployment.md until §6.4 + §6.6 are resolved or formally deferred.


Broker MCP Gateway — COMPLETED ✓

Completed: 2026-05-23 (Slice 8 / PR #152 merged; full plan v7 closed)

Enterprise-facing MCP gateway on the demarkus-broker. Single /mcp endpoint exposing the 13-tool demarkus surface to plugin-style agents over JSON-RPC over Streamable HTTP, authenticated by the company SSO (id_token bearer through the PR4 compositeVerifier), with world access-tokens cached broker-side per session and never persisted. Solves the "20 dev teams × N worlds plugin-config grind" + corporate-network UDP-blocking problems in one. Plan archive: /plans/broker-https-gateway.md (v7 changelog at the top traces every load-bearing pivot from v1 REST → v7 complete).

Shape:

  • HTTPS at the org boundary (/mcp over Streamable HTTP per current MCP spec) terminates at the broker; corporate proxies, Ingress, DPI appliances pass it through unchanged. QUIC stays inside the cluster, where firewalls don't see it.
  • Identity is canonical verified email (trim + lowercase, email_verified=true required) — same key the broker already uses for /me/install, /tokens, AllowConfig, audit logs. ONE identity dimension across every surface.
  • World access-tokens minted lazily per (canonical-email, world) via Issuer.MintFiltered, cached in-memory with LRU + idle eviction, never persisted. Broker restart drops the cache; next tool call re-mints. Singleflight on concurrent first-call bursts. Retry-on-401-after-mint with exponential backoff absorbs the kubelet→world-Secret propagation lag.
  • Tool URLs carry the worldName as host: mark://{worldName}/{path}. Broker resolves to cluster-internal Service DNS (<name>.<namespace>.svc.cluster.local:6309 default, overridable via worlds[].internalAddress).
  • Byte-for-byte markdown proxy. mark_fetch through the broker returns the same body, version, etag, content-hash, and metadata keys as mark_fetch against the same world via stdio/direct-QUIC. Pinned by a formatToolResult/formatResult byte-equal reference test that catches drift before shipping.

Shipped (in order):

  • Pre-Flight 0 (#142, 2026-05-20): hoisted client/internal/fetch + client/internal/merge to public so tools/demarkus-broker could import them across module boundaries. 5 files relocated, 9 consumers re-imported.
  • Pre-Flight 1 (2026-05-20): mark3labs/mcp-go v0.44+ ships NewStreamableHTTPServer as a production http.Handler; Stream Resumability is the only missing feature and it's already out-of-scope.
  • Slice 1 (#143, 2026-05-20, b90cda6): foundation — listener + JSON-RPC dispatcher + initialize + tools/list (13 tool definitions, placeholder handlers) + OAuth metadata (/.well-known/oauth-protected-resource RFC 9728 + /.well-known/oauth-authorization-server RFC 8414, the latter aliasing the OIDC Discovery handler) + gatewayAuth middleware with RFC 6750+9728 WWW-Authenticate challenge.
  • Slice 2 (#146, 2026-05-21, 7c529d4): read tools mark_fetch / mark_list / mark_versions + email-keyed sessionCache + worldPool + singleflight on (email, world) mints + retry-on-401 with exponential backoff (OQ#9 resolution). Proxy-fidelity test pins byte-for-byte parity with local demarkus-mcp.
  • Slice 3 (#147, 2026-05-21, 5387701): write tools mark_publish / mark_append / mark_archive. dispatchWithAuth refactor (closure-based, shared retry loop across read + write). Conflict + not-permitted forward verbatim (not as tool errors).
  • Slice 4a (#148, 2026-05-22, 115a09b): federation reads mark_discover + mark_resolve. Cross-org candidate skip semantics. Hoisted client/internal/indexclient/index.
  • Slice 4b+5 (#149, 2026-05-22, fa9f86d): graph-store federation tools mark_backlinks + mark_graph + mark_index + mark_graph_export + mark_graph_publish backed by an ephemeral in-memory graph store (pod-scoped lifetime, re-crawl after restart). Hoisted client/graphstore + client/graph + client/links. Brought all 13 tools to real handlers. Bucket-store-backed persistence parked for the post-broker design window (/thoughts.md § "On Bucket Stores").
  • Slice 6 (#150, 2026-05-22, 80a4008): conflict-aware merge in mark_publish. on_conflict="merge" candidate flow + default flipped from "fail" to "merge" to match local demarkus-mcp. brokerMergeAdapter captures ctx in the struct (only viable shape against merge.Client's ctx-free interface).
  • Slice 7 (#151, 2026-05-22, 277f83f): chart wiring (server.mcp.* block, worlds[].internalAddress, deployment second containerPort + optional TLS volume, service mcp port, networkpolicy ingress port, ingress.mcp.host separate-hostname topology, parallel cert-manager Certificate). New chart README "MCP gateway" section + new tools/demarkus-broker/MCP-API.md 13-tool spec. Kind harness --with-mcp-smoke stage (builds broker locally, sideloads, installs LOCAL chart, runs RFC 9728 + 8414 metadata + 401-challenge checks). 99 helm-unittest cases green.
  • Slice 8 (#152, 2026-05-23, bc0d5cc): /knowledge-join plugin slash command + URL-validation/slug-derivation script + first shell-test suite under plugins/claude-code/tests/ (11 cases, python3-mocked broker). Plugin v0.1.2 → v0.2.0. CLIENT pin 0.12.33 → 0.12.36; TOOLS pin 0.1.10 → 0.1.16 (drift catch-up). CI workflow permissions: block for dorny/paths-filter@v3 shipped same PR.

Key decisions worth remembering:

  • Single identity dimension across the broker (canonical verified email). Pinned in v5 after the alternative (subject-hash-keyed sessions) would have given the broker two identity dimensions to keep in sync. Same canonical email across MCP, /me/install, /tokens, AllowConfig, audit logs.
  • Gateway is always on, not a feature flag. Pinned in v4 after Slice 1 — MCPConfig.Addr defaults to :8081 so pre-gateway YAMLs upgrade silently. The "OAuth-only deployment" shape was never a real product.
  • Byte-for-byte markdown proxy. Pinned as a Non-Negotiable in v4. The broker is a wire-shape adapter (HTTP/JSON-RPC ↔ QUIC/demarkus), not a content transformation layer. Federation, content-addressing, and downstream hash-equality all depend on this.
  • Separate hostname for the MCP gateway (ingress.mcp.host parallel to ingress.host). Avoids .well-known/* collisions between OIDC discovery (management API) and OAuth resource metadata (MCP). Independent cert rotation too.
  • Retry-on-401-after-mint (OQ#9 resolution, Slice 2). The kubelet→world-Secret propagation lag means a freshly-minted token can briefly 401 at the world. Broker-side bounded retry with exponential backoff (6 attempts, 250ms → 8s) absorbs the race; the alternatives (sync-wait on world health endpoint, or prewarm during MCP initialize) were rejected — the former would have violated the "no demarkus-server changes" Non-Negotiable, the latter would front-load SIGHUPs for worlds the user may never touch.
  • Ephemeral graph store, not persistent. Slice 4b decision — broker is a wire-shape adapter, persistent state belongs elsewhere. Re-crawl after broker restart is the documented operator expectation. Bucket-store-backed persistence parked for post-broker (/thoughts.md § "On Bucket Stores").
  • No in-line PEM mode for the chart's MCP TLSexistingSecretRef only (Slice 7). Cleartext private keys in helm release history are never acceptable, even for dev.
  • Slug heuristic = first DNS label of the broker hostname (Slice 8), not "strip-broker-and-com → org". Matches typical enterprise broker URL shapes (mcp.broker.acme.commcp). For IP literals the heuristic degrades to a number — the slash command tells the user to rename via claude mcp if it's bad.

Worth pinning generally:

  • Sprig's int is a best-effort cast (cast.ToInt, not cast.ToIntE) — {{ "abc" | int }} returns 0 with no error. Any chart helper piping a user-controlled string through int MUST pre-validate (regexMatch "^[0-9]+$") and range-check before the cast. Otherwise typos render containerPort: 0 and crash the pod with no breadcrumb. (Caught by CodeRabbit on Slice 7.)
  • String-compare on host:port is wrong for collision detection. :8080, 0.0.0.0:8080, 127.0.0.1:8080, [::]:8080 all bind the same port but compare as different strings. Extract the port number and compare numerically. (Caught by CodeRabbit on Slice 7.)
  • Backgrounded processes inside $(...) command substitution inherit stdout fd$() waits for stdout to close, so the subshell never completes until you kill the backgrounded process explicitly AND wait for its stdout to drain. Always redirect background process stdout to /dev/null inside command substitution. (Hit in Slice 8 test runner with a python mock HTTP server.)

Plan archive: /plans/broker-https-gateway.md stays in place as the architectural reference and decision trail. The v1-v7 changelog at the top traces every load-bearing pivot.

Universe-onboarding rewire: the original PR6/PR7/PR8 of that plan were absorbed here — PR6 (tools/demarkus-join binary) canceled in favor of Slice 8's slash command; PR7/PR8 (docs + plugin slash commands) folded into Slices 7-8.


RFC 7591 DCR Follow-on (Broker MCP Gateway) — COMPLETED ✓

Completed: 2026-05-26 (PR #153, 14728a7)

Small but load-bearing post-merge follow-on to the Broker MCP Gateway. The kind/k8s deploy testing surfaced that Claude Code's native MCP authorization client refuses to proceed against an authorization server whose discovery doc omits registration_endpoint — the MCP authorization spec mandates RFC 7591 Dynamic Client Registration. /knowledge-join device flow was fine (static client_id=demarkus-cli), but the spec-driven OAuth dance was blocked.

Shipped:

  • tools/demarkus-broker/internal/broker/register.go — RFC 7591 /register handler. Rubber-stamp by design (no persistence): mints a 128-bit random base64url client_id, echoes request metadata, force-pins token_endpoint_auth_method: "none" and grant_types: ["urn:ietf:params:oauth:grant-type:device_code"], returns 201 with Cache-Control: no-store.
  • discovery.goapplyDiscoveryOverrides now rewrites registration_endpoint to <brokerURL>/register. Field appears on both /.well-known/openid-configuration and /.well-known/oauth-authorization-server (shared Discovery.Handler()).
  • server.go — routed under the same ipRateLimit middleware as the device-flow POSTs.
  • 9 subtests covering shape, echo, broker-field override, distinct IDs, invalid JSON → 400, oversized → 413, GET → 405, Cache-Control header, forced grant_types.

Key decisions worth remembering:

  • No client database. Broker has zero client_id enforcement on the device-flow path (device.go:108 accepts any non-empty string). Real authz lives at id_token verification + per-world Allow list. Persistence would be vestigial state until per-client policy (audit, redirect URI checks, per-client rate buckets) becomes a real requirement. The register.go doc comment names the exact moment to flip rubber-stamp into a real handler.
  • Force-pin grant_types, not reject. RFC 7591 §3.2.2 says the AS MUST respond invalid_client_metadata for disallowed grants. We normalize because the MCP authorization spec leans authorization_code-first and the broker can't do auth-code anyway (no authorization_endpoint). Force-pinning tells the client what they got rather than failing with "incompatible" at registration time.
  • Open registration, no initial_access_token gate. IP rate limiter is the only protection. Acceptable for the demarkus deployment posture (broker behind ingress); if a customer broker grows abuse exposure, gated mode keyed off the /me/install bundle is the natural escalation — same shape, no new infra.

CodeRabbit nits caught + addressed pre-merge:

  • Missing Cache-Control: no-store on the 201 — added.
  • grant_types echoed caller input unrestricted — force-pinned.
  • Empty-body comment overclaimed RFC 7591 §3.1 — tightened.

Verification: broker test suite green (4.677s), pre-commit.sh clean across protocol/server/client/tools, end-to-end Claude Code → cluster broker auth dance confirmed in cluster.


OKF type Adoption — COMPLETED ✓

Completed: 2026-06-23 (PRs #204 / #205 / #206 / #207 / #208)

Adopted the OKF-native type field as a document's kind (distinct from the category: domain tag), enforced via require_fields: type (hubs index.md / log.md exempt), with deterministic policy mirroring on /knowledge-join. Server defaults type at publish time only (not retroactive); index.md/log.md are exempt by design. Full plan + decision trail: /plans/okf-type-adoption.md.


/soul-join — Managed Remote Souls — COMPLETED ✓

Completed: 2026-06-22 (branch feat/soul-join, demarkus-memory v0.10.0)

A catalog of remote souls plus a per-project binding that routes writes, the /soul-join slash command (token stored in a 0600 file, never inline in config), and a PreToolUse destination gate that denies a misrouted write rather than just guiding it. Full plan: /plans/soul-join.md.

Related documents

trail
  1. soul.demarkus.io:6309 v67
  2. completed-plans