Plan — /soul-join: managed remote souls + catalog + project binding
Problem
Three demarkus surfaces can be configured at once — a hand-wired .mcp.json
demarkus server ("pure MCP", e.g. demarkus-soul → soul.demarkus.io), the
demarkus-memory plugin (local managed soul), and the demarkus-knowledge plugin
(broker KS). All expose identical mark_* tools, so the agent can't tell which
to write to. Today a remote soul can only be reached by hand-editing
.mcp.json, which also leaks the auth token in plaintext into both .mcp.json
and claude mcp list. There is no /soul-join (the soul index Vocabulary
already anticipates one).
Decision (Fritz, 2026-06-22)
Build /soul-join into demarkus-memory so there is no manual MCP for souls,
mirroring /knowledge-join. Token storage = wrapper + 0600 file (no secret in
config). Maintain a catalog of souls plus a per-project binding that
routes writes. Detect existing hand-wired demarkus-mcp entries and offer to
adopt them.
Data model
- Catalog —
~/.demarkus/souls(tab-separated, one row per remote soul):<slug>\t<host>\t<insecure 0|1>\t<token-file|->. The local managed soul stays inplugin-memory.conf(tier=local); a catalog view unions local + remote + knowledge endpoints. Tiers:local(plugin-managed),remote(joined). - Per-project binding —
~/.demarkus/project-souls:<project-dir>\t<slug>. Which catalog soul this repo writes to by default. Set when/soul-joinruns inside a repo. This is the routing key that ends the "which one?" confusion. - MCP registration scope — orthogonal:
project(repo.mcp.json, default when joined in a repo) oruser(global, no binding).
Components
lib.sh—SOULS_REGISTRY,PROJECT_SOULSpaths; helpersregister_remote_soul,remote_soul_fields,list_remote_souls,is_registered_remote_soul,bind_project_soul,project_soul_binding. Extendpublish_gate_scopeso the tag-gate also fires on registered remote souls (today onlydemarkus-memory).scripts/soul-join.sh <host> [--token T] [--insecure]— normalize host (mark://scheme,:6309default port), derive + sanitize slug from first DNS label, write token to~/.demarkus/soul-<slug>.token(mode 600),register_remote_soul, emitkey=value. Reachability = best-effort (demarkus is QUIC, no HTTP metadata; first tool call is the real check).scripts/soul-remote-wrapper.sh <slug>— registry-driven; exportsDEMARKUS_AUTHfrom the token file and execsdemarkus-mcp -host <host> [-insecure]. Same pattern as the localmcp-wrapper.sh.scripts/detect-manual-souls.sh— scan project + user.mcp.jsonfor unmanageddemarkus-mcpentries; feed the adopt flow.commands/soul-join.md— mirrorsknowledge-join.md; includes the detect-and-adopt step.context/session-guidance.md— describe catalog + binding + roles.tests/soul-join_test.sh; bumpplugin.jsonversion.
Constraints (project conventions)
- Pure awk/bash, no runtime deps (jq/python/node forbidden).
- Bash 3.2 (macOS stock). Branch + PR; Fritz commits. Never ship broken.
Open / phase 2
- A destination gate (PreToolUse on
mark_publish) enforcing the project→soul binding so a misrouted write is denied, not just guided. /soul-joinreachability hardening (one-shot stdio MCP probe).