Skip to content

borgmcp 6.0.0

Choose a tag to compare

@TheodorStorm TheodorStorm released this 29 Sep 07:10
· 157 commits to main since this release
e6a7fac

borgmcp 6.0.0

The human representative's host integration is rebuilt as one system. Representative state moves into one SQLite database, and the listener becomes the push engine. The Hermes plugin is now a thin adapter, and one command sets Hermes up. Merged since 5.7.0: #594, #595, #596, #597 and #598.

Breaking

  • Node.js 22.13.0 or later is required (engines.node >=22.13.0, up from >=22.12.0). Representative state uses Node's built-in node:sqlite, which is unflagged from 22.13.0.
  • Representative state is rebuilt in one SQLite database, <Borg config>/representative/state/.
    • Bindings, the request ledger, delivery positions and wake state all live there, under generation directories and a CURRENT pointer that is replaced by atomic rename.
    • Every path is checked on every open: owned by you, 0700 directories, 0600 files, never a symlink.
    • The borgmcp 5.x files are imported once, when the database is first created. Each valid 5.x binding is imported (a second binding of the same generation is skipped), and its delivery start follows the documented start rule. Until that database exists, hermes-plugin install may also read the 5.x bindings, read only and on each run, to find the prepared worktree; it creates nothing. The 5.x files are never written, and never read again once the database exists.
    • The database is created on first use by borg representative prepare, mcp or listen.
    • A database with another schema version refuses with REPRESENTATIVE_STATE_VERSION.
  • The single tools lease is removed. Any number of processes may use the representative tools at once, for example Hermes Desktop, the CLI and the gateway. Each operation takes its own transaction.
  • borg representative listen speaks protocol 2 only.
    • It requires --protocol 2, and stdin must be a pipe or socket owned by the host. Without these it prints refused and exits 2.
    • It prints listening, wake, refused and stopped events, with no message body, and reads {wake_id, accepted} acks of at most 1 KiB each on stdin.
    • EOF on stdin, or SIGTERM/SIGINT, stops it from the first startup step on.
    • --replay-after and the inbox replay are removed.
    • A 5.7.0 plugin, which starts the listener without --protocol 2, is refused and stops. Upgrade the plugin as below.
  • borg representative hermes-plugin install now configures Hermes (see New). --force is removed: a rerun updates an existing install.
  • The Hermes plugin settings mcp_server, reinject_after_s and max_reinjects are gone. Plugin v2 keeps no state of its own and no longer uses <Hermes home>/plugin-data/borg-representative-push/.

New

  • The listener is the push engine.
    • Wake state is durable, and one wake batch is outstanding at a time, with a 2-second debounce.
    • The ack deadline is 60 seconds, measured absolutely.
    • An undelivered reply is woken again after 10 minutes, 1 hour and 6 hours, then every 24 hours.
    • A refused wake backs off from 30 seconds, doubling to 30 minutes.
    • On start, a bounded startup cohort ends in one startup wake for replies that are still undelivered.
    • Delivery is observed from any process; Borg stops waking for a reply once it is delivered.
    • Invalid wake state is rebuilt from the delivered checkpoint instead of blocking the listener.
  • Hermes plugin v2 (borg-representative-push 2.0.0).
    • It has three settings: session_key, worktree and borg_command.
    • It starts the listener only inside the messaging gateway and owns the listener's stdin pipe, so the listener exits when the gateway does.
    • Each wake becomes one fixed, body-free injected message.
    • A listener that does not announce protocol 2 first is never driven: it is closed, then sent SIGTERM, then SIGKILL, and reaped.
  • borg representative hermes-plugin install sets Hermes up in one command.
    • It installs the plugin files and writes the settings, allow_gateway_injection and the borg-representative MCP entry (lazy: true, the absolute borg path) through Hermes's own CLI, reading every value back.
    • It enables the plugin.
    • It restarts the gateway only when it runs as a launchd or systemd service, and confirms the restart by a new PID. For a hand-started gateway it prints the command instead.
    • It stops and restarts nothing in Hermes Desktop. It prints "new chats get the Borg tools" for an added MCP entry, and the /reload-mcp-or-restart line for a changed one.
    • It never picks the conversation to wake without your confirmation. A --session-key, or an already configured session_key that is a valid gateway DM key, is used without a prompt and without a refusal. Otherwise, at a terminal it lists every gateway DM candidate and asks [y/N]; without a terminal it refuses and prints the exact --session-key command, keeping --hermes-home and --worktree.
    • config.yaml is backed up first. On failure Borg reverses only its own keys.
    • Nothing is written before its checks pass, and the installer never creates representative state.
    • It reports whether a gateway allow-all switch is set (gateway.allow_all_users, allow_all_users, GATEWAY_ALLOW_ALL_USERS or <PLATFORM>_ALLOW_ALL_USERS). It does not report other gateway access such as allowlists or pairing.
    • --dry-run only reads, and --no-restart leaves the activation pending, so a rerun finishes it.
  • borg representative hermes-plugin uninstall removes only Borg's entries and files. The unload stays pending until the gateway is confirmed to have dropped the plugin.
  • borg update activates an installed plugin.
    • It refreshes the files, settings and MCP entry, and never asks a question.
    • An activation that fails or throws produces a warning naming borg representative hermes-plugin install. It never changes the update's result. An activation left pending on a hand-started gateway is not a failure: it prints the command to restart that gateway.
  • borg representative status reports the listener, its wake state and, when the plugin is installed, hermes_plugin: its activation state, a pending Desktop reload, the conversation and the open-gateway report.
  • borg representative reset-state is disaster recovery for a corrupt state database. It refuses a healthy database and reports exactly what was kept and what was lost.

Fixed

  • The Hermes plugin guide's refused-wake wording was qualified to match the 5.7.0 release notes (#594). Plugin v2 in this release replaces that whole mechanism.
  • The test suite refuses writes into the real home directory at the file-system layer; only isolated test roots are allowed. This only affects development.
  • A failing cross-process state test can no longer hang the suite: its child processes wait with a deadline, and every child still running is killed and reaped after each test (7583e50). This only affects development.

Upgrading

  1. Make sure Node.js is 22.13.0 or later.
  2. Run borg update. With a 5.7.0 plugin installed, it upgrades the plugin, its settings and the MCP entry. It keeps a configured session_key when that key is a gateway DM key.
  3. Run borg representative hermes-plugin install when borg update warns that the activation is incomplete, or to install the plugin for the first time.
    • An already configured session_key that is a valid gateway DM key is kept without a prompt. Otherwise, at a terminal, confirm the gateway DM to wake, or pass --session-key.
  4. If Hermes Desktop was open, run /reload-mcp in its open chats, or restart it, when the install says the Borg MCP entry changed.
  5. The shared contract pin is unchanged: borgmcp-shared 2.2.0.