hermes-talk 0.8.1__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.
- hermes_talk-0.8.1/LICENSE +21 -0
- hermes_talk-0.8.1/PKG-INFO +432 -0
- hermes_talk-0.8.1/README.md +413 -0
- hermes_talk-0.8.1/hermes_talk.egg-info/PKG-INFO +432 -0
- hermes_talk-0.8.1/hermes_talk.egg-info/SOURCES.txt +65 -0
- hermes_talk-0.8.1/hermes_talk.egg-info/dependency_links.txt +1 -0
- hermes_talk-0.8.1/hermes_talk.egg-info/requires.txt +10 -0
- hermes_talk-0.8.1/hermes_talk.egg-info/top_level.txt +24 -0
- hermes_talk-0.8.1/pyproject.toml +95 -0
- hermes_talk-0.8.1/setup.cfg +4 -0
- hermes_talk-0.8.1/talk_apiserver.py +435 -0
- hermes_talk-0.8.1/talk_audio.py +212 -0
- hermes_talk-0.8.1/talk_auth.py +532 -0
- hermes_talk-0.8.1/talk_cli.py +1245 -0
- hermes_talk-0.8.1/talk_config.py +501 -0
- hermes_talk-0.8.1/talk_core_realtime.py +1369 -0
- hermes_talk-0.8.1/talk_core_session.py +184 -0
- hermes_talk-0.8.1/talk_discord.py +1520 -0
- hermes_talk-0.8.1/talk_doctor.py +571 -0
- hermes_talk-0.8.1/talk_host.py +1381 -0
- hermes_talk-0.8.1/talk_identity.py +184 -0
- hermes_talk-0.8.1/talk_lifecycle.py +205 -0
- hermes_talk-0.8.1/talk_openai_realtime.py +580 -0
- hermes_talk-0.8.1/talk_operator_auth.py +525 -0
- hermes_talk-0.8.1/talk_providers.py +227 -0
- hermes_talk-0.8.1/talk_realtime.py +315 -0
- hermes_talk-0.8.1/talk_relay.py +548 -0
- hermes_talk-0.8.1/talk_runs.py +507 -0
- hermes_talk-0.8.1/talk_setup.py +1223 -0
- hermes_talk-0.8.1/talk_steer.py +737 -0
- hermes_talk-0.8.1/talk_tools.py +584 -0
- hermes_talk-0.8.1/talk_transcript.py +436 -0
- hermes_talk-0.8.1/talk_vault.py +301 -0
- hermes_talk-0.8.1/talk_wire.py +183 -0
- hermes_talk-0.8.1/tests/test_apiserver_lane.py +516 -0
- hermes_talk-0.8.1/tests/test_audio.py +128 -0
- hermes_talk-0.8.1/tests/test_auth.py +294 -0
- hermes_talk-0.8.1/tests/test_cli.py +1335 -0
- hermes_talk-0.8.1/tests/test_config.py +212 -0
- hermes_talk-0.8.1/tests/test_core_realtime_adapter.py +3036 -0
- hermes_talk-0.8.1/tests/test_core_session.py +365 -0
- hermes_talk-0.8.1/tests/test_dashboard_api.py +500 -0
- hermes_talk-0.8.1/tests/test_dashboard_js.py +92 -0
- hermes_talk-0.8.1/tests/test_dashboard_manifest.py +131 -0
- hermes_talk-0.8.1/tests/test_delegation.py +492 -0
- hermes_talk-0.8.1/tests/test_discord.py +2008 -0
- hermes_talk-0.8.1/tests/test_doctor.py +525 -0
- hermes_talk-0.8.1/tests/test_fake_provider_session.py +799 -0
- hermes_talk-0.8.1/tests/test_identity.py +127 -0
- hermes_talk-0.8.1/tests/test_identity_injection.py +682 -0
- hermes_talk-0.8.1/tests/test_installed_core_session_integration.py +611 -0
- hermes_talk-0.8.1/tests/test_lifecycle.py +247 -0
- hermes_talk-0.8.1/tests/test_openai_realtime.py +660 -0
- hermes_talk-0.8.1/tests/test_operator_auth.py +736 -0
- hermes_talk-0.8.1/tests/test_realtime_contract.py +271 -0
- hermes_talk-0.8.1/tests/test_register.py +543 -0
- hermes_talk-0.8.1/tests/test_relay.py +357 -0
- hermes_talk-0.8.1/tests/test_repository_hygiene.py +17 -0
- hermes_talk-0.8.1/tests/test_runs.py +504 -0
- hermes_talk-0.8.1/tests/test_setup.py +1290 -0
- hermes_talk-0.8.1/tests/test_steer.py +725 -0
- hermes_talk-0.8.1/tests/test_steer_ledger.py +620 -0
- hermes_talk-0.8.1/tests/test_tools.py +384 -0
- hermes_talk-0.8.1/tests/test_transcript.py +574 -0
- hermes_talk-0.8.1/tests/test_vault.py +392 -0
- hermes_talk-0.8.1/tests/test_watcher.py +143 -0
- hermes_talk-0.8.1/tests/test_wire.py +138 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 SmokeDev
|
|
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,432 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: hermes-talk
|
|
3
|
+
Version: 0.8.1
|
|
4
|
+
Summary: OpenAI Realtime speech-to-speech voice for Hermes Agent: duplex talk with live tool calling.
|
|
5
|
+
Author: SmokeDev
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.11
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Dist: httpx>=0.27
|
|
11
|
+
Requires-Dist: aiohttp>=3.9
|
|
12
|
+
Provides-Extra: audio
|
|
13
|
+
Requires-Dist: sounddevice>=0.4.6; extra == "audio"
|
|
14
|
+
Requires-Dist: numpy>=1.26; extra == "audio"
|
|
15
|
+
Provides-Extra: dev
|
|
16
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
17
|
+
Requires-Dist: ruff>=0.4; extra == "dev"
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
|
|
20
|
+
# hermes-talk
|
|
21
|
+
|
|
22
|
+
**Realtime voice as an orchestrator for [Hermes Agent](https://github.com/NousResearch/hermes-agent) — talk to it, it runs agents, it reports back out loud.**
|
|
23
|
+
|
|
24
|
+

|
|
25
|
+
|
|
26
|
+
*Real session, 8× speed. 🔊 [Watch it with sound](https://github.com/TheSmokeDev/hermes-talk/releases/download/v0.3.0/hermes-talk-dashboard-cut.mp4) — 2:27: delegate, keep talking, hear the result land. This is a voice demo; the sound is the point. Recorded at v0.3.0 — the flow is unchanged.*
|
|
27
|
+
|
|
28
|
+
## What this actually is
|
|
29
|
+
|
|
30
|
+
Not dictation. Not read-my-reply-aloud. A conversation you can hand work to
|
|
31
|
+
while it's still going:
|
|
32
|
+
|
|
33
|
+
> **You:** audit the auth module for error-handling gaps and report back
|
|
34
|
+
> **Hermes:** starting that now — run one.
|
|
35
|
+
> **You:** while that runs — what did we decide about the retry policy?
|
|
36
|
+
> **Hermes:** *(searches your past sessions)* three attempts with exponential backoff, decided on the 14th…
|
|
37
|
+
> **You:** how's that audit going?
|
|
38
|
+
> **Hermes:** run one's still working, about two minutes in.
|
|
39
|
+
> *…later, unprompted:*
|
|
40
|
+
> **Hermes:** that audit finished — three gaps, starting with the token refresh swallowing exceptions…
|
|
41
|
+
|
|
42
|
+
Four properties make that possible, and each one is the part other voice
|
|
43
|
+
integrations don't have:
|
|
44
|
+
|
|
45
|
+
1. **Duplex.** One bidirectional audio session — turn-taking, interruption,
|
|
46
|
+
and tool calls happen *inside* the speech layer, not around it. Cut it off
|
|
47
|
+
mid-sentence and it stops, because it never stopped listening.
|
|
48
|
+
2. **Its tools are Hermes's tools.** Realtime function calls relay straight
|
|
49
|
+
into the agent's real tool surface. Ask it something it can't know and you
|
|
50
|
+
hear it go look, then answer from what it found.
|
|
51
|
+
3. **Work outlives the sentence.** Delegation spawns a real background Hermes
|
|
52
|
+
agent. You keep talking. The result is spoken when it lands — you don't
|
|
53
|
+
poll, you don't wait, you don't go check a terminal.
|
|
54
|
+
4. **It starts already knowing you.** The session prompt is assembled from
|
|
55
|
+
what Hermes itself knows — your `SOUL.md`, and whatever your configured
|
|
56
|
+
memory provider contributes. Install one (e.g.
|
|
57
|
+
[hermes-homie-memory](https://github.com/TheSmokeDev/hermes-homie-memory))
|
|
58
|
+
and the first thing you say lands on an agent that already has context. No
|
|
59
|
+
tool call, no "let me look that up", no warm-up turn.
|
|
60
|
+
|
|
61
|
+
**Why this exists:** OpenAI shipped this exact pattern for Codex on 2026-07-23
|
|
62
|
+
— voice as a control layer over concurrent agents. It's excellent, and it's
|
|
63
|
+
closed: paid ChatGPT plans only, and GPT-Live has no developer API. Their own
|
|
64
|
+
docs point builders back at the Realtime API. So that's what this is built on,
|
|
65
|
+
for an agent you actually own.
|
|
66
|
+
|
|
67
|
+
Hermes's built-in voice mode is good and this doesn't replace it — turn-based
|
|
68
|
+
STT → inference → TTS is the right shape for plenty of work. This is the other
|
|
69
|
+
shape.
|
|
70
|
+
|
|
71
|
+
## Install
|
|
72
|
+
|
|
73
|
+
Needs Python ≥ 3.11 and a Hermes host ≥ v0.17. `redirect_agent`'s
|
|
74
|
+
clean-abort path wants 0.20+ and [degrades honestly below it](#redirecting-work-thats-already-running).
|
|
75
|
+
The plugin remains backward-compatible with older hosts, but session-owned
|
|
76
|
+
subagent completion announcements require a Hermes release exposing
|
|
77
|
+
`PluginContext.active_parent_session_id` (upstream
|
|
78
|
+
[PR #79716](https://github.com/NousResearch/hermes-agent/pull/79716)); without
|
|
79
|
+
that property, announcements are suppressed rather than guessed. Details are in
|
|
80
|
+
[docs/OPERATING.md](docs/OPERATING.md#prerequisites).
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
hermes plugins install TheSmokeDev/hermes-talk --enable
|
|
84
|
+
pip install "hermes-talk[audio]" # mic + speaker support (sounddevice)
|
|
85
|
+
hermes talk
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Zero core edits — pure `register(ctx)` plugin surface, proven on a stock
|
|
89
|
+
v0.17.0 install. 650+ offline tests, CI on ubuntu + windows × py3.11–3.13.
|
|
90
|
+
|
|
91
|
+
**Verify it** (no talking required):
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
hermes plugins list # → hermes-talk · enabled · current version
|
|
95
|
+
hermes talk --help # → registration proof: the command only exists if the plugin loaded
|
|
96
|
+
hermes talk setup # → guided, confirmation-gated setup plus doctor verification
|
|
97
|
+
hermes talk doctor # → read-only human diagnostics
|
|
98
|
+
hermes talk doctor --json # → the same versioned receipt for scripts/issues
|
|
99
|
+
# then, in any session: say "status report" — talk_status answers with
|
|
100
|
+
# version, auth lane, agent lane, and audio state.
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
**Upgrade** with `hermes plugins update hermes-talk` — not a second
|
|
104
|
+
`install` (it refuses on an existing plugin) — then **restart the
|
|
105
|
+
gateway**: a running process keeps executing the old code until you do.
|
|
106
|
+
Full runbook, wire canary included: [docs/OPERATING.md](docs/OPERATING.md#verify--the-receipts).
|
|
107
|
+
|
|
108
|
+
## Auth — no API key needed if you have ChatGPT
|
|
109
|
+
|
|
110
|
+
Signed into the [Codex CLI](https://github.com/openai/codex) (`codex login`)?
|
|
111
|
+
Talk runs on your own ChatGPT subscription's Realtime entitlement — no key, no
|
|
112
|
+
per-minute API bill. Bring a key instead if you'd rather.
|
|
113
|
+
|
|
114
|
+
Resolved fail-closed in this order:
|
|
115
|
+
|
|
116
|
+
1. `TALK_OPENAI_API_KEY` — a Talk-scoped API key (set-but-empty refuses, never
|
|
117
|
+
falls through)
|
|
118
|
+
2. `OPENAI_API_KEY` — the shared environment key
|
|
119
|
+
3. **Codex OAuth** — no key at all: if you're signed into the
|
|
120
|
+
[Codex CLI](https://github.com/openai/codex) (`codex login`), Talk rides
|
|
121
|
+
your own ChatGPT subscription's Realtime entitlement. Expired tokens
|
|
122
|
+
refresh automatically and write back atomically, so the Codex CLI keeps
|
|
123
|
+
working.
|
|
124
|
+
|
|
125
|
+
That historical order remains unchanged when `TALK_PREFER_CODEX_OAUTH` is
|
|
126
|
+
absent or explicitly false. Set `TALK_PREFER_CODEX_OAUTH=true` to require the
|
|
127
|
+
subscription lane even when API keys exist. The preference is fail-closed: a
|
|
128
|
+
missing/unusable Codex login refuses instead of spending a metered key, and a
|
|
129
|
+
blank or invalid preference refuses until corrected. `hermes talk doctor`
|
|
130
|
+
names the winning lane and distinguishes valid OAuth from an expired credential
|
|
131
|
+
that still requires a successful refresh; it never prints the key or token.
|
|
132
|
+
When setup offers the API-key lane under an enabled OAuth preference, it reuses
|
|
133
|
+
an existing metered key when present and separately confirms the required
|
|
134
|
+
`TALK_PREFER_CODEX_OAUTH=false` policy transition.
|
|
135
|
+
|
|
136
|
+
Whatever the lane, the session is minted server-side into an **ephemeral
|
|
137
|
+
client secret** — the raw key or OAuth token touches exactly one OpenAI
|
|
138
|
+
endpoint and never reaches the socket, a log line, or a client.
|
|
139
|
+
|
|
140
|
+
## Use
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
hermes talk # terminal duplex voice session
|
|
144
|
+
hermes talk setup # detect → ask only missing decisions → confirm/write → verify
|
|
145
|
+
hermes talk doctor # strictly read-only configuration and host diagnostics
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Setup commits all individually confirmed settings to the active Hermes home's
|
|
149
|
+
`.env` as one secure atomic transaction and updates the current process to match.
|
|
150
|
+
On failure it rolls both surfaces back when possible, emits a value-free
|
|
151
|
+
applied/rolled-back/failed receipt, attempts and verifies every secret-bearing
|
|
152
|
+
temporary-file cleanup, and reruns doctor whenever a mutation may remain. Any
|
|
153
|
+
surviving temp is a surviving mutation: setup returns `failed` and identifies
|
|
154
|
+
the cleanup slot/error class without printing the path nonce or secret value.
|
|
155
|
+
New secret files keep POSIX `0600` behavior and receive a protected owner-only
|
|
156
|
+
DACL on Windows; an existing Windows destination DACL is preserved. A healthy
|
|
157
|
+
configuration asks no questions and performs no writes. Doctor never delegates
|
|
158
|
+
to setup.
|
|
159
|
+
|
|
160
|
+
or `/talk` inside an interactive Hermes session, which additionally reaches the
|
|
161
|
+
agent-loop-only tools (`memory`, `session_search`, `delegate_task`).
|
|
162
|
+
|
|
163
|
+
**In Discord**, `/talk join` runs the call in the voice channel Hermes is
|
|
164
|
+
already in — same conversation, same tools, same steering, in a room other
|
|
165
|
+
people can hear. Talk now reports speaker transitions to the model using the
|
|
166
|
+
member's immutable Discord user ID; display names are quoted as untrusted data,
|
|
167
|
+
and an unknown SSRC stays unresolved and unauthorized. Configure immutable IDs
|
|
168
|
+
with `TALK_DISCORD_OPERATOR_USER_IDS=<id>[,<id>...]`. Only those speakers may
|
|
169
|
+
run `delegate_task`, `steer_agent`, `redirect_agent`, or `stop_work`; everyone
|
|
170
|
+
may still converse and use read-only tools. Unset, blank, or any malformed list
|
|
171
|
+
authorizes nobody. Talk binds permission to the exact Discord PCM, VAD input
|
|
172
|
+
item, and opaque Realtime response metadata — never a display name, SSRC,
|
|
173
|
+
model argument, or whichever person spoke most recently. Mixed, missing, or
|
|
174
|
+
unresolved attribution fails closed with a spoken denial. Terminal microphone
|
|
175
|
+
and dashboard sessions retain their existing behavior. Talk borrows the host's
|
|
176
|
+
own voice connection rather than opening a second one. Details:
|
|
177
|
+
[docs/OPERATING.md](docs/OPERATING.md#discord-voice--talking-in-the-channel-hermes-is-already-in).
|
|
178
|
+
|
|
179
|
+
What can you actually say? The full say-this → hear-this card, with what
|
|
180
|
+
each spoken receipt commits to: [docs/VOICE-COMMANDS.md](docs/VOICE-COMMANDS.md).
|
|
181
|
+
|
|
182
|
+
## Dashboard tab
|
|
183
|
+
|
|
184
|
+
The demo at the top of this README is this tab. Start the dashboard and Talk
|
|
185
|
+
appears in the nav:
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
hermes plugins enable hermes-talk # already done by `install --enable`
|
|
189
|
+
hermes dashboard # then open the Talk tab
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Hit **Start**, allow the microphone, and talk. The page mints an ephemeral
|
|
193
|
+
secret server-side, dials OpenAI directly over WebRTC, relays every function
|
|
194
|
+
call back into the plugin's real tool surface, and shows the transcript plus a
|
|
195
|
+
live list of background runs. Nothing to install — the bundle ships with the
|
|
196
|
+
plugin and the host serves it.
|
|
197
|
+
|
|
198
|
+
**Memory writeback currently covers terminal and Discord Talk sessions.** Those
|
|
199
|
+
rooms share the server-side Realtime relay, which durably captures completed
|
|
200
|
+
turns. The dashboard's Realtime events stay in the browser, so matching durable
|
|
201
|
+
capture requires a separate authenticated transcript endpoint; until that lane
|
|
202
|
+
exists, the tab does not claim to write its conversation back to memory.
|
|
203
|
+
|
|
204
|
+
The tile at the top of the tab reads **attached**, **api-server**, or **out of
|
|
205
|
+
process** — which of the three agent lanes below this session would actually
|
|
206
|
+
use. It is not a guess; it is the lane the next tool call will take.
|
|
207
|
+
|
|
208
|
+
### `TALK_DASHBOARD_TOKEN` — the tab's own gate
|
|
209
|
+
|
|
210
|
+
Dashboard routes already sit behind the dashboard's session auth, but this
|
|
211
|
+
plugin's routes mint real credentials, so they carry a second check that never
|
|
212
|
+
fails open:
|
|
213
|
+
|
|
214
|
+
- **Unset (default): loopback only.** A browser on the same machine works with
|
|
215
|
+
no configuration. Anything else is refused with a message naming this
|
|
216
|
+
variable — including a request whose peer address this process cannot read
|
|
217
|
+
at all, which is treated as remote rather than trusted.
|
|
218
|
+
- **Set: the token is required**, on loopback too, compared with
|
|
219
|
+
`hmac.compare_digest`. Paste it into the field the tab offers when it gets
|
|
220
|
+
refused; it's held in `sessionStorage`, so it dies with the tab.
|
|
221
|
+
|
|
222
|
+
Set it whenever the dashboard is reachable from anywhere but this machine.
|
|
223
|
+
|
|
224
|
+
## Reaching a real agent — the three lanes
|
|
225
|
+
|
|
226
|
+
Everything that needs an actual Hermes agent — a memory lookup, a delegated
|
|
227
|
+
task — goes down the same chain, and **every fall-through is said out loud**:
|
|
228
|
+
|
|
229
|
+
1. **Attached** — the agent loop this session is running inside. Only `/talk`
|
|
230
|
+
has one. Answers come back inline, in the same breath.
|
|
231
|
+
2. **api-server** — a real, fully-tooled Hermes agent reached over the
|
|
232
|
+
[api_server gateway platform](#turning-the-api-server-lane-on). This is what
|
|
233
|
+
makes the dashboard tab and a standalone `hermes talk` more than a fallback.
|
|
234
|
+
3. **Out of process** — no agent lane. Delegation still spawns a detached
|
|
235
|
+
`hermes -z` one-shot; a memory lookup refuses, naming exactly what's missing.
|
|
236
|
+
|
|
237
|
+
Lanes 2 and 3 answer with a receipt rather than the answer, and speak the
|
|
238
|
+
result when it lands. That is not a shortcut: an agent run takes seconds to
|
|
239
|
+
minutes, and the tool call that starts it runs on the same thread carrying your
|
|
240
|
+
microphone. Waiting there wouldn't be patience, it would be dead air.
|
|
241
|
+
|
|
242
|
+
### Turning the api-server lane on
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
# in your gateway environment
|
|
246
|
+
API_SERVER_ENABLED=true
|
|
247
|
+
API_SERVER_KEY=<a key you choose>
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Restart the gateway. Talk finds it by itself — no Talk-side configuration is
|
|
251
|
+
needed, because `API_SERVER_KEY` is the same variable the gateway reads. If you
|
|
252
|
+
want Talk to use a *different* key or a non-default address, set
|
|
253
|
+
`TALK_API_SERVER_KEY` / `TALK_API_SERVER_URL`.
|
|
254
|
+
|
|
255
|
+
Talk probes `GET /v1/capabilities` (which is authenticated, on purpose) at
|
|
256
|
+
session start, so a wrong key is reported as **"running but rejected my key"**
|
|
257
|
+
rather than as "not reachable" — those send you to two different places.
|
|
258
|
+
|
|
259
|
+
## Background work
|
|
260
|
+
|
|
261
|
+
Say "go audit the site and tell me what's broken" and it starts a real agent,
|
|
262
|
+
then keeps talking to you. When the work lands, Talk speaks the result
|
|
263
|
+
unprompted. Ask "how's that going?" in the meantime and `check_work` answers.
|
|
264
|
+
|
|
265
|
+
Delegation walks the [three lanes](#reaching-a-real-agent--the-three-lanes) and
|
|
266
|
+
then one more, and **every fall-through is said out loud** — the plugin never
|
|
267
|
+
silently does less than you asked:
|
|
268
|
+
|
|
269
|
+
1. **Hermes's own agent loop** — inside `/talk`, where there's a parent agent
|
|
270
|
+
to delegate into.
|
|
271
|
+
2. **A real agent over the api_server** — preferred over a spawn: it reuses a
|
|
272
|
+
warm, fully-tooled agent instead of paying a process start.
|
|
273
|
+
3. **A detached `hermes -z` one-shot** — needs nothing enabled, so this is the
|
|
274
|
+
lane that always exists as long as `hermes` is on the PATH.
|
|
275
|
+
4. None available — a refusal naming all three missing lanes.
|
|
276
|
+
|
|
277
|
+
### Redirecting work that's already running
|
|
278
|
+
|
|
279
|
+
Say "tell that audit to focus on the token refresh instead" and `steer_agent`
|
|
280
|
+
queues the note into the running agent. Steering is not stopping: the agent
|
|
281
|
+
sees the note after its current step, and the current step always finishes.
|
|
282
|
+
|
|
283
|
+
The honest part — and the reason this surface looks the way it does — is that
|
|
284
|
+
the host's steer primitive is a **queue write**. Queued is not delivered. So
|
|
285
|
+
every note gets a receipt with a state the substrate can actually prove:
|
|
286
|
+
|
|
287
|
+
| State | What proves it |
|
|
288
|
+
|---|---|
|
|
289
|
+
| `queued` | the steer call was accepted — the only claim made at call time |
|
|
290
|
+
| `landed` | one of the host's own drain artifacts fired: the post-tool-batch log line (matched by the correlation token each note carries), or the pre-API drain attributed to that exact agent |
|
|
291
|
+
| `redirected` | `AIAgent.redirect()` returned True on a live turn — the return value IS the artifact; that path emits no log line |
|
|
292
|
+
| `unconfirmed` | the agent finished and no landing was ever observed |
|
|
293
|
+
| `missed` | a patched host reported the note back as undelivered |
|
|
294
|
+
| `superseded` | the agent was stopped — stopping drops unread notes, by design |
|
|
295
|
+
|
|
296
|
+
Ask `check_work` and you hear the note's state in those words — never "they
|
|
297
|
+
got it" unless the artifact that proves it exists. Since v0.6 every note
|
|
298
|
+
travels as `[tk-xxxxxxxx] note` — the token is what the drain preview is
|
|
299
|
+
matched on, so two agents holding identical text can never land each
|
|
300
|
+
other's receipts. If a truncated sibling receipt has no exact agent reference,
|
|
301
|
+
it stays queued/unconfirmed: receipt order and a reused public subagent id never
|
|
302
|
+
let it inherit another receipt's generation. One substrate note: watching the
|
|
303
|
+
pre-API drain lowers the host's `agent.conversation_loop` logger to DEBUG (with
|
|
304
|
+
a gate filter so operator log output is unchanged) — any DEBUG-guarded
|
|
305
|
+
computation in that one module becomes active, a bounded perf cost traded for
|
|
306
|
+
killing the false-"unconfirmed" class. And you often don't have to ask: the
|
|
307
|
+
host's `subagent_stop` hook announces a finished background agent into the live
|
|
308
|
+
call the moment it lands. Those hook events are filtered to the parent session
|
|
309
|
+
that owns the call; foreign or ownership-less completions are never spoken.
|
|
310
|
+
|
|
311
|
+
Four tools carry the surface, discovery-first:
|
|
312
|
+
|
|
313
|
+
- **`list_agents`** — everything running, tagged `can steer` (live subagent
|
|
314
|
+
ids) or `stop only` (run numbers). The model resolves "the research one"
|
|
315
|
+
here, against ids that exist right now.
|
|
316
|
+
- **`steer_agent`** — subagent ids only. Prefers the host's public
|
|
317
|
+
`steer_subagent` ([hermes-agent#76805](https://github.com/NousResearch/hermes-agent/pull/76805))
|
|
318
|
+
when present; otherwise resolves the same delegation registry directly and
|
|
319
|
+
calls the public `AIAgent.steer()`. A genuine host error is spoken, never
|
|
320
|
+
routed around.
|
|
321
|
+
- **`redirect_agent`** — the stronger correction, for "stop, wrong repo":
|
|
322
|
+
the host's public `AIAgent.redirect()` (0.20+) aborts the agent's
|
|
323
|
+
in-flight thinking and retries with your correction, instead of waiting
|
|
324
|
+
for the next tool boundary. Mid-tool it degrades to the steer queue and
|
|
325
|
+
says so; on a pre-0.20 host it falls back to `steer_agent` entirely.
|
|
326
|
+
Never cancels the work.
|
|
327
|
+
- **`stop_work`** — the one verb every lane supports: subagents via the
|
|
328
|
+
host's `interrupt_subagent()`, api-server runs via `POST /v1/runs/{id}/stop`,
|
|
329
|
+
detached one-shots via their retained process handle. Every "want me to
|
|
330
|
+
stop it?" the refusals offer is backed by this tool — no offered action is
|
|
331
|
+
fictional.
|
|
332
|
+
|
|
333
|
+
Runs on the api-server and detached lanes cannot be steered at all — those
|
|
334
|
+
lanes have no inbound channel — and the refusal says exactly that, then
|
|
335
|
+
offers the stop that actually works.
|
|
336
|
+
|
|
337
|
+
Runs are tracked in `$HERMES_HOME/state/talk-runs.jsonl`. The work is
|
|
338
|
+
detached, so ending the call does **not** stop it — but the watcher that would
|
|
339
|
+
have spoken the result dies with the session, so a run from a previous session
|
|
340
|
+
is reported as `lost`, never as "still running".
|
|
341
|
+
|
|
342
|
+
### `TALK_AGENT_PROFILE` — which profile the background agent runs under
|
|
343
|
+
|
|
344
|
+
If your model config lives in a **profile** rather than the root
|
|
345
|
+
`config.yaml`, a bare `hermes -z` cannot resolve a model and dies with
|
|
346
|
+
`Invalid length for parameter modelId, value: 0`. Talk handles this for you:
|
|
347
|
+
|
|
348
|
+
- `TALK_AGENT_PROFILE=<name>` — spawn `hermes --profile <name> -z …`.
|
|
349
|
+
- **Unset (default): auto-detect.** If the root `config.yaml` names a
|
|
350
|
+
`model.default`, no flag is added. If it doesn't and *exactly one* profile
|
|
351
|
+
under `$HERMES_HOME/profiles/` does, that profile is used.
|
|
352
|
+
- Zero matching profiles, or two or more → no flag, deliberately. Guessing
|
|
353
|
+
between profiles would be invisible until the wrong agent had already run;
|
|
354
|
+
the spawn's own error names the problem better.
|
|
355
|
+
- Set-but-blank (`TALK_AGENT_PROFILE=`) is an explicit opt out: never pass a
|
|
356
|
+
flag, even if detection would have found one.
|
|
357
|
+
|
|
358
|
+
## Knobs
|
|
359
|
+
|
|
360
|
+
The common ones. Every variable — api-server probe internals included —
|
|
361
|
+
with defaults and failure modes: [docs/OPERATING.md](docs/OPERATING.md#configuration--every-knob).
|
|
362
|
+
|
|
363
|
+
| Variable | Default | What it does |
|
|
364
|
+
|---|---|---|
|
|
365
|
+
| `TALK_MODEL` | `gpt-realtime-2.1` | Realtime model; doctor certifies only the bounded duplex-audio + tool-calling policy and labels other Realtime-shaped ids compatibility-unknown |
|
|
366
|
+
| `TALK_VOICE` | `cedar` | Realtime voice (fail-closed on unknown ids) |
|
|
367
|
+
| `TALK_PREFER_CODEX_OAUTH` | unset | `true` requires Codex OAuth and refuses key fallback; absent/`false` keeps key-first precedence |
|
|
368
|
+
| `TALK_INPUT_DEVICE` / `TALK_OUTPUT_DEVICE` | auto | sounddevice overrides |
|
|
369
|
+
| `TALK_AGENT_PROFILE` | auto-detect | Profile for the detached background agent |
|
|
370
|
+
| `TALK_API_SERVER_URL` | `http://127.0.0.1:8642` | Where the api-server lane looks |
|
|
371
|
+
| `TALK_API_SERVER_KEY` | `API_SERVER_KEY` | Key for the api-server lane (blank = send none) |
|
|
372
|
+
| `TALK_AGENT_TIMEOUT_S` | `1800` | Budget for one background run, and its watcher |
|
|
373
|
+
| `TALK_IDENTITY_INCLUDE` | all | Which identity sections ride the prompt |
|
|
374
|
+
| `TALK_DASHBOARD_TOKEN` | unset | Token for the dashboard tab's routes (unset = loopback only) |
|
|
375
|
+
| `TALK_DISCORD_OPERATOR_USER_IDS` | none | Comma-separated immutable Discord IDs allowed to run mutating tools; malformed = nobody |
|
|
376
|
+
|
|
377
|
+
### `TALK_IDENTITY_INCLUDE` — what the session starts knowing
|
|
378
|
+
|
|
379
|
+
Two sections are resolved at session start, each independently and each
|
|
380
|
+
optional:
|
|
381
|
+
|
|
382
|
+
- **`PERSONA`** — your `SOUL.md`, read through Hermes's own loader (so it gets
|
|
383
|
+
the same injection scan the text agent's copy does).
|
|
384
|
+
- **`MEMORY`** — the system-prompt block your configured memory provider
|
|
385
|
+
contributes. Inside `/talk` this is the live agent's already-assembled
|
|
386
|
+
block; standalone, Talk loads the configured provider itself, reads the
|
|
387
|
+
block, and shuts it down again.
|
|
388
|
+
|
|
389
|
+
A broken or missing provider costs that section and nothing else — the call
|
|
390
|
+
still starts. `talk_status` reports which sections resolved and how many
|
|
391
|
+
characters each contributes, never their content.
|
|
392
|
+
|
|
393
|
+
Set `TALK_IDENTITY_INCLUDE=MEMORY,PERSONA` to pin the list. **The trap: this
|
|
394
|
+
REPLACES the default rather than extending it** — `TALK_IDENTITY_INCLUDE=MEMORY`
|
|
395
|
+
means memory *and nothing else*, and the only symptom is a session that has
|
|
396
|
+
quietly stopped knowing who it's talking to. Unknown names are dropped
|
|
397
|
+
silently, so a typo narrows the prompt instead of taking voice down.
|
|
398
|
+
|
|
399
|
+
Sections are capped (`PERSONA` 4,000 chars, `MEMORY` 6,000). A Realtime
|
|
400
|
+
session's instructions are resident for the whole call and paid on every turn,
|
|
401
|
+
so these are a budget, not a nicety.
|
|
402
|
+
|
|
403
|
+
## Design rules
|
|
404
|
+
|
|
405
|
+
The three that shaped everything else:
|
|
406
|
+
|
|
407
|
+
- **Nothing fails quietly.** A degraded backend, a missing tool, a run whose
|
|
408
|
+
watcher died — each is said out loud in the conversation. A voice surface
|
|
409
|
+
that silently does less than you asked is worse than one that refuses.
|
|
410
|
+
- **The credential never leaves the process.** Key or OAuth token hits exactly
|
|
411
|
+
one OpenAI endpoint (the mint) and the socket only ever sees the ephemeral
|
|
412
|
+
secret it returns.
|
|
413
|
+
- **Hermes owns the tools and the session.** The Realtime layer is ears, mouth,
|
|
414
|
+
and turn-taking. It never owns the agent loop.
|
|
415
|
+
|
|
416
|
+
## Status
|
|
417
|
+
|
|
418
|
+
v0.7.0 — under active development. Changes, all seven versions with their
|
|
419
|
+
receipts: [CHANGELOG.md](CHANGELOG.md). Roadmap: barge-in latch + spoken-text
|
|
420
|
+
normalizer, Gemini Live backend
|
|
421
|
+
([#3](https://github.com/TheSmokeDev/hermes-talk/issues/3)), `computer_use`
|
|
422
|
+
relay, session-end memory debrief, gateway platform adapter.
|
|
423
|
+
|
|
424
|
+
Related: [RFC #77111](https://github.com/NousResearch/hermes-agent/issues/77111)
|
|
425
|
+
proposes a `RealtimeVoiceProvider` ABC in Hermes core — four open PRs are
|
|
426
|
+
building duplex voice independently, and the category deserves an interface
|
|
427
|
+
rather than a merge queue. This plugin is a working reference implementation
|
|
428
|
+
for that discussion, not a bid to be merged.
|
|
429
|
+
|
|
430
|
+
## License
|
|
431
|
+
|
|
432
|
+
MIT
|