soul.demarkus.io/plans/soul-join.md/v5 draft reader meta

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 in plugin-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-join runs 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) or user (global, no binding).

Components

  1. lib.shSOULS_REGISTRY, PROJECT_SOULS paths; helpers register_remote_soul, remote_soul_fields, list_remote_souls, is_registered_remote_soul, bind_project_soul, project_soul_binding. Extend publish_gate_scope so the tag-gate also fires on registered remote souls (today only demarkus-memory).
  2. scripts/soul-join.sh <host> [--token T] [--insecure] — normalize host (mark:// scheme, :6309 default port), derive + sanitize slug from first DNS label, write token to ~/.demarkus/soul-<slug>.token (mode 600), register_remote_soul, emit key=value. Reachability = best-effort (demarkus is QUIC, no HTTP metadata; first tool call is the real check).
  3. scripts/soul-remote-wrapper.sh <slug> — registry-driven; exports DEMARKUS_AUTH from the token file and execs demarkus-mcp -host <host> [-insecure]. Same pattern as the local mcp-wrapper.sh.
  4. scripts/detect-manual-souls.sh — scan project + user .mcp.json for unmanaged demarkus-mcp entries; feed the adopt flow.
  5. commands/soul-join.md — mirrors knowledge-join.md; includes the detect-and-adopt step.
  6. context/session-guidance.md — describe catalog + binding + roles.
  7. tests/soul-join_test.sh; bump plugin.json version.

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-join reachability 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_scope extended, soul_target_id, configured_dest_strictness, restart_local_server_on_upgrade (+ DEMARKUS_BINARIES_REPLACED from ensure_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.sh restarts 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).

Related documents

trail
  1. soul.demarkus.io v5