lyriel-hermes 0.1.0__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.
- lyriel_hermes-0.1.0/PKG-INFO +233 -0
- lyriel_hermes-0.1.0/README.md +211 -0
- lyriel_hermes-0.1.0/lyriel_hermes/__init__.py +149 -0
- lyriel_hermes-0.1.0/lyriel_hermes/api.py +204 -0
- lyriel_hermes-0.1.0/lyriel_hermes/cli.py +138 -0
- lyriel_hermes-0.1.0/lyriel_hermes/inbox.py +151 -0
- lyriel_hermes-0.1.0/lyriel_hermes/pending.py +178 -0
- lyriel_hermes-0.1.0/lyriel_hermes/plugin.yaml +17 -0
- lyriel_hermes-0.1.0/lyriel_hermes/primer.py +32 -0
- lyriel_hermes-0.1.0/lyriel_hermes/surfacing.py +256 -0
- lyriel_hermes-0.1.0/lyriel_hermes/tools.py +532 -0
- lyriel_hermes-0.1.0/lyriel_hermes.egg-info/PKG-INFO +233 -0
- lyriel_hermes-0.1.0/lyriel_hermes.egg-info/SOURCES.txt +16 -0
- lyriel_hermes-0.1.0/lyriel_hermes.egg-info/dependency_links.txt +1 -0
- lyriel_hermes-0.1.0/lyriel_hermes.egg-info/entry_points.txt +2 -0
- lyriel_hermes-0.1.0/lyriel_hermes.egg-info/top_level.txt +1 -0
- lyriel_hermes-0.1.0/pyproject.toml +42 -0
- lyriel_hermes-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: lyriel-hermes
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Lyriel plugin for Hermes — native integration with the Lyriel substrate.
|
|
5
|
+
Author: Lyriel
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://lyriel.ai
|
|
8
|
+
Project-URL: Repository, https://github.com/CharlieKerfoot/agent-net
|
|
9
|
+
Project-URL: Documentation, https://lyriel.ai/docs/agents
|
|
10
|
+
Keywords: lyriel,hermes,plugin,agent,coordination
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: MacOS
|
|
15
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# Lyriel plugin for Hermes
|
|
24
|
+
|
|
25
|
+
Native Hermes integration for the Lyriel substrate. One bot, one chat, full
|
|
26
|
+
agent context. Replaces the standalone Telegram bridge with a plugin that
|
|
27
|
+
runs inside the Hermes process and pipes Lyriel asks into the user's
|
|
28
|
+
existing Hermes Telegram chat via the gateway's own send pipeline.
|
|
29
|
+
|
|
30
|
+
## What you get
|
|
31
|
+
|
|
32
|
+
- 🪶 Lyriel asks arrive in your active Hermes surface the instant
|
|
33
|
+
they're dispatched (long-poll loop on a daemon thread inside Hermes).
|
|
34
|
+
The plugin auto-detects which surface you're on:
|
|
35
|
+
- **CLI mode** (`hermes` in a terminal): the dispatch is injected as
|
|
36
|
+
the next message in your conversation via Hermes's `inject_message`
|
|
37
|
+
primitive. If the agent is mid-turn, it interrupts; if idle, the
|
|
38
|
+
surface message becomes the next "user" input that triggers the
|
|
39
|
+
agent.
|
|
40
|
+
- **Gateway mode** (Hermes connected to Telegram / Discord / Slack):
|
|
41
|
+
the plugin pushes through the gateway's existing platform adapter.
|
|
42
|
+
No second bot to configure.
|
|
43
|
+
- Two LLM tools — `lyriel_send_ask` and `lyriel_reply` — so the user
|
|
44
|
+
drives Lyriel in natural language. "Hermes, ping @noor about dinner."
|
|
45
|
+
"Reply yes thursday works."
|
|
46
|
+
- Full agent context: Hermes's memory, preferences, calendar tools, etc.
|
|
47
|
+
are all available when drafting replies to Lyriel asks.
|
|
48
|
+
|
|
49
|
+
## Install
|
|
50
|
+
|
|
51
|
+
### Published (recommended)
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pip install lyriel-hermes
|
|
55
|
+
lyriel-hermes install
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
The `install` subcommand symlinks the installed package into
|
|
59
|
+
`~/.hermes/plugins/lyriel/` so Hermes discovers it on next boot. Pass
|
|
60
|
+
`--copy` if your environment doesn't allow symlinks; `--target PATH`
|
|
61
|
+
to override the destination.
|
|
62
|
+
|
|
63
|
+
Subsequent `pip install --upgrade lyriel-hermes` updates the plugin
|
|
64
|
+
through the symlink — no need to re-run `lyriel-hermes install`.
|
|
65
|
+
|
|
66
|
+
### Development (from this checkout)
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# Editable install — pip points at lyriel_hermes/ in this repo.
|
|
70
|
+
pip install -e clients/hermes-plugin
|
|
71
|
+
lyriel-hermes install # symlinks the editable install into ~/.hermes/plugins/lyriel
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Or symlink directly without pip:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
ln -sfn "$(pwd)/clients/hermes-plugin/lyriel_hermes" ~/.hermes/plugins/lyriel
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Configure
|
|
81
|
+
|
|
82
|
+
1. **Enable the plugin in `~/.hermes/config.yaml`:**
|
|
83
|
+
```yaml
|
|
84
|
+
plugins:
|
|
85
|
+
enabled:
|
|
86
|
+
- lyriel
|
|
87
|
+
entries:
|
|
88
|
+
lyriel:
|
|
89
|
+
telegram_chat_id: "123456789" # your DM chat with the Hermes bot
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
If `gateway.telegram.home_chat_id` is already set, the plugin will fall
|
|
93
|
+
back to that; you can skip the `plugins.entries.lyriel` block in that case.
|
|
94
|
+
|
|
95
|
+
2. **Set the Lyriel API key in `~/.hermes/.env`** (or your shell):
|
|
96
|
+
```bash
|
|
97
|
+
LYRIEL_API_KEY=lyk_xxxxxxxxxxxxxxxx # from /me/agent setup
|
|
98
|
+
LYRIEL_BASE_URL=https://lyriel.ai # or http://localhost:5173 for dev,
|
|
99
|
+
# or your cloudflared tunnel URL
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
3. **Restart the Hermes gateway** so the plugin is loaded:
|
|
103
|
+
```bash
|
|
104
|
+
# however you run Hermes — systemd restart, supervisor restart, etc.
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
4. **Verify** by sending an ask to yourself or asking Hermes to ping
|
|
108
|
+
`@lyriel`. You should see a 🪶 message land in your Hermes Telegram
|
|
109
|
+
chat within a second.
|
|
110
|
+
|
|
111
|
+
## Configuration
|
|
112
|
+
|
|
113
|
+
| Setting | Where | Required | Default |
|
|
114
|
+
|---|---|---|---|
|
|
115
|
+
| `LYRIEL_API_KEY` | env / `.env` | yes | — |
|
|
116
|
+
| `LYRIEL_BASE_URL` | env / `.env` | no | `https://lyriel.ai` |
|
|
117
|
+
| `plugins.entries.lyriel.telegram_chat_id` | `~/.hermes/config.yaml` | yes (or fallback) | — |
|
|
118
|
+
| `gateway.telegram.home_chat_id` | `~/.hermes/config.yaml` | fallback for above | — |
|
|
119
|
+
|
|
120
|
+
If neither chat id is set, the plugin still claims dispatches from
|
|
121
|
+
Lyriel (so they don't pile up server-side) but won't surface them to
|
|
122
|
+
Telegram. The LLM tools still work, so the user can list pending asks
|
|
123
|
+
and reply via direct prompting if needed.
|
|
124
|
+
|
|
125
|
+
## How it works
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
┌────────────────────────────────────────────────────┐
|
|
129
|
+
│ Hermes process (always-on) │
|
|
130
|
+
│ │
|
|
131
|
+
register() │ ┌──────────────────┐ ┌──────────────────────┐ │
|
|
132
|
+
─────────▶ │ │ Inbox poll loop │ │ LLM tool handlers │ │
|
|
133
|
+
│ │ (daemon thread) │ │ lyriel_send_ask │ │
|
|
134
|
+
│ └────────┬─────────┘ │ lyriel_reply │ │
|
|
135
|
+
│ │ └──────────┬───────────┘ │
|
|
136
|
+
│ │ │ │
|
|
137
|
+
│ ▼ ▼ │
|
|
138
|
+
│ pending.py (ask_id → callback URL/token map) │
|
|
139
|
+
│ │
|
|
140
|
+
│ Telegram surfacing via │
|
|
141
|
+
│ tools.send_message_tool._send_to_platform │
|
|
142
|
+
└────────────────┬───────────────────────────────────┘
|
|
143
|
+
│
|
|
144
|
+
┌────────────────▼──────────────────┐
|
|
145
|
+
│ User's Hermes Telegram chat │
|
|
146
|
+
│ │
|
|
147
|
+
│ 🪶 Lyriel ask from @noor: │
|
|
148
|
+
│ "dinner thursday?" │
|
|
149
|
+
│ To reply, say: reply to Lyriel │
|
|
150
|
+
│ ask <id> with <your response> │
|
|
151
|
+
│ │
|
|
152
|
+
│ > yes thursday works │
|
|
153
|
+
│ │
|
|
154
|
+
│ (LLM calls lyriel_reply → │
|
|
155
|
+
│ plugin POSTs callback) │
|
|
156
|
+
└───────────────────────────────────┘
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Comparison with the standalone Telegram bridge
|
|
160
|
+
|
|
161
|
+
The standalone bridge (`clients/telegram-bridge/lyriel_bridge.py`) is a
|
|
162
|
+
runtime-agnostic reference client. It works for users who don't run
|
|
163
|
+
Hermes at all but produces a worse UX:
|
|
164
|
+
|
|
165
|
+
| | Bridge | This plugin |
|
|
166
|
+
|---|---|---|
|
|
167
|
+
| Bots in user's Telegram | 2 (Hermes + bridge) | 1 (Hermes) |
|
|
168
|
+
| LLM context on incoming asks | none — bridge has no LLM | full Hermes context, memory, tools |
|
|
169
|
+
| Reply path | type verbatim into bridge chat | natural-language drafting via Hermes |
|
|
170
|
+
| Outbound asks via natural language | no — manual curl | yes — LLM calls `lyriel_send_ask` |
|
|
171
|
+
| Setup | run a separate Python process + new bot | drop into ~/.hermes/plugins/ + config entry |
|
|
172
|
+
|
|
173
|
+
The plugin is the production answer. The bridge stays useful for users on
|
|
174
|
+
other runtimes (Claude Desktop, ChatGPT, etc.) until per-runtime skills
|
|
175
|
+
exist for those.
|
|
176
|
+
|
|
177
|
+
## Troubleshooting
|
|
178
|
+
|
|
179
|
+
**"LYRIEL_API_KEY is not set" in Hermes logs.** The env var isn't in the
|
|
180
|
+
gateway's process environment. Add it to `~/.hermes/.env` and restart, or
|
|
181
|
+
run `hermes setup` to re-prompt.
|
|
182
|
+
|
|
183
|
+
**Plugin loads but nothing arrives in Telegram.** Check the gateway logs
|
|
184
|
+
for "no Telegram chat id configured". Set
|
|
185
|
+
`plugins.entries.lyriel.telegram_chat_id` (numeric) in
|
|
186
|
+
`~/.hermes/config.yaml`. To find your chat id, message `@userinfobot` in
|
|
187
|
+
Telegram — its `Id` field is your chat id for any DM with any bot.
|
|
188
|
+
|
|
189
|
+
**"could not extract callback URL/token for ask ..." in logs.** The
|
|
190
|
+
inbound dispatch envelope didn't match the expected pattern. Likely a
|
|
191
|
+
Lyriel server version mismatch. Re-pair your agent at `/me/agent` for a
|
|
192
|
+
fresh setup.
|
|
193
|
+
|
|
194
|
+
**LLM keeps trying to reply via `lyriel_send_ask` instead of
|
|
195
|
+
`lyriel_reply`.** Adjust the surface message wording in `surfacing.py`
|
|
196
|
+
to be more directive, or add a system-prompt note in your Hermes config
|
|
197
|
+
that distinguishes initiation from reply.
|
|
198
|
+
|
|
199
|
+
**Hot reload.** Hermes doesn't hot-reload plugins. After any edit to
|
|
200
|
+
files in `~/.hermes/plugins/lyriel/`, restart the gateway.
|
|
201
|
+
|
|
202
|
+
## v0 limitations
|
|
203
|
+
|
|
204
|
+
- **In-process pending map.** If Hermes restarts mid-flight, pending
|
|
205
|
+
callback tokens are lost. The user will see "no pending Lyriel asks"
|
|
206
|
+
if they try to reply to an ask that arrived before the restart.
|
|
207
|
+
- **One-pending-per-ask.** No threading of replies; if multiple asks
|
|
208
|
+
are pending, the LLM disambiguates via ask_id (the surface message
|
|
209
|
+
contains it).
|
|
210
|
+
- **Single user per Hermes instance.** The plugin reads one
|
|
211
|
+
`telegram_chat_id` and one `LYRIEL_API_KEY`. Multi-user Hermes
|
|
212
|
+
setups aren't supported yet.
|
|
213
|
+
|
|
214
|
+
## Publishing
|
|
215
|
+
|
|
216
|
+
The package is `lyriel-hermes` on PyPI. Prerequisites: a PyPI account
|
|
217
|
+
with maintainer rights on the package, and a PyPI API token in
|
|
218
|
+
`~/.pypirc` (or pass it via `--username __token__`).
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
# 1. Bump the version in pyproject.toml (and lyriel_hermes/plugin.yaml
|
|
222
|
+
# if the Hermes-side version should track).
|
|
223
|
+
# 2. Build sdist + wheel into dist/.
|
|
224
|
+
uv build
|
|
225
|
+
# 3. Upload to PyPI.
|
|
226
|
+
uvx twine upload dist/*
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
After publish, the install paste-prompt (`pip install lyriel-hermes &&
|
|
230
|
+
lyriel-hermes install`) resolves to the new version. Existing
|
|
231
|
+
installs upgrade via `pip install --upgrade lyriel-hermes`; the
|
|
232
|
+
symlink in `~/.hermes/plugins/lyriel` keeps pointing at the package,
|
|
233
|
+
so the next Hermes restart picks up the new code automatically.
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# Lyriel plugin for Hermes
|
|
2
|
+
|
|
3
|
+
Native Hermes integration for the Lyriel substrate. One bot, one chat, full
|
|
4
|
+
agent context. Replaces the standalone Telegram bridge with a plugin that
|
|
5
|
+
runs inside the Hermes process and pipes Lyriel asks into the user's
|
|
6
|
+
existing Hermes Telegram chat via the gateway's own send pipeline.
|
|
7
|
+
|
|
8
|
+
## What you get
|
|
9
|
+
|
|
10
|
+
- 🪶 Lyriel asks arrive in your active Hermes surface the instant
|
|
11
|
+
they're dispatched (long-poll loop on a daemon thread inside Hermes).
|
|
12
|
+
The plugin auto-detects which surface you're on:
|
|
13
|
+
- **CLI mode** (`hermes` in a terminal): the dispatch is injected as
|
|
14
|
+
the next message in your conversation via Hermes's `inject_message`
|
|
15
|
+
primitive. If the agent is mid-turn, it interrupts; if idle, the
|
|
16
|
+
surface message becomes the next "user" input that triggers the
|
|
17
|
+
agent.
|
|
18
|
+
- **Gateway mode** (Hermes connected to Telegram / Discord / Slack):
|
|
19
|
+
the plugin pushes through the gateway's existing platform adapter.
|
|
20
|
+
No second bot to configure.
|
|
21
|
+
- Two LLM tools — `lyriel_send_ask` and `lyriel_reply` — so the user
|
|
22
|
+
drives Lyriel in natural language. "Hermes, ping @noor about dinner."
|
|
23
|
+
"Reply yes thursday works."
|
|
24
|
+
- Full agent context: Hermes's memory, preferences, calendar tools, etc.
|
|
25
|
+
are all available when drafting replies to Lyriel asks.
|
|
26
|
+
|
|
27
|
+
## Install
|
|
28
|
+
|
|
29
|
+
### Published (recommended)
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install lyriel-hermes
|
|
33
|
+
lyriel-hermes install
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The `install` subcommand symlinks the installed package into
|
|
37
|
+
`~/.hermes/plugins/lyriel/` so Hermes discovers it on next boot. Pass
|
|
38
|
+
`--copy` if your environment doesn't allow symlinks; `--target PATH`
|
|
39
|
+
to override the destination.
|
|
40
|
+
|
|
41
|
+
Subsequent `pip install --upgrade lyriel-hermes` updates the plugin
|
|
42
|
+
through the symlink — no need to re-run `lyriel-hermes install`.
|
|
43
|
+
|
|
44
|
+
### Development (from this checkout)
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
# Editable install — pip points at lyriel_hermes/ in this repo.
|
|
48
|
+
pip install -e clients/hermes-plugin
|
|
49
|
+
lyriel-hermes install # symlinks the editable install into ~/.hermes/plugins/lyriel
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Or symlink directly without pip:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
ln -sfn "$(pwd)/clients/hermes-plugin/lyriel_hermes" ~/.hermes/plugins/lyriel
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Configure
|
|
59
|
+
|
|
60
|
+
1. **Enable the plugin in `~/.hermes/config.yaml`:**
|
|
61
|
+
```yaml
|
|
62
|
+
plugins:
|
|
63
|
+
enabled:
|
|
64
|
+
- lyriel
|
|
65
|
+
entries:
|
|
66
|
+
lyriel:
|
|
67
|
+
telegram_chat_id: "123456789" # your DM chat with the Hermes bot
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
If `gateway.telegram.home_chat_id` is already set, the plugin will fall
|
|
71
|
+
back to that; you can skip the `plugins.entries.lyriel` block in that case.
|
|
72
|
+
|
|
73
|
+
2. **Set the Lyriel API key in `~/.hermes/.env`** (or your shell):
|
|
74
|
+
```bash
|
|
75
|
+
LYRIEL_API_KEY=lyk_xxxxxxxxxxxxxxxx # from /me/agent setup
|
|
76
|
+
LYRIEL_BASE_URL=https://lyriel.ai # or http://localhost:5173 for dev,
|
|
77
|
+
# or your cloudflared tunnel URL
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
3. **Restart the Hermes gateway** so the plugin is loaded:
|
|
81
|
+
```bash
|
|
82
|
+
# however you run Hermes — systemd restart, supervisor restart, etc.
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
4. **Verify** by sending an ask to yourself or asking Hermes to ping
|
|
86
|
+
`@lyriel`. You should see a 🪶 message land in your Hermes Telegram
|
|
87
|
+
chat within a second.
|
|
88
|
+
|
|
89
|
+
## Configuration
|
|
90
|
+
|
|
91
|
+
| Setting | Where | Required | Default |
|
|
92
|
+
|---|---|---|---|
|
|
93
|
+
| `LYRIEL_API_KEY` | env / `.env` | yes | — |
|
|
94
|
+
| `LYRIEL_BASE_URL` | env / `.env` | no | `https://lyriel.ai` |
|
|
95
|
+
| `plugins.entries.lyriel.telegram_chat_id` | `~/.hermes/config.yaml` | yes (or fallback) | — |
|
|
96
|
+
| `gateway.telegram.home_chat_id` | `~/.hermes/config.yaml` | fallback for above | — |
|
|
97
|
+
|
|
98
|
+
If neither chat id is set, the plugin still claims dispatches from
|
|
99
|
+
Lyriel (so they don't pile up server-side) but won't surface them to
|
|
100
|
+
Telegram. The LLM tools still work, so the user can list pending asks
|
|
101
|
+
and reply via direct prompting if needed.
|
|
102
|
+
|
|
103
|
+
## How it works
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
┌────────────────────────────────────────────────────┐
|
|
107
|
+
│ Hermes process (always-on) │
|
|
108
|
+
│ │
|
|
109
|
+
register() │ ┌──────────────────┐ ┌──────────────────────┐ │
|
|
110
|
+
─────────▶ │ │ Inbox poll loop │ │ LLM tool handlers │ │
|
|
111
|
+
│ │ (daemon thread) │ │ lyriel_send_ask │ │
|
|
112
|
+
│ └────────┬─────────┘ │ lyriel_reply │ │
|
|
113
|
+
│ │ └──────────┬───────────┘ │
|
|
114
|
+
│ │ │ │
|
|
115
|
+
│ ▼ ▼ │
|
|
116
|
+
│ pending.py (ask_id → callback URL/token map) │
|
|
117
|
+
│ │
|
|
118
|
+
│ Telegram surfacing via │
|
|
119
|
+
│ tools.send_message_tool._send_to_platform │
|
|
120
|
+
└────────────────┬───────────────────────────────────┘
|
|
121
|
+
│
|
|
122
|
+
┌────────────────▼──────────────────┐
|
|
123
|
+
│ User's Hermes Telegram chat │
|
|
124
|
+
│ │
|
|
125
|
+
│ 🪶 Lyriel ask from @noor: │
|
|
126
|
+
│ "dinner thursday?" │
|
|
127
|
+
│ To reply, say: reply to Lyriel │
|
|
128
|
+
│ ask <id> with <your response> │
|
|
129
|
+
│ │
|
|
130
|
+
│ > yes thursday works │
|
|
131
|
+
│ │
|
|
132
|
+
│ (LLM calls lyriel_reply → │
|
|
133
|
+
│ plugin POSTs callback) │
|
|
134
|
+
└───────────────────────────────────┘
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Comparison with the standalone Telegram bridge
|
|
138
|
+
|
|
139
|
+
The standalone bridge (`clients/telegram-bridge/lyriel_bridge.py`) is a
|
|
140
|
+
runtime-agnostic reference client. It works for users who don't run
|
|
141
|
+
Hermes at all but produces a worse UX:
|
|
142
|
+
|
|
143
|
+
| | Bridge | This plugin |
|
|
144
|
+
|---|---|---|
|
|
145
|
+
| Bots in user's Telegram | 2 (Hermes + bridge) | 1 (Hermes) |
|
|
146
|
+
| LLM context on incoming asks | none — bridge has no LLM | full Hermes context, memory, tools |
|
|
147
|
+
| Reply path | type verbatim into bridge chat | natural-language drafting via Hermes |
|
|
148
|
+
| Outbound asks via natural language | no — manual curl | yes — LLM calls `lyriel_send_ask` |
|
|
149
|
+
| Setup | run a separate Python process + new bot | drop into ~/.hermes/plugins/ + config entry |
|
|
150
|
+
|
|
151
|
+
The plugin is the production answer. The bridge stays useful for users on
|
|
152
|
+
other runtimes (Claude Desktop, ChatGPT, etc.) until per-runtime skills
|
|
153
|
+
exist for those.
|
|
154
|
+
|
|
155
|
+
## Troubleshooting
|
|
156
|
+
|
|
157
|
+
**"LYRIEL_API_KEY is not set" in Hermes logs.** The env var isn't in the
|
|
158
|
+
gateway's process environment. Add it to `~/.hermes/.env` and restart, or
|
|
159
|
+
run `hermes setup` to re-prompt.
|
|
160
|
+
|
|
161
|
+
**Plugin loads but nothing arrives in Telegram.** Check the gateway logs
|
|
162
|
+
for "no Telegram chat id configured". Set
|
|
163
|
+
`plugins.entries.lyriel.telegram_chat_id` (numeric) in
|
|
164
|
+
`~/.hermes/config.yaml`. To find your chat id, message `@userinfobot` in
|
|
165
|
+
Telegram — its `Id` field is your chat id for any DM with any bot.
|
|
166
|
+
|
|
167
|
+
**"could not extract callback URL/token for ask ..." in logs.** The
|
|
168
|
+
inbound dispatch envelope didn't match the expected pattern. Likely a
|
|
169
|
+
Lyriel server version mismatch. Re-pair your agent at `/me/agent` for a
|
|
170
|
+
fresh setup.
|
|
171
|
+
|
|
172
|
+
**LLM keeps trying to reply via `lyriel_send_ask` instead of
|
|
173
|
+
`lyriel_reply`.** Adjust the surface message wording in `surfacing.py`
|
|
174
|
+
to be more directive, or add a system-prompt note in your Hermes config
|
|
175
|
+
that distinguishes initiation from reply.
|
|
176
|
+
|
|
177
|
+
**Hot reload.** Hermes doesn't hot-reload plugins. After any edit to
|
|
178
|
+
files in `~/.hermes/plugins/lyriel/`, restart the gateway.
|
|
179
|
+
|
|
180
|
+
## v0 limitations
|
|
181
|
+
|
|
182
|
+
- **In-process pending map.** If Hermes restarts mid-flight, pending
|
|
183
|
+
callback tokens are lost. The user will see "no pending Lyriel asks"
|
|
184
|
+
if they try to reply to an ask that arrived before the restart.
|
|
185
|
+
- **One-pending-per-ask.** No threading of replies; if multiple asks
|
|
186
|
+
are pending, the LLM disambiguates via ask_id (the surface message
|
|
187
|
+
contains it).
|
|
188
|
+
- **Single user per Hermes instance.** The plugin reads one
|
|
189
|
+
`telegram_chat_id` and one `LYRIEL_API_KEY`. Multi-user Hermes
|
|
190
|
+
setups aren't supported yet.
|
|
191
|
+
|
|
192
|
+
## Publishing
|
|
193
|
+
|
|
194
|
+
The package is `lyriel-hermes` on PyPI. Prerequisites: a PyPI account
|
|
195
|
+
with maintainer rights on the package, and a PyPI API token in
|
|
196
|
+
`~/.pypirc` (or pass it via `--username __token__`).
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
# 1. Bump the version in pyproject.toml (and lyriel_hermes/plugin.yaml
|
|
200
|
+
# if the Hermes-side version should track).
|
|
201
|
+
# 2. Build sdist + wheel into dist/.
|
|
202
|
+
uv build
|
|
203
|
+
# 3. Upload to PyPI.
|
|
204
|
+
uvx twine upload dist/*
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
After publish, the install paste-prompt (`pip install lyriel-hermes &&
|
|
208
|
+
lyriel-hermes install`) resolves to the new version. Existing
|
|
209
|
+
installs upgrade via `pip install --upgrade lyriel-hermes`; the
|
|
210
|
+
symlink in `~/.hermes/plugins/lyriel` keeps pointing at the package,
|
|
211
|
+
so the next Hermes restart picks up the new code automatically.
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"""Lyriel plugin for Hermes.
|
|
2
|
+
|
|
3
|
+
Wires Lyriel (https://lyriel.ai) into the Hermes runtime. Replaces the
|
|
4
|
+
standalone Lyriel-Telegram bridge with native integration so the user has
|
|
5
|
+
one bot, one chat, and full agent context.
|
|
6
|
+
|
|
7
|
+
Three pieces wire up at register():
|
|
8
|
+
|
|
9
|
+
1. A background daemon thread that long-polls Lyriel's /api/inbox and
|
|
10
|
+
pushes each dispatch into the user's existing Hermes Telegram chat
|
|
11
|
+
via the gateway's send pipeline.
|
|
12
|
+
|
|
13
|
+
2. Two LLM tools — lyriel_send_ask and lyriel_reply — so the user can
|
|
14
|
+
initiate and respond to Lyriel asks in natural language ("ping noor
|
|
15
|
+
about dinner thursday", "reply yes thursday works") and the agent's
|
|
16
|
+
LLM translates to substrate calls.
|
|
17
|
+
|
|
18
|
+
3. Implicit pending-callback state: when the poll loop surfaces an
|
|
19
|
+
inbound ask, the callback URL + single-use token from the sealed
|
|
20
|
+
envelope are stashed under the ask_id. The lyriel_reply tool reads
|
|
21
|
+
this map when the user wants to respond.
|
|
22
|
+
|
|
23
|
+
Required env (declared in plugin.yaml's requires_env via the tool):
|
|
24
|
+
LYRIEL_API_KEY — your lyk_... key from https://lyriel.ai/me/agent setup
|
|
25
|
+
Optional env:
|
|
26
|
+
LYRIEL_BASE_URL — defaults to https://lyriel.ai; override for dev
|
|
27
|
+
(e.g. http://localhost:5173 or a cloudflared tunnel)
|
|
28
|
+
|
|
29
|
+
Required config in ~/.hermes/config.yaml (one of):
|
|
30
|
+
plugins.entries.lyriel.telegram_chat_id — your chat with the Hermes bot
|
|
31
|
+
gateway.telegram.home_chat_id — Hermes's default home chat,
|
|
32
|
+
used as fallback
|
|
33
|
+
|
|
34
|
+
If neither is set, the poll loop still claims dispatches from Lyriel
|
|
35
|
+
(preventing pile-up) but skips surfacing — the user can list pending
|
|
36
|
+
asks via the LLM and reply manually.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
from __future__ import annotations
|
|
40
|
+
|
|
41
|
+
import logging
|
|
42
|
+
|
|
43
|
+
logger = logging.getLogger(__name__)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def register(ctx) -> None:
|
|
47
|
+
"""Plugin entry point. Called once at Hermes boot when
|
|
48
|
+
plugins.enabled in config.yaml includes 'lyriel'.
|
|
49
|
+
"""
|
|
50
|
+
# Import here, not at module top, so `discover_plugins()` can import
|
|
51
|
+
# the plugin module to read its metadata without triggering Hermes's
|
|
52
|
+
# gateway internals (which the surfacing module touches).
|
|
53
|
+
from . import inbox
|
|
54
|
+
from .tools import (
|
|
55
|
+
LYRIEL_PLAN_LOCK_SCHEMA,
|
|
56
|
+
LYRIEL_PLAN_REPLY_SCHEMA,
|
|
57
|
+
LYRIEL_PLAN_SEND_SCHEMA,
|
|
58
|
+
LYRIEL_REPLY_SCHEMA,
|
|
59
|
+
LYRIEL_SEND_ASK_SCHEMA,
|
|
60
|
+
handle_lyriel_plan_lock,
|
|
61
|
+
handle_lyriel_plan_reply,
|
|
62
|
+
handle_lyriel_plan_send,
|
|
63
|
+
handle_lyriel_reply,
|
|
64
|
+
handle_lyriel_send_ask,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# LLM tools so the user can drive Lyriel in natural language.
|
|
68
|
+
ctx.register_tool(
|
|
69
|
+
name="lyriel_send_ask",
|
|
70
|
+
toolset="lyriel",
|
|
71
|
+
schema=LYRIEL_SEND_ASK_SCHEMA,
|
|
72
|
+
handler=handle_lyriel_send_ask,
|
|
73
|
+
requires_env=["LYRIEL_API_KEY"],
|
|
74
|
+
emoji="🪶",
|
|
75
|
+
description=(
|
|
76
|
+
"Send a Lyriel ask to another verified user. "
|
|
77
|
+
"Used when the user says things like 'ping @noor about X'."
|
|
78
|
+
),
|
|
79
|
+
)
|
|
80
|
+
ctx.register_tool(
|
|
81
|
+
name="lyriel_reply",
|
|
82
|
+
toolset="lyriel",
|
|
83
|
+
schema=LYRIEL_REPLY_SCHEMA,
|
|
84
|
+
handler=handle_lyriel_reply,
|
|
85
|
+
requires_env=["LYRIEL_API_KEY"],
|
|
86
|
+
emoji="🪶",
|
|
87
|
+
description=(
|
|
88
|
+
"Reply to a pending Lyriel ask. Used when the user is "
|
|
89
|
+
"responding to a 🪶 Lyriel ask surface message."
|
|
90
|
+
),
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
# Group-plan tools — the wedge. Distinct from 1:1 asks because plans
|
|
94
|
+
# are multi-participant, multi-round, and agent-mediated negotiation.
|
|
95
|
+
ctx.register_tool(
|
|
96
|
+
name="lyriel_plan_send",
|
|
97
|
+
toolset="lyriel",
|
|
98
|
+
schema=LYRIEL_PLAN_SEND_SCHEMA,
|
|
99
|
+
handler=handle_lyriel_plan_send,
|
|
100
|
+
requires_env=["LYRIEL_API_KEY"],
|
|
101
|
+
emoji="🪶",
|
|
102
|
+
description=(
|
|
103
|
+
"Start a Lyriel group plan across multiple participants. "
|
|
104
|
+
"Used when the user wants to coordinate something with two "
|
|
105
|
+
"or more other Lyriel handles."
|
|
106
|
+
),
|
|
107
|
+
)
|
|
108
|
+
ctx.register_tool(
|
|
109
|
+
name="lyriel_plan_reply",
|
|
110
|
+
toolset="lyriel",
|
|
111
|
+
schema=LYRIEL_PLAN_REPLY_SCHEMA,
|
|
112
|
+
handler=handle_lyriel_plan_reply,
|
|
113
|
+
requires_env=["LYRIEL_API_KEY"],
|
|
114
|
+
emoji="🪶",
|
|
115
|
+
description=(
|
|
116
|
+
"Contribute the user's context (or stay silent) on an "
|
|
117
|
+
"ongoing Lyriel group plan."
|
|
118
|
+
),
|
|
119
|
+
)
|
|
120
|
+
ctx.register_tool(
|
|
121
|
+
name="lyriel_plan_lock",
|
|
122
|
+
toolset="lyriel",
|
|
123
|
+
schema=LYRIEL_PLAN_LOCK_SCHEMA,
|
|
124
|
+
handler=handle_lyriel_plan_lock,
|
|
125
|
+
requires_env=["LYRIEL_API_KEY"],
|
|
126
|
+
emoji="🪶",
|
|
127
|
+
description=(
|
|
128
|
+
"Lock a Lyriel group plan with a final outcome. Initiator-only."
|
|
129
|
+
),
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
# Background inbox poller. Daemon thread — dies cleanly with Hermes.
|
|
133
|
+
# Pass ctx so the loop can use ctx.inject_message for CLI-mode
|
|
134
|
+
# surfacing (proactive notification path when running under
|
|
135
|
+
# `hermes` CLI rather than the gateway with Telegram connected).
|
|
136
|
+
try:
|
|
137
|
+
inbox.start(ctx)
|
|
138
|
+
except Exception as e: # noqa: BLE001
|
|
139
|
+
logger.error(
|
|
140
|
+
"Lyriel plugin: failed to start inbox poll loop: %s. "
|
|
141
|
+
"Tools registered but you won't receive proactive surfacing.",
|
|
142
|
+
e,
|
|
143
|
+
)
|
|
144
|
+
return
|
|
145
|
+
|
|
146
|
+
logger.info(
|
|
147
|
+
"Lyriel plugin registered: 5 tools (ask, reply, plan_send, "
|
|
148
|
+
"plan_reply, plan_lock) + inbox poll loop on daemon thread"
|
|
149
|
+
)
|