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).
Status — built (branch feat/soul-join)
Phase 1 (catalog + binding + /soul-join + wrapper + adopt detection) and
phase 2 (destination gate) both implemented and tested. Plugin v0.9.0 → v0.10.0.
- lib.sh: catalog/binding helpers,
publish_gate_scopeextended,soul_target_id,configured_dest_strictness,restart_local_server_on_upgrade(+DEMARKUS_BINARIES_REPLACEDfromensure_binaries). - scripts:
soul-join.sh,soul-remote-wrapper.sh(self-contained, stable install path),detect-manual-souls.sh. - hooks:
dest-gate.sh(PreToolUse on publish+append) registered in plugin.json;session-start.shrestarts the configured server after a binary upgrade. - commands/soul-join.md; session-guidance routing + enforcement note.
- tests: soul-join (16), dest-gate (11), restart-on-upgrade (5); full suite 112 green; shellcheck clean.
Also closed an adjacent bug: a binary swap (via /soul-join or a pin bump) now
restarts the configured local server with its own recorded config, in every
mode including reuse.
Remaining (deferred): full auto-migration of a hand-wired .mcp.json entry
(currently guided re-join + claude mcp remove); /soul-join reachability
hardening; wrapper auto-reinstall on plugin upgrade; append-metadata
carry-forward (orthogonal, pre-existing). Binary version pins intentionally not
bumped (plugin-only change; release-time decision).