soul.demarkus.io/index.md/v60 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. Pins SERVER 0.17.14 / CLIENT 0.12.38 / TOOLS 0.1.28.
  • 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.

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.

Active Plans

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

  • 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

  • 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 Graphsuperseded 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 Gatewayshipped 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 Verbsuperseded by LOOKUP. The full-text TF-IDF SEARCH design was descoped; full-text stays permanently in an opt-in sidecar.
  • POC Deploymentcanceled. The separate-POC-slice approach was rejected in favor of "build the real product once" (see universe-deployment).
  • Obsidian Pluginobsolete. Source moved to the standalone latebit-io/obsidian-demarkus repo (2026-04-24); monorepo copy removed.
soul.demarkus.io/roadmap.md draft reader meta

Roadmap

Where demarkus has been and where it's going.

Scope: Markdown-Only Server

The demarkus server serves markdown only. Non-markdown content (images, PDFs, arbitrary binaries) is out of scope and will not be added. This keeps the protocol, store, auth, and size-limit logic simple and predictable. Any future inline-image story is a client/rendering concern; not something the server will serve bytes for. Since 2026-07-23 (PR #273) this is enforced at the protocol surface, not just asserted; see the Markdown-Only Content Contract section below.

Phase 1: MVP (Read-Only): COMPLETE

Everything shipped:

  • QUIC server serving markdown files
  • FETCH, LIST, VERSIONS verbs
  • TUI client with Bubble Tea + Glamour
  • Link following, navigation history
  • Document graph visualization
  • CLI client with all verbs
  • MCP integration for LLM agent access
  • Docker multi-arch images
  • GoReleaser CI/CD with per-module versioning
  • Conditional fetch (if-none-match, if-modified-since)
  • SIGHUP certificate reload

Phase 2: Publish Operations: COMPLETE

Done:

  • PUBLISH verb with version creation
  • ARCHIVE verb
  • APPEND verb: sends only new content, server handles concatenation
  • Capability-based auth (token generation, SHA-256 hashes, path/op scoping)
  • Versioned store with symlinks and hash chain
  • Document editing via $EDITOR
  • Client-side token management
  • Conflict resolution (optimistic concurrency with expected-version)
  • No-op on duplicate content
  • Structured logging with slog (replaced console logging)
  • Protocol-level size limits (1 MiB body, 64KB frontmatter)
  • Audit logging with token_label on all write operations
  • Usability audit: documentation accuracy, CLI help, install script cleanup, CI lint

Phase 2 is fully complete. No remaining items.

Phase 3: Agent-Native & Advanced Features: COMPLETE

Done:

  • Agent manifest discovery (/.well-known/agent-manifest.md)
  • Bookmarks/favorites
  • MCP mark_append auto-resolve expected_version
  • Content-addressed fetch
  • Federation via MCP tools

Phase 4: The Information Graph: COMPLETE

Done:

  • The Demarkus Hub pattern
  • Persistent graph store
  • Backlinks
  • Graph as content (export/import)
  • Graph-aware navigation (TUI)
  • Agent discovery

Phase 5: The Demarkus Agent & Private Networks: IN PROGRESS

Read Auth for Private Networks: DONE

Per-path read token enforcement. Server-side and client-side shipped.

Security Hardening: DONE

See plan for full details.

  • Systemd hardening: install script generates units with ProtectSystem=strict, ReadWritePaths, NoNewPrivileges, etc. Conditional ProtectHome. Update path detects insecure config and prompts to harden with rollback.
  • -read-only mode: DEMARKUS_READ_ONLY env var / -read-only flag. Handler rejects PUBLISH/APPEND/ARCHIVE with not-permitted. Zero write access needed.
  • demarkus-publish: CLI tool that writes directly to the versioned store on disk. Enables local publishing when server runs read-only. Shares store.Write() with the server.
  • Read-only chroot install: install-readonly.sh. Chrooted server with ReadOnlyPaths=/, BindReadOnlyPaths=/dev/urandom. Maximum lockdown: Gemini-level security with full versioning.
  • Security documentation: attack surface analysis, threat model, comparison table, hardening guide.

Core Loop: Crawl & Index

  1. Seed: start from configured servers (hub, known peers)
  2. Crawl: follow mark:// links, discover new servers and documents
  3. Hash: collect content hashes from every document
  4. Index: publish updated hash indexes to configured hubs
  5. Repeat: on a configurable schedule, with conditional fetch (if-none-match) to be polite

Server-to-Server Sync

Rsync for the Mark Protocol. Replicate content between servers using content hashes as the diff mechanism.

How it works:

  1. LIST both source and destination servers
  2. Compare content hashes: skip documents that match
  3. FETCH changed/new documents from source
  4. PUBLISH to destination with the fetched content
  5. Optionally handle deletions (ARCHIVE on destination for docs removed from source)

Sync modes:

  • Mirror: destination becomes an exact copy of source (one-way)
  • Selective: sync specific paths or glob patterns (e.g., /docs/* only)
  • Multi-source: aggregate content from multiple servers into one destination

Key Design Points

  • Go binary, not an LLM agent: mechanical work, not reasoning
  • Polite crawling: conditional fetch, configurable rate limits
  • Hub-aware: reads and updates hub index documents
  • Daemon or cron: continuous or triggered
  • Auth-aware: passes read and write tokens for private servers

Implementation Sketch

  • New module: tools/demarkus-agent (the tools/ directory is the home for utility binaries; demarkus-token, demarkus-publish, and now sync/agent tooling)
  • Reuses: fetch.Client, graphstore, client/internal/index, graph.Crawl
  • CLI: demarkus-agent crawl, demarkus-agent sync source dest, demarkus-agent daemon

External Tool Sync: PLANNED

The shape that lets any markdown-emitting tool (OpenSPDD, ADR generators, doc generators, custom workflows) land its output on demarkus without integrating the protocol itself. The integration burden lives on demarkus's side; upstream tools change nothing.

demarkus-sync: single binary in tools/. Watches a local directory and PUBLISHes (or writes via demarkus-publish against a local store) on every changed .md. Versioning happens automatically through the existing store. Cross-team sharing emerges naturally from publishing to a team server.

Core flags:

  • --watch <dir>: directory to watch
  • --target mark://host/path/: remote demarkus server (uses fetch.Client PUBLISH)
  • --store <local-path>: alternative: write directly to a local versioned store via store.Write
  • --include / --exclude glob filters (e.g. **/*.canvas.md)
  • --token / DEMARKUS_AUTH for write auth

Project-flow integration is the killer use: drop into a Makefile target, pre-commit hook, CI step, or long-running dev daemon. Design contracts, ADRs, RFCs, runbooks, agent canvases all flow into the team world automatically; no per-tool integration work.

OpenSPDD as first concrete validation: REASONS Canvas docs are pure markdown contracts written to a local directory. demarkus-sync makes them a versioned, federated demarkus surface without OpenSPDD knowing. Same pattern works for any tool that emits markdown.

The deeper framing: demarkus shouldn't ask other tools to integrate it. It should make any markdown-emitting tool's output queryable as a versioned protocol surface, transparently. demarkus-sync is the generic shape of that.

Reuses: fetch.Client for remote PUBLISH, store.Write for local-store writes, fsnotify for the watch loop.

Phase 6: Universe Deployment: PLANNED

See plan for full details.

Make it cheap to stand up N worlds and onboard real users without hand-distributing tokens. Phase 5 gave us a single hardened server; Phase 6 makes a universe of them deployable with one Helm chart, one ApplicationSet, and one OIDC-fronted token broker.

Sub-phases:

  • 6.1: Helm chart (deploy/helm/demarkus-server/): StatefulSet + volumeClaimTemplates so each world owns its own PVC, Service, bootstrap Job that runs demarkus-token generate and writes to a Secret, optional read-only mode, SIGHUP reload wiring.
  • 6.2: Universe topology (deploy/k8s/examples/): reference Argo CD ApplicationSet over a worlds: list, plus a Kustomize overlay for clusters without Argo. No new code.
  • 6.3: Token broker (prototype in tools/demarkus-broker/, splits to latebit-io/demarkus-broker after 6.4): OIDC-fronted HTTP service that mints scoped tokens, writes hashes to world Secrets, SIGHUPs on revocation, audit log to stdout.
  • 6.4: User install flow (lives with broker): GET /me/install returns a one-shot shell script that writes ~/.config/demarkus/auth and idempotently patches ~/.claude.json with a demarkus-mcp server entry per world.
  • 6.5: Docs: /deployment.md for chart values + broker setup; plan doc maintained at /plans/universe-deployment.md.

Constraint: zero changes to protocol or core server. Phase 6 is packaging and lifecycle. Any need for a new core primitive must be discussed and justified before landing, same rule that applied to the Claude Code plugin.

Explicitly deferred to Phase 7+: multi-replica worlds with shared storage, cross-cluster universe federation, an operator with a World CRD, a web UI on top of the broker.

Single-host mode (shipped 2026-07-17, PR #262 + #263): the broker also runs on a plain VPS beside the server in file-backend mode, no Kubernetes; see the Remote Token Issuance section below and /plans/remote-token-issuance.md. This is the non-k8s delivery of the same broker; the k8s sub-phases above are unchanged.

Verb Set: Complete

7 verbs: FETCH, LIST, VERSIONS, PUBLISH, APPEND, ARCHIVE, LOOKUP.

LOOKUP (issue #113) is the catalog verb: subject lookup over an in-memory, importance-ranked catalog of declared tags + title. It is a token-efficient supplement to the index, not full-text search (which stays in an opt-in sidecar). See the LOOKUP Catalog Verb section below and /plans/lookup-verb.md.

LOOKUP Catalog Verb: DONE (merged to main 2026-05-30, PR #166; plugin tail closed by #168)

See plan. Tracks issue #113.

Given a subject, return which documents are about it and how important they are; a card-catalog supplement to the index hub so an agent finds the right doc without fetching and grepping the whole index. Catalog scan only: in-memory path → {tags, importance, title}, built on the startup walk and maintained inline on writes, ranked by match-count then importance. No body reads at query time, no relevance modeling. Full-text / semantic search is permanently out of core, in an opt-in sidecar.

Shipped:

  • Protocol verb + spec §6.7.
  • server/internal/catalog package + handler with read-auth result filtering.
  • Client surfaces: fetch.Client.Lookup, demarkus lookup CLI, MCP mark_lookup.
  • Broker parity: mark_lookup is the gateway's 14th tool.
  • Publisher metadata: tags/importance settable at publish; CLI -meta key=value (repeatable), and a metadata object on MCP + broker mark_publish. This is what makes the tag/importance ranking actually usable (before it, LOOKUP was title-only with uniform 0.5 importance).
  • End-to-end smoke test passed (publish with tags → lookup by tag → archive, over QUIC).
  • Plugin surfacing (v0.3.0, #168, 2026-05-31): Claude Code plugin wires mark_lookup, fixes the knowledge-join tool-count text (13→14), and injects SessionStart guidance so sessions recall via lookup.

Remaining tail (small): mark_append metadata; deferred by design (you tag on publish, not append). The plugin knowledge-join/version-pin items are now shipped in v0.3.0.

Open Knowledge Format (OKF) Compatibility: DONE (merged 2026-06-22, PR #195)

demarkus interoperates with Google's Open Knowledge Format v0.1 (a bundle of markdown files with YAML frontmatter). The document content model is OKF-compatible; demarkus layers versioning, hash chain, QUIC transport, capability auth, and LOOKUP around it.

Shipped:

  • Store metadata alignment: recognized OKF fields (type, title, description, resource, tags, timestamp) serialize as bare frontmatter; tags as a YAML flow list; non-spec keys keep the meta. prefix; reserved operational fields (version, previous-hash, archived) enforced by name. Back-compatible read of legacy meta.*. Caps raised to 50 keys / 1024 bytes (frontmatter budget 2048), tags counted at serialized length.
  • demarkus okf codec (client/internal/okf); validate (v0.1 conformance), import (bundle → world, sanitize/cap with warnings, link rewrite, upsert), export (world subtree → conformant bundle, frontmatter reattach, type synthesis, RFC 3339 timestamp normalization). Verified live: import → export → validate round-trip, byte-identical bodies.
  • Opinionated default: server assigns type: Document on PUBLISH and APPEND when none declared (reserved index.md/log.md exempt), so every served document is a typed OKF concept by construction. Non-breaking for existing typeless writers (ADR 0003).
  • Spec §8.1/§9.4/§13/§14; ADR 0002 (metadata alignment), ADR 0003 (type default).
  • Review hardening (PR #195 CodeRabbit): export path-traversal guard, import symlink rejection, LIST decode/cycle hardening, reserved-key read guard, cap re-validation, link query strip.

MCP Agent Ergonomics & Attach Surface: DONE (2026-07-04/05)

Two same-week phases that made MCP agents cheap to serve and demarkus pleasant to drive from Claude Code / Desktop. Plans: /plans/mcp-client-ergonomics.md and /plans/mcp-resources-prompts.md. Both fully shipped AND deployed on both MCP surfaces (local demarkus-mcp and the broker gateway).

Shipped:

  • Size-adaptive mark_fetch: full body under 8KB; above it an outline (heading tree with GitHub-slug #anchors + per-section line counts + opening paragraph + hint). url#anchor slices one section at any size; force overrides. Killed the five-figure-token fetches of append-heavy docs (measured: a 52KB roadmap → 5.9KB outline).
  • Session unchanged-dedup: re-fetch of an unchanged doc returns status: unchanged since vN (identity = version+etag; identity-flip edge cases spelled out). Process-scoped in the local client; MCP-session-keyed at the broker (multi-tenant safe: no session → no dedup; LRU eviction at cap, since mcp-go only unregisters sessions on explicit DELETE).
  • mark_explore: one-call orientation card (outline head, opening paragraph, outbound links, backlinks, siblings; 10/section + "+N more").
  • MCP resources: documents as client-attachable context, zero tool turns: mark:// URI templates, #anchor section attach, well-known + background-listed picker entries (client) / per-world index hubs (broker).
  • MCP prompts: orient / recall / whats-new as server-vended slash commands; the broker variants sweep worlds via mark_worlds.
  • Shared packages client/mdoutline (outline/slice + render helpers) and client/fetchdedup (dedup identity + notice text); one implementation for both surfaces (the changedNote mirror drifted twice in review before hoisting; lesson in /debugging.md).
  • One #section grammar everywhere: mark_fetch, mark_explore, MCP resource URIs, and the demarkus-library librarian's open tool (adopted 2026-07-05) all share the same anchors from the same implementation.
  • Deployed: plugin users via client v0.15.0→v0.17.0 (tools 0.4.1+, pin chain now self-driving after the #228 pin-bump automation fix); knowledge.demarkus.io via broker 0.5.0 (ergonomics) and 0.6.0 (resources+prompts).

Remaining: none. Follow-ups all landed (broker parity, resources/prompts, librarian adoption).

Distribution & Package Management: PLANNED

Homebrew tap for easier installation on macOS and Linux.

Build Targets

Supported platforms: macOS, Linux, Windows (WSL only).

Plugins: IN PROGRESS

Obsidian Plugin: v0.1.0 RELEASED

Standalone repo: latebit-io/obsidian-demarkus. Source in plugins/obsidian/.

Claude Code Plugin: v0.13.5 (current)

Merged into main via PR #96 (commit af8e210) on 2026-04-23 as v0.1.0. Source at plugins/claude-code/ in the monorepo; marketplace manifest at .claude-plugin/marketplace.json.

See plan for the full design. One-click marketplace install that lazy-spawns a local demarkus-server, auto-generates a token, and wires demarkus-mcp into the agent. Zero config on install. Includes /soul, /soul-journal, /soul-init, /soul-join, /soul-default, /knowledge-join slash commands and a soul-memory skill.

Shipped with a project-centric soul schema: /index.md is a project list, each project under /<slug>/ holds plan/tasks.md, architecture.md, patterns.md, roadmap.md, adr/, journal/<YYYY-MM-DD>.md.

Version history:

  • v0.1.0 (2026-04-23, #96): initial marketplace plugin.
  • v0.2.0 (2026-05-23, #152): /knowledge-join command for organizational broker-fronted knowledge systems.
  • v0.3.0 (2026-05-31, #168): wires mark_lookup; injects standing SessionStart guidance (context/session-guidance.md) so sessions self-document to the soul and recall via lookup; fixes knowledge-join tool count (13→14); bumps binary pins to SERVER 0.17.13 / CLIENT 0.12.38 / TOOLS 0.1.28.
  • v0.4.0–v0.9.x: not enumerated here (promote bridge, soul-refresh, binary-pin automation, OKF release pin, soul catalog groundwork). See the journals for detail.
  • v0.10.0 / v0.10.1 (#200, #201): /soul-join: managed remote souls. Joins a direct-QUIC remote soul (token in a 0600 file via a launch wrapper, never inline in MCP config), records it in a catalog (~/.demarkus/souls) with a per-project binding (~/.demarkus/project-souls), and enforces the binding with a destination gate (PreToolUse on mark_publish/mark_append; a write to a non-bound soul is denied/asked/warned). Publish tag-gate extended to joined remote souls. 0.10.1 closed a reuse-server restart bug (never kill a healthy externally-managed server on binary upgrade).
  • v0.11.0 (#202, 2026-06-22): /soul-default: set a project's default write target. Standalone command to re-point an already-joined repo at a different default without re-running /soul-join (until now the binding was only ever written as a --bind side-effect of join). Lists the joined-soul catalog (local managed soul + remotes), picks one, saves the binding the dest-gate enforces. Routing stays model-driven: the catalog is the discovery surface and each soul is its own MCP server, so no router or dispatch tool; reads and one-off writes to another joined soul go direct to its mcp__<slug>__mark_* tools (the gate fires only on publish/append). New lib.sh helpers (local_soul_present, soul_catalog, is_catalog_soul); no core/protocol change.
  • v0.12.x (late June → 2026-07-05): pi-agent port fixes (#217), the ergonomics release-chain repins (tools 0.3.5 → 0.4.1, #221/#227), and the automated pin-bump sweep (#229) after the pin-bump workflow's closed-PR upsert bug was fixed (#228: the pin chain is self-driving again: release → auto bump PR → merge).
  • v0.13.0 (2026-07-17, #261): join URLs. /soul-join accepts mark://host#token=... (one paste-able string from install.sh or demarkus-token join); new demarkus join CLI subcommand; pi-memory 0.13.0 ships the same command update.
  • v0.13.x pins (2026-07-18 → 07-24, #264/#266/#268/#274/#275): automated pin-bump chain keeping managed binaries current (the release.yml dispatch fix in #265 revived the dead release trigger). Current: demarkus-memory 0.13.5, demarkus-knowledge 0.5.21, tools 0.14.3.

No core code changes landed. The plugin is built entirely on existing primitives: DEMARKUS_AUTH env var for token injection, /health endpoint plus ALPN "mark" negotiation for port probing, and shell redirection of demarkus-token generate stdout for writing the token to a file. Any future need for a new core primitive must be discussed and justified before landing.

TUI Polish

External Links: PLANNED (implementation ready to commit)

See plan. Closes issue #75 in full. Opens http/https/gemini/mailto links in the user's default handler via open/xdg-open/rundll32. Scheme allowlist, URL passed as argv only, no shell.

The original plan had a Phase 2 for same-server non-markdown files (images); dropped because the server is markdown-only and will not serve binaries. Inline image rendering, if ever pursued, is a separate client-rendering initiative (terminal graphics protocols like Kitty/Sixel), not a continuation of this work.

Features Not Prioritized: Backlog

  • WebSub-style Subscriptions (removed from spec)
  • Offline Mode (deferred)
  • Full-Text / Semantic Search (out of core: opt-in sidecar reading via LIST/FETCH; in-core LOOKUP covers catalog-level subject lookup, see above)
  • Diff / Changelog Between Versions (noted, not implemented)
  • Blind Append with Content Deduplication (rejected)
  • Non-markdown content types on the server (out of scope by design)
  • TOFU cert-fingerprint pinning in join URLs (built on branch join-string, cut before merge; see the pinning decision in /plans/remote-token-issuance.md; any revival rides a redeem handshake)

Operational Polish: Backlog

  • Plain-directory root returns silent not-found; the server only serves the versioned-symlink store layout. Pointing -root at a directory of plain .md files (e.g. docs/site) silently returns not-found for every path and builds a 0-entry hash index and catalog, with no startup warning. Bit setup during the 2026-06-02 load-test work. Flagged to revisit: at minimum a startup warning when the root has markdown files but no versions/ layout; ideally a clear hint.

  • tools/demarkus-loadtest: concurrent read-only load generator (FETCH/LIST/ VERSIONS/LOOKUP) for capacity measurement. Reuses fetch.Client; warm (reused-conn) vs -fresh (handshake-per-request) regimes. Report and first measured numbers in tools/demarkus-loadtest/REPORT.md. See journal 2026-06-02. Key finding: server self-throttles per IP (default 50 req/s, burst 100, DEMARKUS_RATE_LIMIT=0 disables); warm ceiling ~7.7k req/s, cold ~1.4k req/s on one core.

Docs Hygiene

  • SPEC.md markdownlint cleanup: DONE (PR #196, merged 2026-06-22). Added repo-wide .markdownlint.json disabling MD013 (line-length) and MD060 (table-column-style): both pervasive, intentional spec style. Fixed the substantive rules in docs/SPEC.md: fence languages (MD040, 29 fences → text), blank lines around fences/lists (MD031/MD032), bare URLs (MD034), and the emphasis footer (MD036). markdownlint-cli2 docs/SPEC.md → 0 errors; formatting only, no content changes.

  • Single-host deployment page: DONE (2026-07-17). docs/site/deployment/single-host.md covers the server + broker + library stack on one VPS, the auth-tier table (read and tool-write need no IdP; browser editing needs OIDC, self-hosted Dex/Keycloak/Authentik included), OIDC app setup, library SSO wiring, and uninstall.

  • Website brought current: DONE (2026-07-23, pages branch). The site had not moved since 2026-06-22; everything from the last month was missing. Added the five-minute appliance, the library and its librarian, the indexing agent, promote/soul-join, retention, MCP resources and prompts. Framing decision and the library-screenshots maintenance note are in journal 2026-07-23.

Knowledge Layer & Storage Backends: NEXT (added 2026-07-13)

Wire backlinks/graph tools to the published /graph.md: DONE (see Graph hub seeding below)

The federation agent already publishes the aggregated link graph to each hub at /graph.md (hourly, cross-server edges, same format as mark_graph_export), but mark_backlinks and mark_graph answered only from the client's local ~/.mark/graph.json crawl cache. A fresh client answered from an empty graph while an authoritative aggregate sat on the hub unread.

  • Seed/refresh the MCP graphstore from the hub's /graph.md (etag-cached fetch, merge into local store) before falling back to local crawl.
  • Fixes cold start and cross-agent consistency with a client-only change; days of work, no server change.
  • Later upgrade, not the fix: publish-time edge extraction in the store (instant, transactional backlinks). Subject to the backend-parity principle below.

Principle: backend parity is system-level, not implementation-level

With two store backends (file, postgres), behavioral parity is a property of the system, not a detail of either implementation. The storetest conformance suite is the contract; both backends run it unchanged and divergence is a protocol bug.

Consequences for future work:

  • Any new store-observable behavior lands in the conformance suite first, then in every backend, or it does not ship.
  • Knowledge-layer features should prefer layers above the store (published documents like /graph.md, broker aggregation, client merge) which are backend-independent by construction.
  • A feature that only one backend can provide efficiently (e.g. a transactional edges table in postgres) must still be defined system-level: either every backend implements the same observable surface, or the capability is exposed as an optional, discoverable feature rather than silently backend-dependent.

Remaining gaps from the 2026-07-13 knowledge-layer analysis

The /graph.md wiring item above came out of a gap analysis (knowledge-system-deploy session, 2026-07-13): demarkus is strong at the document layer (versioning, integrity, provenance, gates) and weak at the knowledge layer. Only gap 1 got recorded at the time; the rest are recorded here so they are not lost. Framing correction from that session: the plugins are the knowledge layer (recall-first, tag-at-publish, promote, doctors), so the governing principle is that the agent owns judgment while the server owns the accumulation of that judgment (edges, indexes, derived rank live in shared storage so the layer compounds across agents instead of being recomputed per client).

Edge semantics: give edges more information

Every link in the graph is an untyped "mentions". Knowledge graphs live on predicates: supersedes, implements, depends-on, derived-from. OKF types the nodes; nothing types the edges (ADR 0002 superseding its v1 draft was expressible only as prose). Edges also carry no provenance: graph.Edge/StoredEdge are bare from/to pairs and /graph.md exports a two-column table, so backlinks cannot say why or where a doc links (no link text, no source anchor, no occurrence count).

Shape: a rel: metadata axis (e.g. rel:supersedes=/adr/0002-....md) surfaced in mark_graph/mark_backlinks, plus richer edge records in the export (label, source anchor, count). This is a plugin and export-format convention, zero server change, so it can land before publish-time edge extraction; when the store later learns edge extraction it must capture the same fields (backend-parity principle above).

Semantic recall beyond tags

Lookup recall depends entirely on declared tags and titles; recall bounded by metadata hygiene loses to grep, and the agent compensates by exploring and fetching more every session (judgment tokens spent on enumeration). Path: Postgres tsvector full-text over bodies (cheap, phase-2 adjacent), then pgvector embeddings, hybrid-ranked with declared importance. Indexes amortize the agent's search loop into infrastructure; they do not replace its judgment.

Universe-wide LOOKUP

Lookup is scoped to one world; the broker routes but does not aggregate, so "search the whole system" is client-side fan-out. A broker-level aggregated LOOKUP (and eventually graph) makes a multi-world system feel like one system. Broker feature, no server change.

Derived ranking signals

importance is entirely self-declared, so a well-tagged orphan outranks the hub everything cites. Blend link-graph signals (in-degree, centrality) with declared importance. Link structure is not usage telemetry, so this respects the no-tracking principle.

Staleness at lookup time

modified exists but nothing decays or flags aging content at query time; the doctors catch it offline only.

Edge semantics: DONE (merged 2026-07-14, PR #251, commit 8798d0a)

The "Edge semantics: give edges more information" gap above is shipped; see /plans/edge-semantics.md and repo ADR 0004. Edges carry {From, To, Rel, Label, Anchor, Count} with identity {From, To, Rel}; the rel-<predicate> publisher-metadata convention (hyphen form, CSV values) is ingested as typed edges by both crawlers; the /graph.md export is six columns with legacy 2-column parse kept; mark_graph/mark_backlinks/mark_explore render provenance via the shared graph.EdgeAnnotation. Zero server changes. The fetch-callback debt item went with it (removed from /debt.md on merge). Review hardening from three CodeRabbit rounds: whitespace-strict rel refs, Merge replaces refreshed sources' outgoing edges so stale backlinks drop, BaseInline.Lines() panic guard for links inside inline formatting (lesson in /debugging.md), BlockStart provenance for empty-label links. Hubs pick up the enriched /graph.md once the federation agent redeploys with the new binary.

Graph hub seeding: DONE (merged 2026-07-14, PR #253 commit 8ab14aa; follow-ups in PR #254 commit 8fe9b98)

The "Wire backlinks/graph tools to the published /graph.md" item is shipped per /plans/graph-hub-seed.md. What shipped:

  • graphstore SeedFromExport with the local-wins rule: authoritative = real fetched status AND non-zero CrawledAt; seeded nodes carry zero CrawledAt as the durable marker; the same rule arbitrates against Merge's replace-refreshed-sources logic (shared dropEdgesFromLocked/upsertEdgeLocked helpers plus the observedStatus predicate, tested from both directions).
  • Seed etags persist in graph.json as an additive seed_etags map (host to etag); schema stays v1, legacy files load with a nil map.
  • fetch.FetchConditional(host, path, token, etag) for explicit if-none-match (token'd souls skip the disk cache, so the seeder tracks freshness itself). First wire-level test of fetch.Client via an in-process QUIC server.
  • demarkus-mcp: seedGraph(host) in mark_backlinks, mark_graph, and explore's backlinks section, seeding the resolved host from the tool URL (review round: originally defaultHost only); 5-minute per-host process-scoped throttle; never fatal (warn-log and degrade).
  • Broker: seedWorldGraph through dispatchWithAuth in the same three handlers, per world, so cold pods answer backlinks; FetchConditional added to worldDispatcher/worldPool (trivial passthrough); MCP-API.md updated.
  • Bonus fix required by the live check: the MCP backlinks/graph URL keys are now canonicalized through resolveURL (default port applied). Without it the seeded aggregate's mark://host:6309 rows never matched queries built from a portless -host flag; regression test pins it.
  • ParseExport got its first production caller and the live soul /graph.md (still two-column legacy, v6) seeded correctly.
  • PR 253 review also closed a SeedFromExport stale-edge gap: the drop set includes observed seed nodes, so a source whose outgoing set went to zero sheds its stale seeded edges.

Verified live: scratch-HOME cold start against the soul answered 4 backlinks for /patterns.md with zero crawls (133 nodes / 190 edges seeded, etag persisted); a depth-1 crawl of /conventions.md then flipped it authoritative and its enriched edges replaced the seeded rows while 130 nodes stayed seeded. TUI seeding and Exported-timestamp arbitration remain out of scope.

Follow-up shipped same day (PR #254, closes issue #222): Merge no longer clobbers a stored node when a re-crawl fails to read it (unobserved status), so titles converge across flaky crawls instead of regressing, and a stalled federation-agent crawl can no longer publish blanked titles into hub /graph.md for seeding to amplify. Two review findings declined with reasoning on the PR: preserving titles on ok-with-blank fetches (blank is affirmative "no H1") and zeroing CrawledAt on never-seen error nodes (observedStatus already gates authoritative). Released as client v0.21.1.

Same-day broker follow-ups, both found by post-deploy verification and closed (PR #256, PR #257): seeded rows are translated from world dial addresses to mark://{worldName}/... form (the hub aggregate keys rows by cluster-internal DNS, which no legal tool URL can address, so seeding was inert in the real topology), and graph calls seed every configured world instead of only the queried one (the aggregate lives only on the hub). A producer-consumer contract test now pins the agent's real export as a golden that the broker seed suite consumes, so either side drifting fails a build; lesson recorded in /debugging.md. Deployed and verified live on knowledge.demarkus.io (broker 0.12.4): the first graph call on a cold pod answers non-hub backlinks from the hub aggregate with zero crawls.

Two more consumers closed on 2026-07-15. demarkus-library PR 62 (repo latebit-io/demarkus-library, released 0.21.2 and deployed): the floor's hand-rolled /graph.md parser classified tables by column count and got zero edges plus phantom nodes from the enriched format; it now consumes graphstore.ParseExport behind a hexagonal GraphExportParser port returning domain types, with the export-format fixtures living only in the adapter tests. And monorepo #259 (client 0.21.3, agent deployed): fedcrawl falls back to the H1 for node titles when publisher metadata declares none, so soul-style docs stop publishing nameless into the hub graph; precedence pinned by test and silently by the contract golden. Live-verified: /graph.md v766 carries titles on all 147 soul rows.

Graph completeness analysis (2026-07-15)

Post-enhancement code survey recorded at /plans/graph-completeness.md. New Tier 1 findings beyond the gap list above: the 1 MiB /graph.md export wall with silent publish failure (no sharding), tombstone accumulation with no graphstore eviction path, Exported-timestamp arbitration confirmed unimplemented (header written, never parsed), and inconsistent URL canonicalization between crawlers. Suggested order in the plan doc: sharding + loud failure, eviction, timestamp arbitration, shared canonicalization, rel query filter + predicate vocabulary, in-degree into LOOKUP.

Remote Token Issuance: DONE (added 2026-07-15, closed 2026-07-17)

See plan. Two onboarding tiers shipped; invites and the capability front door both dropped as unnecessary.

Step 1 DONE (join URLs, PR #261): join URL mark://host#token=... as the one paste-able onboarding string; demarkus-token join; demarkus join CLI subcommand; install.sh ready-to-paste /soul-join line; /soul-join in claude-code 0.13.0 + pi-memory 0.13.0. TOFU cert pinning built then cut (see plan pinning decision).

Step 2 DONE (broker single-host mode, PR #262 + #263): the broker runs on a plain VPS in file-backend mode (SecretStore interface, tokens.toml writer that the server hot-reloads), so /knowledge-join, library SSO, and per-world authorization work without Kubernetes. install.sh --with-broker --with-library with hardened systemd units; docs/site/deployment/single-host.md. Verified live end to end (broker-signed bearer drives the gateway; mark_publish provisions a write token into the local tokens.toml and the doc lands; the library renders broker-written docs).

Invites DROPPED, capability front door DECLINED (2026-07-17): with the broker in single-host mode, invites are redundant, because the broker is OIDC-fronted by construction, so any broker deployment onboards via /knowledge-join plus an existing IdP login, which beats an anonymous invite code on identity, revocation, and secret-in-transit. The one remaining case (multi-user browser editing without any IdP) is declined: the library's cataloging desk (browser editor, shipped in library v0.22.0) writes only through broker mode, the broker needs OIDC, and OIDC means any provider including a self-hosted one (Dex/Keycloak/Authentik), so the ecosystem already covers the no-commercial-IdP case and a bespoke broker auth surface would be a worse Dex. Final tier map: read anywhere (no IdP); write via CLI/agent/Obsidian anywhere (no IdP, join URL); browser editing needs an OIDC provider (self-hosted fine). The earlier in-server options (A, B) and the sidecar framing are recorded in the plan as superseded.

Five-Minute Appliance & Install Hardening: DONE (2026-07-18 → 07-22, PRs #267, #269–#272)

The single-host stack became a one-command appliance, then a week of hardening driven by the soul.demarkus.io droplet migration (journal 2026-07-22). Journals 2026-07-18 through 07-22 carry the detail.

  • Agent release asset (PR #267): demarkus-agent ships as a release tarball so the appliance can install the indexing agent without building from source.
  • install-stack.sh appliance (PR #269): orchestrator that runs install.sh --with-broker --with-library then layers Authelia (all secrets generated by the authelia binary itself, mandatory claims_policy), Caddy (path-split reverse proxy owning 80/443, caddy validate gate), broker OIDC wiring with an abort-if-any-placeholder-survives guard, sslip.io default hostname, and a final card with library URL, owner login, and the /knowledge-join line. Split out of install.sh by decision (precedent: install-readonly.sh); install.sh stays standalone, its uninstall extended to tear down all flavors. Verified live in a debian bookworm container.
  • Library on HTTPS :443 (PR #270): on TLS installs the library defaults to 443 sharing the world's cert (one hostname, one identity); --library-port override; kept /etc/demarkus-library/env is authoritative and validated (validate_port on kept values too); fail-closed ss port preflight keyed on the unit's MainPID, not process names; conditional CAP_NET_BIND_SERVICE; cert-group access and renewal hook follow the effective values. Three CodeRabbit rounds; 41-assertion harness.
  • Self-dial probe (PR #271): after install with --with-broker/--with-library on a domain, ensure_self_dial probes mark://<domain>/health; on hairpin-NAT failure it pins the domain to loopback in /etc/hosts (exact-token idempotent) and re-probes; fail-closed diagnostics otherwise. Long-term fix stays open in demarkus-library: decouple display name from dial address.
  • Cert renewal on re-run (PR #272): a re-run of install.sh silently skipped the renewal-hook setup (gotcha found during the droplet migration); now reachable on re-run and timer-safe, with a CI-run renewal test harness.
  • Ops: soul.demarkus.io migrated off the home Orange Pi onto a DigitalOcean droplet with full version history intact (2026-07-22); migration gotchas and droplet hardening recorded in journal 2026-07-22 and /debugging.md.

Markdown-Only Content Contract: DONE (merged 2026-07-23, PR #273)

Implementation caught up with SPEC 2.3 ("content is markdown"): before this, a PNG published to a server round-tripped byte-perfect and every markdown-derived feature degraded silently, worst of all MCP outline mode injecting raw binary as mojibake into an agent's context. Full analysis and layer-split reasoning in journal 2026-07-23; architecture decision recorded in /architecture.md.

  • Handler gate (protocol surface): PUBLISH/APPEND path must end in lowercase .md and body must be valid UTF-8, else bad-request with no version created; pure store.ValidateDocumentContent called from both write handlers (same one-choke pattern as ValidateMeta).
  • Store invariant (defense in depth): store.ValidateBody (UTF-8 only) at both backends' write choke plus a RejectsBinaryBody conformance case; the .md rule deliberately stays out of the store, which is path-agnostic and whose traversal-safety tests rely on non-.md paths.
  • MCP render guard: mark_fetch, mark_explore, and resource reads return a one-line notice for non-UTF-8 bodies instead of mojibake; force=true still pulls raw bytes. Raw protocol FETCH stays byte-faithful (refusing to serve would make any binary that reached disk permanently uninspectable).
  • SPEC updates (4.4, 6.4, 6.6) and the §7 status table corrected: bad-request and conflict moved from "future" to active.

Broker follow-up DONE (merged 2026-07-25, PR #277, closes issue #276). #273 guarded only the local demarkus-mcp, leaving the broker gateway's render paths still able to put mojibake into agent context. The guard now lives once in client/mdoutline (BinaryBody, NonMarkdownNotice), shared by both MCP surfaces the same way fetchdedup and the outline helpers already were; the broker calls it at handleMarkFetch (before the anchor slice, mode: binary), handleMarkExplore (before the outline builder), and readResource (text/plain notice). As in #273, force=true bypasses the size gate but never the binary gate. Deployed to knowledge.demarkus.io as broker 0.14.5 and verified live (gateway fetch and explore render normally; the guard itself is now an unexercisable backstop, since worlds at server 0.22.0 refuse to accept binary in the first place).

OpenCode Memory Plugin: DONE (merged 2026-08-10, PR #281)

plugins/opencode-memory/ v0.13.8: the OpenCode port of demarkus-memory. Thin single-file TS adapter over the shared demarkus-plugin binary (pi pattern); config hook wires the MCP server + joined souls + slash commands, gates via tool.execute.before/after, guidance/recall via chat.message, journal nudge via session.idle. Installed by curl one-liner (no npm). Plan + follow-ups: /plans/opencode-memory-plugin.md.

Store: make LIST and FETCH agree on flat files: NEXT (added 2026-08-11)

Root cause behind issue #288. The store's ListDir deliberately shows flat files (legacy compat, store.go dirHasVisibleEntry path) while Get refuses anything without version history, so a flat file LISTs but 404s on FETCH forever. Worse, Write's next==1 branch skips migrateFlatFile (migrations only run under next > 1 via prepareExistingDoc), so publishing over a flat file silently clobbers it instead of promoting it to v1.

The fix: Write migrates a versionless flat current file to v1 before writing (extend the existing migrateFlatFile machinery to the next==1 path). A create-only publish over a flat file then conflicts with the migrated v1 instead of destroying it, and any write to a flat path heals it into a served document. Decide separately whether LIST should keep showing never-written flat files or hide them.

Sequencing with the plugin fix (branch fix/seed-via-publish): the plugin's seedDoc adoption branch for index.md becomes removable once the server pin bumps past this fix. The project-template.md adoption stays regardless, because store migration only triggers on a write and nothing else ever writes that path.

Update (same day): the plugin branch dropped its adoption paths entirely rather than carrying transitional code. cleanupLegacyTemplate is local-only (pristine hash match → delete; customized flat template left on disk for store migration), and seedDoc publishes the embedded seed create-only. Consequence: the store fix must ship first and the plugin's serverVersion pin must bump past it before this plugin release, because on an unfixed server the create-only seed publish over a legacy flat index.md clobbers it (the next==1 migrateFlatFile skip) instead of conflicting.

Resolution (2026-08-11, branch fix/store-flat-file-migration): Fritz ruled flat files are not documents, and the spec was amended instead of implementing adoption. SPEC 9.8 rewritten from "Flat File Migration" (publish MUST migrate flat to v1) to "Non-Document Files" (publish MUST NOT incorporate flat content; it creates v1 and replaces the file), SPEC 6.2/6.5/11.9 and DESIGN updated to match. Store change is LIST-side only: ListDir and the directory-visibility walks exclude versionless flat files in both views, and a directory whose subtree holds no documents is excluded even from the include-archived audit view. Legacy flat-layout docs (protocol-written, versions/doc.md.v{N}) remain visible and are unaffected. Write path needed no change; its replace behavior is now the specified behavior, which also removes the plugin PR's release-ordering constraint entirely.

Merged 2026-08-12 as PR #291 (9f1f062 on main): spec amendment, LIST exclusion of non-documents, eager legacy-layout migration in store.Open, legacy flat-layout code removed. The plugin PR #290 has no release-ordering dependency on it and remains open.

PR #290 merged 2026-08-12 (8571554); issue #288 resolved at both layers.

Related documents

Production Shared Knowledge System: DONE (2026-08-23)

The GKE reference deployment now serves three logical worlds from one multi-replica GCS-backed knowledge server: root, latebit, and ontehfritz. Broker routing, Workload Identity, per-world buckets and policy, SNI aliases, writable token flows, Library broker mode, and the hourly federation agent are live. Client and agent 0.26.1 add the strict-policy metadata required by generated hash indexes and /graph.md; production publish verification passed.

Legacy per-world storage and credentials for root, servicing, latebit, origination, and world-a were removed after explicit review. The authenticated broker directory is now the three-world production topology. Current content worlds contain no user documents; populating latebit and ontehfritz is product/content work rather than deployment debt.

trail
  1. soul.demarkus.io v60
  2. roadmap