Plan: Plugin Knowledge-Quality Enforcement
Status: active (opened 2026-06-01). Raises the demarkus-memory Claude Code plugin from "advisory knowledge system" to "enforced knowledge system," and aligns the per-project template to the proven demarkus-soul structure.
Owner: Fritz. Driver: agent.
Problem
The plugin splits into two layers at very different maturity:
- Infra layer (binary install, server spawn, token mint, version pins, health checks): solid, deterministic, harness-executed.
- Knowledge-quality layer (
context/session-guidance.md, thesoul-memoryskill, routing + tagging rules): 100% advisory prose executed by the model on best effort. Nothing verifies any of it happened.
Every load-bearing quality assumption — tag every publish, recall before answering, route content to the right file, journal significant sessions — is hope, not enforcement. The server "infers nothing." So quality degrades silently and invisibly: a tagless publish is accepted happily and becomes permanently invisible to mark_lookup. That is the single most damaging hole, because the entire recall half of the system depends on tags the agent must remember to set by hand while focused on content.
Strategy
Move enforcement off advisory prose and onto hooks, which the harness executes deterministically. Pure composition of existing surface — no core changes (consistent with the core-vs-overlay layering rule: the server stays permissive; the agent-driven client gets the guardrail).
Severity is configurable but the check always runs — this is a strictness level, not an opt-in capability (no "validation off" deployment exists):
warn(default) — PostToolUse injects a system-reminder after a tagless publish, nudging a re-publish with tags. Non-blocking.block— PreToolUse denies a tagless publish until tags are added.ask— PreToolUse escalates to the user.
Hook mechanics (verified against code.claude.com/docs/en/hooks)
- PreToolUse:
hookSpecificOutput.permissionDecisioninallow|deny|ask|defer+permissionDecisionReason. Carriesblock/ask. - PostToolUse:
hookSpecificOutput.additionalContextinjects a system-reminder the model sees next turn. Carrieswarn. (Plain stdout is debug-only, not model-visible.) - Matchers are JS regex against the full
mcp__server__toolname. Scope tomcp__.*demarkus-memory__mark_publishso it hits the plugin's own server, not the user's other demarkus servers (soul/hub/knowledge). - One dispatcher script handles both events; branches on
hook_event_name, readsSTRICTNESS. Fails open — no JSON parser (jq/python3) → defer, never block.
Work items
1. Publish tag/importance gate (STARTED)
hooks/publish-gate.sh + PreToolUse/PostToolUse wiring in plugin.json. Tagless publish (tool_input.metadata.tags absent/empty) → warn/block/ask per STRICTNESS. Optional: importance present but outside [0,1] → same nudge. STRICTNESS read from ~/.demarkus/plugin-memory.conf (default warn). Shell test mirroring tests/knowledge-join_test.sh. Bump SERVER/CLIENT/TOOLS pins per the version-pin rule. Update session-guidance.md to note tagging is now gated.
2. Relocate decaying guidance to the moment of action
SessionStart additionalContext is summarized away first in long sessions. The write-time gate (#1) re-asserts the tagging rule exactly when it matters. Trim/realign session-guidance so it points at the gate rather than carrying the full burden as startup prose.
3. Session-end journal nudge (Stop hook, guarded)
Stop hook checks: did the session do real work but write nothing to the soul? If so, nudge to journal. Guard against re-prompt loops (only fire once per session; respect a sentinel).
4. Recall-before-answering (deferred until 1-3 land)
Hardest, most false-positive-prone. Good capture is the precondition for good recall — fix tags first. Cheap version: UserPromptSubmit pattern-match ("did we / what did / last time / decide") → one-line "check the soul" nudge.
5. Catalog hygiene tooling (/soul-doctor)
Surfaces existing rot: orphan docs (no inbound links, not in any index), broken [[links]], duplicate content, stale index entries, ADR numbering collisions. Built on mark_backlinks / mark_graph. Tooling, not enforcement.
6. Align local per-project template to the proven demarkus-soul structure
The current template (plan/tasks.md, architecture.md, patterns.md, roadmap.md, adr/NNNN, journal/) is thin, defined three times with drift (session-guidance.md, SKILL.md, seed/index.md), and poorer than the structure demarkus-soul actually evolved. Adopt the proven layout: a maintained per-project index.md hub, architecture.md, patterns.md, guidelines.md, debugging.md (highest recall value — lessons from bugs), roadmap.md, debt.md, adr/NNNN-*.md, plans/ as a first-class lifecycle (active/completed/archived), journal/<YYYY-MM-DD>.md, thoughts.md. Define the canonical template once; the other docs reference it (kills the three-way drift).
7. Knowledge-system template + policy, anchored at the root hub
Every knowledge system gets a guaranteed hub world named root that tracks things globally. The org template + policy live there as ordinary versioned docs:
mark://root/.well-known/demarkus/template.md— required structure (same canonical layout as #6).mark://root/.well-known/demarkus/policy.md— strictness level + required tags / rules.
Read path is constrained by a hard fact: hooks cannot authenticate to the broker (the OAuth token lives in Claude Code's MCP machinery, not a file). So:
- The tag-presence gate works unchanged on knowledge-system publishes — tags are in the tool-call input the hook receives; no broker access needed. Matcher extends to
mcp__<slug>__mark_publish. - The template/policy are agent-read (only the agent can reach the broker via the MCP OAuth session). Session guidance: on a knowledge system, fetch
rootpolicy/template first and follow it. Advisory by necessity, but it is structure convention, not a security boundary. - For enforced strictness, the agent is courier: reads
policy.md's declared level → writesKNOWLEDGE_<slug>_STRICTNESS=blockinto local config → the credential-less hook reads it off disk and enforces. Org declares once onroot; agent mirrors; hook enforces.
Customization = the org edits the two root docs. Versioned like any demarkus doc; propagates to every member on next session. No plugin release, no per-user setup. Open question: how the plugin learns which world is root — prefer the broker advertising it in /.well-known metadata (the join script already hits that endpoint) over a guessed/fixed name.
Sequencing
1 → 2 → 6 (canonical template, used by 7) → 3 → 7 → 5 → 4. Item 1 is highest-leverage and testable in isolation; start there.
Constraints honored
- No core changes — pure plugin composition.
- Version pins bumped on every plugin change (
scripts/lib.sh). - No opt-in capability knob — strictness is a severity level on an always-on check.
- Plans live on the soul, not in a vendor folder.