borgee-hermes-plugin 0.1.3__tar.gz
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.
- borgee_hermes_plugin-0.1.3/LICENSE +21 -0
- borgee_hermes_plugin-0.1.3/MANIFEST.in +1 -0
- borgee_hermes_plugin-0.1.3/PKG-INFO +210 -0
- borgee_hermes_plugin-0.1.3/README.md +184 -0
- borgee_hermes_plugin-0.1.3/plugin.yaml +10 -0
- borgee_hermes_plugin-0.1.3/pyproject.toml +78 -0
- borgee_hermes_plugin-0.1.3/setup.cfg +4 -0
- borgee_hermes_plugin-0.1.3/src/borgee_hermes/__init__.py +44 -0
- borgee_hermes_plugin-0.1.3/src/borgee_hermes/adapter.py +1140 -0
- borgee_hermes_plugin-0.1.3/src/borgee_hermes/bridge.py +113 -0
- borgee_hermes_plugin-0.1.3/src/borgee_hermes/cli.py +114 -0
- borgee_hermes_plugin-0.1.3/src/borgee_hermes/config.py +120 -0
- borgee_hermes_plugin-0.1.3/src/borgee_hermes/inbox.py +1078 -0
- borgee_hermes_plugin-0.1.3/src/borgee_hermes/locks.py +115 -0
- borgee_hermes_plugin-0.1.3/src/borgee_hermes/py.typed +1 -0
- borgee_hermes_plugin-0.1.3/src/borgee_hermes/tools.py +640 -0
- borgee_hermes_plugin-0.1.3/src/borgee_hermes_plugin.egg-info/PKG-INFO +210 -0
- borgee_hermes_plugin-0.1.3/src/borgee_hermes_plugin.egg-info/SOURCES.txt +23 -0
- borgee_hermes_plugin-0.1.3/src/borgee_hermes_plugin.egg-info/dependency_links.txt +1 -0
- borgee_hermes_plugin-0.1.3/src/borgee_hermes_plugin.egg-info/entry_points.txt +5 -0
- borgee_hermes_plugin-0.1.3/src/borgee_hermes_plugin.egg-info/requires.txt +12 -0
- borgee_hermes_plugin-0.1.3/src/borgee_hermes_plugin.egg-info/top_level.txt +1 -0
- borgee_hermes_plugin-0.1.3/tests/test_adapter.py +1145 -0
- borgee_hermes_plugin-0.1.3/tests/test_config_and_locks.py +408 -0
- borgee_hermes_plugin-0.1.3/tests/test_inbox.py +444 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Borgee contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
include plugin.yaml
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: borgee-hermes-plugin
|
|
3
|
+
Version: 0.1.3
|
|
4
|
+
Summary: Borgee messaging platform adapter for Hermes Agent
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Classifier: Development Status :: 3 - Alpha
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
11
|
+
Requires-Python: <3.14,>=3.11
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: borgee-plugin-sdk==0.1.2
|
|
15
|
+
Requires-Dist: hermes-agent==0.18.2
|
|
16
|
+
Requires-Dist: pyyaml>=6.0
|
|
17
|
+
Provides-Extra: test
|
|
18
|
+
Requires-Dist: build==1.2.2.post1; extra == "test"
|
|
19
|
+
Requires-Dist: mypy==1.17.0; extra == "test"
|
|
20
|
+
Requires-Dist: pytest==9.0.3; extra == "test"
|
|
21
|
+
Requires-Dist: pytest-asyncio==1.3.0; extra == "test"
|
|
22
|
+
Requires-Dist: pytest-timeout==2.4.0; extra == "test"
|
|
23
|
+
Requires-Dist: ruff==0.12.4; extra == "test"
|
|
24
|
+
Requires-Dist: twine==6.1.0; extra == "test"
|
|
25
|
+
Dynamic: license-file
|
|
26
|
+
|
|
27
|
+
# Borgee platform for Hermes Agent
|
|
28
|
+
|
|
29
|
+
`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.
|
|
30
|
+
|
|
31
|
+
## Compatibility
|
|
32
|
+
|
|
33
|
+
| Component | Supported version |
|
|
34
|
+
| --- | --- |
|
|
35
|
+
| Python | 3.11, 3.12, or 3.13 |
|
|
36
|
+
| Hermes distribution | `hermes-agent==0.18.2` |
|
|
37
|
+
| Hermes release source | `9de9c25f620ff7f1ce0fd5457d596052d5159596` |
|
|
38
|
+
| Borgee Python SDK | `borgee-plugin-sdk==0.1.2` |
|
|
39
|
+
|
|
40
|
+
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.
|
|
41
|
+
|
|
42
|
+
## Install
|
|
43
|
+
|
|
44
|
+
Build and install the local SDK first:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
python -m build --wheel packages/sdk/plugin-py
|
|
48
|
+
python -m pip install packages/sdk/plugin-py/dist/borgee_plugin_sdk-0.1.2-*.whl
|
|
49
|
+
python -m pip install packages/plugins/hermes
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Enable the entry-point plugin and configure the platform in the active Hermes
|
|
53
|
+
profile's `config.yaml`:
|
|
54
|
+
|
|
55
|
+
```yaml
|
|
56
|
+
plugins:
|
|
57
|
+
enabled:
|
|
58
|
+
- borgee
|
|
59
|
+
|
|
60
|
+
group_sessions_per_user: false
|
|
61
|
+
|
|
62
|
+
display:
|
|
63
|
+
busy_input_mode: queue
|
|
64
|
+
|
|
65
|
+
streaming:
|
|
66
|
+
enabled: true
|
|
67
|
+
|
|
68
|
+
borgee:
|
|
69
|
+
enabled: true
|
|
70
|
+
base_url: https://chat.example.com
|
|
71
|
+
# Set true only for an explicit http://127.0.0.1 or http://localhost server.
|
|
72
|
+
allow_insecure_loopback: false
|
|
73
|
+
max_concurrent_dispatches: 4
|
|
74
|
+
drain_timeout_seconds: 30
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`streaming.enabled` is a global Hermes setting — it applies to every
|
|
78
|
+
connected platform by default. To disable streaming for a specific platform,
|
|
79
|
+
set `platforms.<name>.display.streaming.enabled: false` in that platform's
|
|
80
|
+
config block.
|
|
81
|
+
|
|
82
|
+
Put the agent API key in the active profile's `.env`:
|
|
83
|
+
|
|
84
|
+
```dotenv
|
|
85
|
+
BORGEE_API_KEY=bgr_replace_with_the_agent_key
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
`base_url` is non-secret and belongs in `config.yaml`. The API key is accepted
|
|
89
|
+
only through `BORGEE_API_KEY`; values in `config.yaml` are rejected. Hermes
|
|
90
|
+
loads each profile's own `.env`, so the same variable name remains
|
|
91
|
+
profile-isolated without inventing profile-suffixed secret names.
|
|
92
|
+
|
|
93
|
+
Run one Hermes process and one Borgee socket per `HERMES_HOME`. Leave
|
|
94
|
+
`gateway.multiplex_profiles` disabled. The adapter takes two Hermes scoped
|
|
95
|
+
interprocess locks in a fixed order: a canonical real-`HERMES_HOME` hash, then
|
|
96
|
+
the Borgee origin plus credential fingerprint. It releases credential then
|
|
97
|
+
profile. An owner-token registry applies the same exclusions within one process
|
|
98
|
+
where Hermes locks are PID-reentrant.
|
|
99
|
+
|
|
100
|
+
## Delivery and session behavior
|
|
101
|
+
|
|
102
|
+
Borgee authenticates the BPP socket as one agent and applies channel membership
|
|
103
|
+
and require-mention policy before sending an event. The adapter therefore sets
|
|
104
|
+
Hermes `authorization_is_upstream=True`; it does not maintain a second local
|
|
105
|
+
allowlist.
|
|
106
|
+
|
|
107
|
+
Hermes DMs use `agent:main:borgee:dm:<channel-id>`. Channel conversations use
|
|
108
|
+
`agent:main:borgee:channel:<channel-id>`. The required top-level
|
|
109
|
+
`group_sessions_per_user: false` makes two authors in one channel share that
|
|
110
|
+
channel session while preserving their names on each inbound event.
|
|
111
|
+
|
|
112
|
+
The plugin checkpoints BPP only after it commits the complete receipt to
|
|
113
|
+
`$HERMES_HOME/plugins/borgee/inbox.sqlite3`. The SDK cursor advances after that
|
|
114
|
+
commit, while pending work remains in the durable inbox. A scheduler claims a
|
|
115
|
+
receipt in SQLite before calling Hermes, preserves FIFO within each ordinary
|
|
116
|
+
session, and permits bounded concurrency across sessions. Ingress stores an
|
|
117
|
+
`ordinary`, `control`, or `urgent_stop` dispatch class using Hermes' real
|
|
118
|
+
command, plugin-command, and clarify state before the receipt commit. If that
|
|
119
|
+
classification fails, the delivery is failed without a checkpoint.
|
|
120
|
+
|
|
121
|
+
SQLite open, close, and every submitted actor operation settle before
|
|
122
|
+
cancellation is rethrown. Cancelling open automatically closes any connection
|
|
123
|
+
created in the worker, shuts down that executor, and makes the actor
|
|
124
|
+
non-reusable. Close is idempotent and always finishes both connection and worker
|
|
125
|
+
shutdown, including when concurrent lifecycle callers are cancelled.
|
|
126
|
+
|
|
127
|
+
Hermes slash commands and pending clarify answers bypass an active ordinary
|
|
128
|
+
turn. `/queue <payload>` and `/q <payload>` are different: the adapter removes
|
|
129
|
+
the prefix in the receipt transaction and keeps the payload as a durable
|
|
130
|
+
ordinary turn until the session becomes idle. A bare `/queue` follows Hermes'
|
|
131
|
+
normal inline usage response.
|
|
132
|
+
|
|
133
|
+
The scheduler can find a persisted control past an arbitrarily deep ordinary
|
|
134
|
+
backlog, then rechecks the live Hermes bypass state before dispatch. If the
|
|
135
|
+
session became idle during that check, the receipt must wait for the ordinary
|
|
136
|
+
FIFO head. Ordinary and control work have independent bounded lanes, and
|
|
137
|
+
`/stop` has a separately reserved urgent slot. A timed-out control or urgent
|
|
138
|
+
execution releases its lane slot, suspends the matching Hermes session when it
|
|
139
|
+
exists, and quarantines the claimed receipt for explicit remediation.
|
|
140
|
+
|
|
141
|
+
## Crash recovery
|
|
142
|
+
|
|
143
|
+
A receipt moves through this state machine:
|
|
144
|
+
|
|
145
|
+
```text
|
|
146
|
+
pending -> processing -> completed | failed
|
|
147
|
+
\-> in_doubt -> quarantined
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
The `pending -> processing` claim and its unique token commit before Hermes
|
|
151
|
+
runs. A crash after the claim may have performed tools or sent side effects, so
|
|
152
|
+
restart never replays that receipt automatically. Startup marks leftover
|
|
153
|
+
`processing` rows `in_doubt`, suspends every matching Hermes session before the
|
|
154
|
+
BPP SDK connects, and quarantines the rows. A missing Hermes session needs no
|
|
155
|
+
suspension because the next turn cannot continue its old tool tail, but its
|
|
156
|
+
receipt is still quarantined.
|
|
157
|
+
|
|
158
|
+
The host can expose the adapter's remediation library method to an operator:
|
|
159
|
+
|
|
160
|
+
```python
|
|
161
|
+
await adapter.remediate_receipt(ingest_seq, "discard")
|
|
162
|
+
|
|
163
|
+
await adapter.remediate_receipt(
|
|
164
|
+
ingest_seq,
|
|
165
|
+
"requeue",
|
|
166
|
+
confirm_duplicate_side_effect_risk=True,
|
|
167
|
+
)
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Both actions reset an existing Hermes session first. `discard` records an
|
|
171
|
+
operator decision and never runs the message. `requeue` requires explicit
|
|
172
|
+
confirmation that tools, provider requests, or outbound effects might occur a
|
|
173
|
+
second time. There is no automatic requeue path.
|
|
174
|
+
|
|
175
|
+
During shutdown, the adapter stops admission, drains Hermes processing for the
|
|
176
|
+
configured bound, and then closes the BPP client, inbox, and both ownership
|
|
177
|
+
locks. Work that does not finish by the bound becomes `in_doubt` for the same
|
|
178
|
+
startup quarantine path.
|
|
179
|
+
|
|
180
|
+
A quarantined receipt blocks later ordinary turns only on its own Hermes
|
|
181
|
+
session lane until the operator discards or explicitly requeues it. Other
|
|
182
|
+
sessions remain schedulable; active session keys are excluded before the
|
|
183
|
+
database query applies its global candidate limit.
|
|
184
|
+
|
|
185
|
+
## Outbound behavior
|
|
186
|
+
|
|
187
|
+
Hermes `send`, `edit_message(finalize=...)`, and `delete_message` map to BPP
|
|
188
|
+
semantic actions. Edit-based Hermes streaming creates a preview with `send`,
|
|
189
|
+
updates it with `edit_message`, and accepts the final `finalize=True` call.
|
|
190
|
+
Typing is a no-op because BPP task lifecycle frames already report real work;
|
|
191
|
+
the adapter does not invent a typing signal.
|
|
192
|
+
|
|
193
|
+
Every outbound message ID is persisted for echo filtering. The resolved Borgee
|
|
194
|
+
agent ID is also filtered, which closes the race where the server broadcasts an
|
|
195
|
+
agent-authored message before the outbound ID transaction completes.
|
|
196
|
+
|
|
197
|
+
Agent config updates are acknowledged as `rejected` with a legal BPP reason.
|
|
198
|
+
The MVP does not pretend to apply Borgee model, prompt, or tool changes to a
|
|
199
|
+
running Hermes conversation. Disabling the Borgee agent stops new intake and
|
|
200
|
+
the connection; BPP has no remote cancellation frame, so the plugin does not
|
|
201
|
+
claim that disable cancels a turn already executing inside Hermes. `/stop`
|
|
202
|
+
remains Hermes' real current-turn cancellation command.
|
|
203
|
+
|
|
204
|
+
## MVP limits
|
|
205
|
+
|
|
206
|
+
The adapter intentionally excludes attachments, artifacts, history reads,
|
|
207
|
+
server `read_file` requests, REST side channels, multi-agent routing, and
|
|
208
|
+
multi-profile multiplexing. It handles only text messages and mentions.
|
|
209
|
+
Edited, deleted, and reaction events receive durable terminal receipts but do
|
|
210
|
+
not start inference.
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# Borgee platform for Hermes Agent
|
|
2
|
+
|
|
3
|
+
`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.
|
|
4
|
+
|
|
5
|
+
## Compatibility
|
|
6
|
+
|
|
7
|
+
| Component | Supported version |
|
|
8
|
+
| --- | --- |
|
|
9
|
+
| Python | 3.11, 3.12, or 3.13 |
|
|
10
|
+
| Hermes distribution | `hermes-agent==0.18.2` |
|
|
11
|
+
| Hermes release source | `9de9c25f620ff7f1ce0fd5457d596052d5159596` |
|
|
12
|
+
| Borgee Python SDK | `borgee-plugin-sdk==0.1.2` |
|
|
13
|
+
|
|
14
|
+
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.
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
Build and install the local SDK first:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
python -m build --wheel packages/sdk/plugin-py
|
|
22
|
+
python -m pip install packages/sdk/plugin-py/dist/borgee_plugin_sdk-0.1.2-*.whl
|
|
23
|
+
python -m pip install packages/plugins/hermes
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Enable the entry-point plugin and configure the platform in the active Hermes
|
|
27
|
+
profile's `config.yaml`:
|
|
28
|
+
|
|
29
|
+
```yaml
|
|
30
|
+
plugins:
|
|
31
|
+
enabled:
|
|
32
|
+
- borgee
|
|
33
|
+
|
|
34
|
+
group_sessions_per_user: false
|
|
35
|
+
|
|
36
|
+
display:
|
|
37
|
+
busy_input_mode: queue
|
|
38
|
+
|
|
39
|
+
streaming:
|
|
40
|
+
enabled: true
|
|
41
|
+
|
|
42
|
+
borgee:
|
|
43
|
+
enabled: true
|
|
44
|
+
base_url: https://chat.example.com
|
|
45
|
+
# Set true only for an explicit http://127.0.0.1 or http://localhost server.
|
|
46
|
+
allow_insecure_loopback: false
|
|
47
|
+
max_concurrent_dispatches: 4
|
|
48
|
+
drain_timeout_seconds: 30
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
`streaming.enabled` is a global Hermes setting — it applies to every
|
|
52
|
+
connected platform by default. To disable streaming for a specific platform,
|
|
53
|
+
set `platforms.<name>.display.streaming.enabled: false` in that platform's
|
|
54
|
+
config block.
|
|
55
|
+
|
|
56
|
+
Put the agent API key in the active profile's `.env`:
|
|
57
|
+
|
|
58
|
+
```dotenv
|
|
59
|
+
BORGEE_API_KEY=bgr_replace_with_the_agent_key
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`base_url` is non-secret and belongs in `config.yaml`. The API key is accepted
|
|
63
|
+
only through `BORGEE_API_KEY`; values in `config.yaml` are rejected. Hermes
|
|
64
|
+
loads each profile's own `.env`, so the same variable name remains
|
|
65
|
+
profile-isolated without inventing profile-suffixed secret names.
|
|
66
|
+
|
|
67
|
+
Run one Hermes process and one Borgee socket per `HERMES_HOME`. Leave
|
|
68
|
+
`gateway.multiplex_profiles` disabled. The adapter takes two Hermes scoped
|
|
69
|
+
interprocess locks in a fixed order: a canonical real-`HERMES_HOME` hash, then
|
|
70
|
+
the Borgee origin plus credential fingerprint. It releases credential then
|
|
71
|
+
profile. An owner-token registry applies the same exclusions within one process
|
|
72
|
+
where Hermes locks are PID-reentrant.
|
|
73
|
+
|
|
74
|
+
## Delivery and session behavior
|
|
75
|
+
|
|
76
|
+
Borgee authenticates the BPP socket as one agent and applies channel membership
|
|
77
|
+
and require-mention policy before sending an event. The adapter therefore sets
|
|
78
|
+
Hermes `authorization_is_upstream=True`; it does not maintain a second local
|
|
79
|
+
allowlist.
|
|
80
|
+
|
|
81
|
+
Hermes DMs use `agent:main:borgee:dm:<channel-id>`. Channel conversations use
|
|
82
|
+
`agent:main:borgee:channel:<channel-id>`. The required top-level
|
|
83
|
+
`group_sessions_per_user: false` makes two authors in one channel share that
|
|
84
|
+
channel session while preserving their names on each inbound event.
|
|
85
|
+
|
|
86
|
+
The plugin checkpoints BPP only after it commits the complete receipt to
|
|
87
|
+
`$HERMES_HOME/plugins/borgee/inbox.sqlite3`. The SDK cursor advances after that
|
|
88
|
+
commit, while pending work remains in the durable inbox. A scheduler claims a
|
|
89
|
+
receipt in SQLite before calling Hermes, preserves FIFO within each ordinary
|
|
90
|
+
session, and permits bounded concurrency across sessions. Ingress stores an
|
|
91
|
+
`ordinary`, `control`, or `urgent_stop` dispatch class using Hermes' real
|
|
92
|
+
command, plugin-command, and clarify state before the receipt commit. If that
|
|
93
|
+
classification fails, the delivery is failed without a checkpoint.
|
|
94
|
+
|
|
95
|
+
SQLite open, close, and every submitted actor operation settle before
|
|
96
|
+
cancellation is rethrown. Cancelling open automatically closes any connection
|
|
97
|
+
created in the worker, shuts down that executor, and makes the actor
|
|
98
|
+
non-reusable. Close is idempotent and always finishes both connection and worker
|
|
99
|
+
shutdown, including when concurrent lifecycle callers are cancelled.
|
|
100
|
+
|
|
101
|
+
Hermes slash commands and pending clarify answers bypass an active ordinary
|
|
102
|
+
turn. `/queue <payload>` and `/q <payload>` are different: the adapter removes
|
|
103
|
+
the prefix in the receipt transaction and keeps the payload as a durable
|
|
104
|
+
ordinary turn until the session becomes idle. A bare `/queue` follows Hermes'
|
|
105
|
+
normal inline usage response.
|
|
106
|
+
|
|
107
|
+
The scheduler can find a persisted control past an arbitrarily deep ordinary
|
|
108
|
+
backlog, then rechecks the live Hermes bypass state before dispatch. If the
|
|
109
|
+
session became idle during that check, the receipt must wait for the ordinary
|
|
110
|
+
FIFO head. Ordinary and control work have independent bounded lanes, and
|
|
111
|
+
`/stop` has a separately reserved urgent slot. A timed-out control or urgent
|
|
112
|
+
execution releases its lane slot, suspends the matching Hermes session when it
|
|
113
|
+
exists, and quarantines the claimed receipt for explicit remediation.
|
|
114
|
+
|
|
115
|
+
## Crash recovery
|
|
116
|
+
|
|
117
|
+
A receipt moves through this state machine:
|
|
118
|
+
|
|
119
|
+
```text
|
|
120
|
+
pending -> processing -> completed | failed
|
|
121
|
+
\-> in_doubt -> quarantined
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
The `pending -> processing` claim and its unique token commit before Hermes
|
|
125
|
+
runs. A crash after the claim may have performed tools or sent side effects, so
|
|
126
|
+
restart never replays that receipt automatically. Startup marks leftover
|
|
127
|
+
`processing` rows `in_doubt`, suspends every matching Hermes session before the
|
|
128
|
+
BPP SDK connects, and quarantines the rows. A missing Hermes session needs no
|
|
129
|
+
suspension because the next turn cannot continue its old tool tail, but its
|
|
130
|
+
receipt is still quarantined.
|
|
131
|
+
|
|
132
|
+
The host can expose the adapter's remediation library method to an operator:
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
await adapter.remediate_receipt(ingest_seq, "discard")
|
|
136
|
+
|
|
137
|
+
await adapter.remediate_receipt(
|
|
138
|
+
ingest_seq,
|
|
139
|
+
"requeue",
|
|
140
|
+
confirm_duplicate_side_effect_risk=True,
|
|
141
|
+
)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Both actions reset an existing Hermes session first. `discard` records an
|
|
145
|
+
operator decision and never runs the message. `requeue` requires explicit
|
|
146
|
+
confirmation that tools, provider requests, or outbound effects might occur a
|
|
147
|
+
second time. There is no automatic requeue path.
|
|
148
|
+
|
|
149
|
+
During shutdown, the adapter stops admission, drains Hermes processing for the
|
|
150
|
+
configured bound, and then closes the BPP client, inbox, and both ownership
|
|
151
|
+
locks. Work that does not finish by the bound becomes `in_doubt` for the same
|
|
152
|
+
startup quarantine path.
|
|
153
|
+
|
|
154
|
+
A quarantined receipt blocks later ordinary turns only on its own Hermes
|
|
155
|
+
session lane until the operator discards or explicitly requeues it. Other
|
|
156
|
+
sessions remain schedulable; active session keys are excluded before the
|
|
157
|
+
database query applies its global candidate limit.
|
|
158
|
+
|
|
159
|
+
## Outbound behavior
|
|
160
|
+
|
|
161
|
+
Hermes `send`, `edit_message(finalize=...)`, and `delete_message` map to BPP
|
|
162
|
+
semantic actions. Edit-based Hermes streaming creates a preview with `send`,
|
|
163
|
+
updates it with `edit_message`, and accepts the final `finalize=True` call.
|
|
164
|
+
Typing is a no-op because BPP task lifecycle frames already report real work;
|
|
165
|
+
the adapter does not invent a typing signal.
|
|
166
|
+
|
|
167
|
+
Every outbound message ID is persisted for echo filtering. The resolved Borgee
|
|
168
|
+
agent ID is also filtered, which closes the race where the server broadcasts an
|
|
169
|
+
agent-authored message before the outbound ID transaction completes.
|
|
170
|
+
|
|
171
|
+
Agent config updates are acknowledged as `rejected` with a legal BPP reason.
|
|
172
|
+
The MVP does not pretend to apply Borgee model, prompt, or tool changes to a
|
|
173
|
+
running Hermes conversation. Disabling the Borgee agent stops new intake and
|
|
174
|
+
the connection; BPP has no remote cancellation frame, so the plugin does not
|
|
175
|
+
claim that disable cancels a turn already executing inside Hermes. `/stop`
|
|
176
|
+
remains Hermes' real current-turn cancellation command.
|
|
177
|
+
|
|
178
|
+
## MVP limits
|
|
179
|
+
|
|
180
|
+
The adapter intentionally excludes attachments, artifacts, history reads,
|
|
181
|
+
server `read_file` requests, REST side channels, multi-agent routing, and
|
|
182
|
+
multi-profile multiplexing. It handles only text messages and mentions.
|
|
183
|
+
Edited, deleted, and reaction events receive durable terminal receipts but do
|
|
184
|
+
not start inference.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77.0,<83", "wheel==0.46.2"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "borgee-hermes-plugin"
|
|
7
|
+
version = "0.1.3"
|
|
8
|
+
description = "Borgee messaging platform adapter for Hermes Agent"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
requires-python = ">=3.11,<3.14"
|
|
13
|
+
dependencies = [
|
|
14
|
+
"borgee-plugin-sdk==0.1.2",
|
|
15
|
+
"hermes-agent==0.18.2",
|
|
16
|
+
"pyyaml>=6.0",
|
|
17
|
+
]
|
|
18
|
+
classifiers = [
|
|
19
|
+
"Development Status :: 3 - Alpha",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Programming Language :: Python :: 3.13",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[project.entry-points."hermes_agent.plugins"]
|
|
27
|
+
borgee = "borgee_hermes"
|
|
28
|
+
|
|
29
|
+
[project.scripts]
|
|
30
|
+
borgee-hermes = "borgee_hermes.cli:main"
|
|
31
|
+
|
|
32
|
+
[project.optional-dependencies]
|
|
33
|
+
test = [
|
|
34
|
+
"build==1.2.2.post1",
|
|
35
|
+
"mypy==1.17.0",
|
|
36
|
+
"pytest==9.0.3",
|
|
37
|
+
"pytest-asyncio==1.3.0",
|
|
38
|
+
"pytest-timeout==2.4.0",
|
|
39
|
+
"ruff==0.12.4",
|
|
40
|
+
"twine==6.1.0",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
[tool.setuptools]
|
|
44
|
+
package-dir = {"" = "src"}
|
|
45
|
+
include-package-data = true
|
|
46
|
+
|
|
47
|
+
[tool.setuptools.packages.find]
|
|
48
|
+
where = ["src"]
|
|
49
|
+
|
|
50
|
+
[tool.setuptools.package-data]
|
|
51
|
+
borgee_hermes = ["py.typed"]
|
|
52
|
+
|
|
53
|
+
[tool.setuptools.data-files]
|
|
54
|
+
"share/borgee-hermes-plugin" = ["plugin.yaml"]
|
|
55
|
+
|
|
56
|
+
[tool.pytest.ini_options]
|
|
57
|
+
addopts = "--strict-markers --timeout=30 --timeout-method=signal"
|
|
58
|
+
asyncio_mode = "auto"
|
|
59
|
+
testpaths = ["tests"]
|
|
60
|
+
markers = [
|
|
61
|
+
"integration: tests against the installed Hermes 0.18.2 runtime",
|
|
62
|
+
"e2e: cross-process tests using the real Go server and Hermes gateway",
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
[tool.ruff]
|
|
66
|
+
target-version = "py311"
|
|
67
|
+
line-length = 110
|
|
68
|
+
|
|
69
|
+
[tool.ruff.lint]
|
|
70
|
+
select = ["E", "F", "I", "UP", "B", "ASYNC", "RUF"]
|
|
71
|
+
|
|
72
|
+
[tool.mypy]
|
|
73
|
+
python_version = "3.11"
|
|
74
|
+
strict = true
|
|
75
|
+
warn_unreachable = true
|
|
76
|
+
packages = ["borgee_hermes"]
|
|
77
|
+
ignore_missing_imports = true
|
|
78
|
+
disallow_subclassing_any = false
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Borgee platform plugin for Hermes Agent."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from .adapter import BorgeePlatformAdapter
|
|
8
|
+
from .config import apply_yaml_config, check_requirements, validate_config
|
|
9
|
+
from .tools import register_borgee_tools, set_active_adapter
|
|
10
|
+
|
|
11
|
+
__version__ = "0.1.3"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _adapter_factory(config: Any) -> BorgeePlatformAdapter:
|
|
15
|
+
"""Create the adapter and expose its BPP client to the tool handlers."""
|
|
16
|
+
adapter = BorgeePlatformAdapter(config)
|
|
17
|
+
set_active_adapter(adapter)
|
|
18
|
+
return adapter
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def register(ctx: Any) -> None:
|
|
22
|
+
"""Register the Borgee messaging platform and tools with Hermes."""
|
|
23
|
+
ctx.register_platform(
|
|
24
|
+
name="borgee",
|
|
25
|
+
label="Borgee",
|
|
26
|
+
adapter_factory=_adapter_factory,
|
|
27
|
+
check_fn=check_requirements,
|
|
28
|
+
validate_config=validate_config,
|
|
29
|
+
required_env=["BORGEE_API_KEY"],
|
|
30
|
+
apply_yaml_config_fn=apply_yaml_config,
|
|
31
|
+
max_message_length=4096,
|
|
32
|
+
pii_safe=True,
|
|
33
|
+
allow_update_command=False,
|
|
34
|
+
platform_hint=(
|
|
35
|
+
"You are chatting through Borgee. Channel conversations are shared "
|
|
36
|
+
"with all channel members, so attribute participants by name."
|
|
37
|
+
),
|
|
38
|
+
emoji="🟣",
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
register_borgee_tools(ctx)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
__all__ = ["BorgeePlatformAdapter", "__version__", "register"]
|