Metadata-Version: 2.4
Name: borgee-hermes-plugin
Version: 0.1.3
Summary: Borgee messaging platform adapter for Hermes Agent
License-Expression: MIT
Classifier: Development Status :: 3 - Alpha
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Requires-Python: <3.14,>=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: borgee-plugin-sdk==0.1.2
Requires-Dist: hermes-agent==0.18.2
Requires-Dist: pyyaml>=6.0
Provides-Extra: test
Requires-Dist: build==1.2.2.post1; extra == "test"
Requires-Dist: mypy==1.17.0; extra == "test"
Requires-Dist: pytest==9.0.3; extra == "test"
Requires-Dist: pytest-asyncio==1.3.0; extra == "test"
Requires-Dist: pytest-timeout==2.4.0; extra == "test"
Requires-Dist: ruff==0.12.4; extra == "test"
Requires-Dist: twine==6.1.0; extra == "test"
Dynamic: license-file

# Borgee platform for Hermes Agent

`borgee-hermes-plugin` connects one Hermes Agent 0.18.2 gateway profile to one Borgee agent over BPP. It depends on `borgee-plugin-sdk==0.1.2`; for local builds, install the SDK wheel before the plugin.

## Compatibility

| Component | Supported version |
| --- | --- |
| Python | 3.11, 3.12, or 3.13 |
| Hermes distribution | `hermes-agent==0.18.2` |
| Hermes release source | `9de9c25f620ff7f1ce0fd5457d596052d5159596` |
| Borgee Python SDK | `borgee-plugin-sdk==0.1.2` |

This package and the SDK are licensed under the MIT License. The repository provides a manual workflow for publishing them to TestPyPI and PyPI; this documentation does not imply that a registry release has occurred.

## Install

Build and install the local SDK first:

```bash
python -m build --wheel packages/sdk/plugin-py
python -m pip install packages/sdk/plugin-py/dist/borgee_plugin_sdk-0.1.2-*.whl
python -m pip install packages/plugins/hermes
```

Enable the entry-point plugin and configure the platform in the active Hermes
profile's `config.yaml`:

```yaml
plugins:
  enabled:
    - borgee

group_sessions_per_user: false

display:
  busy_input_mode: queue

streaming:
  enabled: true

borgee:
  enabled: true
  base_url: https://chat.example.com
  # Set true only for an explicit http://127.0.0.1 or http://localhost server.
  allow_insecure_loopback: false
  max_concurrent_dispatches: 4
  drain_timeout_seconds: 30
```

`streaming.enabled` is a global Hermes setting — it applies to every
connected platform by default. To disable streaming for a specific platform,
set `platforms.<name>.display.streaming.enabled: false` in that platform's
config block.

Put the agent API key in the active profile's `.env`:

```dotenv
BORGEE_API_KEY=bgr_replace_with_the_agent_key
```

`base_url` is non-secret and belongs in `config.yaml`. The API key is accepted
only through `BORGEE_API_KEY`; values in `config.yaml` are rejected. Hermes
loads each profile's own `.env`, so the same variable name remains
profile-isolated without inventing profile-suffixed secret names.

Run one Hermes process and one Borgee socket per `HERMES_HOME`. Leave
`gateway.multiplex_profiles` disabled. The adapter takes two Hermes scoped
interprocess locks in a fixed order: a canonical real-`HERMES_HOME` hash, then
the Borgee origin plus credential fingerprint. It releases credential then
profile. An owner-token registry applies the same exclusions within one process
where Hermes locks are PID-reentrant.

## Delivery and session behavior

Borgee authenticates the BPP socket as one agent and applies channel membership
and require-mention policy before sending an event. The adapter therefore sets
Hermes `authorization_is_upstream=True`; it does not maintain a second local
allowlist.

Hermes DMs use `agent:main:borgee:dm:<channel-id>`. Channel conversations use
`agent:main:borgee:channel:<channel-id>`. The required top-level
`group_sessions_per_user: false` makes two authors in one channel share that
channel session while preserving their names on each inbound event.

The plugin checkpoints BPP only after it commits the complete receipt to
`$HERMES_HOME/plugins/borgee/inbox.sqlite3`. The SDK cursor advances after that
commit, while pending work remains in the durable inbox. A scheduler claims a
receipt in SQLite before calling Hermes, preserves FIFO within each ordinary
session, and permits bounded concurrency across sessions. Ingress stores an
`ordinary`, `control`, or `urgent_stop` dispatch class using Hermes' real
command, plugin-command, and clarify state before the receipt commit. If that
classification fails, the delivery is failed without a checkpoint.

SQLite open, close, and every submitted actor operation settle before
cancellation is rethrown. Cancelling open automatically closes any connection
created in the worker, shuts down that executor, and makes the actor
non-reusable. Close is idempotent and always finishes both connection and worker
shutdown, including when concurrent lifecycle callers are cancelled.

Hermes slash commands and pending clarify answers bypass an active ordinary
turn. `/queue <payload>` and `/q <payload>` are different: the adapter removes
the prefix in the receipt transaction and keeps the payload as a durable
ordinary turn until the session becomes idle. A bare `/queue` follows Hermes'
normal inline usage response.

The scheduler can find a persisted control past an arbitrarily deep ordinary
backlog, then rechecks the live Hermes bypass state before dispatch. If the
session became idle during that check, the receipt must wait for the ordinary
FIFO head. Ordinary and control work have independent bounded lanes, and
`/stop` has a separately reserved urgent slot. A timed-out control or urgent
execution releases its lane slot, suspends the matching Hermes session when it
exists, and quarantines the claimed receipt for explicit remediation.

## Crash recovery

A receipt moves through this state machine:

```text
pending -> processing -> completed | failed
                    \-> in_doubt -> quarantined
```

The `pending -> processing` claim and its unique token commit before Hermes
runs. A crash after the claim may have performed tools or sent side effects, so
restart never replays that receipt automatically. Startup marks leftover
`processing` rows `in_doubt`, suspends every matching Hermes session before the
BPP SDK connects, and quarantines the rows. A missing Hermes session needs no
suspension because the next turn cannot continue its old tool tail, but its
receipt is still quarantined.

The host can expose the adapter's remediation library method to an operator:

```python
await adapter.remediate_receipt(ingest_seq, "discard")

await adapter.remediate_receipt(
    ingest_seq,
    "requeue",
    confirm_duplicate_side_effect_risk=True,
)
```

Both actions reset an existing Hermes session first. `discard` records an
operator decision and never runs the message. `requeue` requires explicit
confirmation that tools, provider requests, or outbound effects might occur a
second time. There is no automatic requeue path.

During shutdown, the adapter stops admission, drains Hermes processing for the
configured bound, and then closes the BPP client, inbox, and both ownership
locks. Work that does not finish by the bound becomes `in_doubt` for the same
startup quarantine path.

A quarantined receipt blocks later ordinary turns only on its own Hermes
session lane until the operator discards or explicitly requeues it. Other
sessions remain schedulable; active session keys are excluded before the
database query applies its global candidate limit.

## Outbound behavior

Hermes `send`, `edit_message(finalize=...)`, and `delete_message` map to BPP
semantic actions. Edit-based Hermes streaming creates a preview with `send`,
updates it with `edit_message`, and accepts the final `finalize=True` call.
Typing is a no-op because BPP task lifecycle frames already report real work;
the adapter does not invent a typing signal.

Every outbound message ID is persisted for echo filtering. The resolved Borgee
agent ID is also filtered, which closes the race where the server broadcasts an
agent-authored message before the outbound ID transaction completes.

Agent config updates are acknowledged as `rejected` with a legal BPP reason.
The MVP does not pretend to apply Borgee model, prompt, or tool changes to a
running Hermes conversation. Disabling the Borgee agent stops new intake and
the connection; BPP has no remote cancellation frame, so the plugin does not
claim that disable cancels a turn already executing inside Hermes. `/stop`
remains Hermes' real current-turn cancellation command.

## MVP limits

The adapter intentionally excludes attachments, artifacts, history reads,
server `read_file` requests, REST side channels, multi-agent routing, and
multi-profile multiplexing. It handles only text messages and mentions.
Edited, deleted, and reaction events receive durable terminal receipts but do
not start inference.
