borgmcp 5.6.0 → 5.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -415,6 +415,115 @@ mapping, and holds unknown correlation for the human. Deduplicate queued hints b
415
415
  advancing the host's `--replay-after` checkpoint. That hint checkpoint is separate
416
416
  from the delivered checkpoint and from `ack`, which remains a server receipt.
417
417
 
418
+ ## Hermes push plugin
419
+
420
+ For Hermes, Borg ships a Hermes user plugin, `borg-representative-push`, that
421
+ supervises the listener for you. When the bound Coordinator replies, the plugin
422
+ wakes one Hermes conversation right away. Hermes source is not changed; the
423
+ plugin is installed and enabled through Hermes's documented plugin mechanism.
424
+
425
+ **Which conversations can be woken.** Only a Hermes *messaging-gateway*
426
+ conversation (Telegram, Discord, Slack and the other gateway platforms), named
427
+ by its gateway `session_key`, for example `agent:main:telegram:dm:<chat id>`.
428
+ A Hermes Desktop chat cannot be woken: Desktop runs its chats in `hermes serve`,
429
+ and Hermes injects plugin messages only into gateway conversations. The
430
+ `session_key` stays the same across `/new` and `/reset` in that chat.
431
+
432
+ ### Install
433
+
434
+ ```bash
435
+ borg representative hermes-plugin install [--hermes-home <path>] [--force]
436
+ ```
437
+
438
+ This copies the plugin's two files into `<Hermes home>/plugins/borg-representative-push/`
439
+ (the Hermes home is `--hermes-home`, else `$HERMES_HOME`, else `~/.hermes`) and
440
+ prints the configuration to add. It refuses to replace an existing install
441
+ without `--force`, refuses a symbolic-link target, never edits Hermes config and
442
+ never starts or restarts Hermes. Rerun it with `--force` after upgrading
443
+ borgmcp to update the plugin.
444
+
445
+ ### Configure
446
+
447
+ Add to the Hermes `config.yaml`:
448
+
449
+ ```yaml
450
+ plugins:
451
+ enabled:
452
+ - borg-representative-push
453
+ entries:
454
+ borg-representative-push:
455
+ allow_gateway_injection: true
456
+ settings:
457
+ session_key: "agent:main:<platform>:<chat type>:<chat id>"
458
+ worktree: "<absolute path of the prepared representative worktree>"
459
+ # optional: borg_command (default borg), mcp_server (default
460
+ # borg-representative), reinject_after_s (default 600), max_reinjects (default 3)
461
+ mcp_servers:
462
+ borg-representative:
463
+ command: borg
464
+ args: ["representative", "mcp", "--worktree", "<same absolute worktree path>"]
465
+ lazy: true
466
+ ```
467
+
468
+ `allow_gateway_injection` is Hermes's per-plugin permission to start gateway
469
+ turns; it is off by default. `mcp_server` must name the `mcp_servers` entry that
470
+ runs `borg representative mcp`, because the plugin recognises the deliver tool
471
+ by that name. Then restart the gateway (`hermes gateway restart`).
472
+
473
+ **One tool owner.** Hermes starts a separate MCP process in every Hermes
474
+ process that uses the server, and only one process may hold the representative
475
+ tools lease. The woken conversation runs in the gateway, so the gateway must be
476
+ the process that uses the tools. With `lazy: true`, a process starts the Borg
477
+ MCP server only when one of its conversations calls a Borg tool. Run `hermes
478
+ tools` and disable the `mcp-borg-representative` toolset on every platform except
479
+ the one in `session_key`, Desktop and CLI included. If another process already
480
+ holds the lease (`borg representative status` shows `owned-by-other-process`),
481
+ restart that process once so it releases the lease.
482
+
483
+ ### Behaviour
484
+
485
+ - The listener starts only inside the Hermes messaging gateway, when the platform
486
+ named in `session_key` connects. The CLI, Desktop and worker processes load the
487
+ plugin but start nothing. A platform reconnect does not start a second listener.
488
+ - A burst of hints (about 2 seconds) becomes one injected message with fixed
489
+ text: `Borg: new Coordinator reply. Call borg_representative-read, persist and
490
+ relay, then borg_representative-deliver through the last persisted entry_id.`
491
+ No message body, sender or document ever passes through the plugin. A busy
492
+ conversation queues the message; it does not interrupt the running turn.
493
+ - The plugin watches this gateway's `borg_representative-deliver` results. A
494
+ hinted reply that is still undelivered after `reinject_after_s` wakes the
495
+ conversation again. Each reply gets at most `1 + max_reinjects` wakes in total;
496
+ after that the plugin logs it and wakes no more for that reply until a delivery
497
+ covers it. Every wake is counted on disk before Hermes is asked to start the turn,
498
+ so neither a replayed hint nor a gateway restart renews the count. A wake Hermes
499
+ refuses is not counted. A crash between counting and asking can lose one wake,
500
+ never add one. Hermes reports only that it accepted a message, not that the
501
+ turn ran, so this is how a dropped wake is recovered.
502
+ - One small record (id, timestamp, count) is kept for each reply the plugin has
503
+ woken the conversation for. It is removed only when an observed delivery covers
504
+ that reply; there is no count limit. The records therefore grow only while woken
505
+ replies stay undelivered.
506
+ - Listener exits: 0 stops; 1 restarts with capped backoff; 2 stops and logs
507
+ (fix the binding, then restart the gateway); 3 (another listener owns the
508
+ lease) retries with backoff; 4 restarts after `lease-lost` and otherwise stops
509
+ and logs (evicted, rebound, revoked, trust-changed). A stop ends every wake,
510
+ including queued and repeat wakes, until the gateway restarts.
511
+ - On restart the listener replays retained hints after the last delivered
512
+ checkpoint the plugin observed (`--replay-after`). The delivered checkpoint
513
+ stays the source of truth: `read` returns every reply not yet delivered.
514
+ - On a normal gateway exit the plugin stops the listener. If the gateway is killed,
515
+ the listener it started keeps its lease until its next hint fails to write.
516
+ The next gateway stops that orphan only if it is the recorded listener and its
517
+ parent is gone. Otherwise it retries with backoff until the lease is free. A
518
+ dead owner's lease expires after about 70 seconds; a live orphan releases it
519
+ when its next hint fails to write.
520
+ - State (the recorded listener, the observed delivered checkpoint and the wake
521
+ records) and the
522
+ listener's stderr live under `<Hermes home>/plugin-data/borg-representative-push/`.
523
+ The plugin sets that directory to mode 0700, refuses it if it is a symbolic link,
524
+ creates its files with mode 0600, and never reads or writes through a symbolic
525
+ link planted there.
526
+
418
527
  ## Recovery
419
528
 
420
529
  Run `borg` with the Node installation that owns the global `borgmcp` install;