Repository navigation
Conversation
…ally (inspired by Muse Code) Muse Code's out-of-beta release (Aug 31 2026) added inter-session messaging: "When a change in one session affects what another is building, that session can pass a warning across... The agent does the sending: it discovers peers and writes the message itself through two built-in tools." Hermes had no equivalent: two concurrent sessions in the same repo cannot warn each other about conflicting changes or hand over an answer without the user retyping between terminals. This adds an opt-in `peer_messaging` toolset with two tools: - `peer_sessions` — list the user's other recently active sessions (session id, title, source, cwd, last activity) from the session store. - `peer_send` — queue a message for an exact session id via an atomic filesystem mailbox under `$HERMES_HOME/runtime/peer_messages/<session>/` (0700 dir, os.replace publish; works on Windows). Unknown targets are refused without creating a mailbox; inboxes are capped at 50 messages. Delivery rides the existing mid-turn steer channel: the activity hook (`agent/activity_tracking._touch_activity`) drains the session's inbox the same way kanban operator comments are injected, so a running peer receives the message mid-turn and an idle one at its next turn. The injected text declares peer-session provenance and explicitly carries peer-agent — not user — authority. Local-only by construction: same-user filesystem is the trust boundary, nothing crosses the network, and sessions with no inbox pay one is_dir() per rate-limited poll. Off by default; enable via `hermes tools` → Peer Messaging.
૮ >ﻌ< ა ci reviewran on cff14a4 — feat(tools): peer session messaging — sessions message each
|
andrexibiza
left a comment
There was a problem hiding this comment.
Reviewed exact head cff14a4c621f36e1d30ea2c64457b2cc058f8e0f against PR base d0df324862cb2244a40789a5ca45392165f1b950 and live main@bf53ff00a7360826ec2c9e2949533160068a8fc8. The branch is one implementation commit ahead of its base and currently three commits behind live main; those intervening main changes are in config-backup/verification surfaces, not the six files changed here, so I do not see a textual merge-order conflict. Exact-head hosted CI is green for CI, Docker, and Nix, so the surviving one-commit train is 1/1 hosted-green.
The implementation has several good containment choices: exact session IDs rather than fuzzy names, sender identity supplied by the registry context rather than model arguments, bounded inboxes, path-component sanitization, atomic publish via os.replace, self/unknown-target refusal, per-agent polling cadence, and a Windows-compatible transport. The two blockers below are both at the authority/settlement boundary rather than in the basic mailbox mechanics.
Blocking findings
-
Peer-agent text is structurally promoted to user authority. The new formatter says the payload has “peer-agent authority, not user authority,” but the receive path passes it to
AIAgent.steer(). At this exact head,agent/interrupt_control.py::steer()explicitly queues user text as its own user row, andagent/prompt_builder.py::steer_user_row()constructs the standalonerole:userrow. The PR body also accurately calls this an “OUT-OF-BAND user row.” That means the lower-authority claim exists only as prose inside a higher-authority message. A collaborating model's output cannot safely self-downgrade arole:userenvelope. This needs a structural peer/provenance lane (or an equivalent recipient-owned admission that cannot become user authority), with a regression that inspects the actual request/persisted message role instead of only asserting text captured by a stubsteer(). -
The durable mailbox is consumed before delivery settles. Every valid file is unlinked before
agent.steer(note)runs. If steer rejects/raises, or the process exits after unlink but before_pending_steeris drained into the conversation,peer_sendhas already returned success while the only durable copy is gone.AIAgent.steer()only acknowledges an in-memory pending slot at this boundary, so atomic publish does not make the consume side durable. The receiver needs a claim/ack or equivalent durable handoff: do not destroy the mailbox record until recipient settlement is known, and make restart/retry idempotent. Please add a fault regression at the read→steer/persist boundary proving one eventual delivery after restart (not zero, not duplicate).
Architecture / lineage / interlocks
This is not the first Hermes exploration of local cross-session messaging, and preserving that history will make the ownership story clearer. HermesZum's #90288 introduced peer/broadcast steering, then was explicitly superseded by their fuller #90340; #90340 was later withdrawn when that feature moved to a private plugin. Those are historical adjacent predecessors, not code duplicates of this filesystem-mailbox implementation, but they should be credited in the PR's Source/related-work section rather than leaving Muse Code as the only lineage. Sahil-SS9's closed draft #83661 is also adjacent prior work: it pursued a host-owned exact-session injection seam with explicit closed/rotated/unauthorized fail-closed behavior for a standalone peer-messaging plugin. This PR does not supersede that broader host/plugin contract simply by choosing a different transport.
Two live interlocks matter. #91192's A2A work keeps peer identity/authority structural and explicitly refuses body text as a way to promote authority; the same invariant should hold locally even though the transport trust model is different. #101057 centralizes managed messaging-session authorization around routed home + exact session identity and deliberately denies ordinary surfaces. If these channels coexist, peer_messaging must not become a second authority path whose semantics are determined only by mailbox possession and textual framing. #106268 is complementary retained-worker orchestration rather than a duplicate, but its durable queue/ACK and permission-filtered target model is the same settlement class; avoid hardening two incompatible peer-message receipt semantics if a shared primitive can carry both.
I would keep this PR as the carrier for the lightweight same-machine peer-session feature rather than reviving the withdrawn predecessors, but it should not land until the two P1s are closed on a fresh exact head. After that, rerun the real role-boundary and crash/restart acceptance paths plus the existing focused suite and preserve the current all-commit hosted-green standard. The transport adaptation itself is clean, focused work; fixing these two edges would make the feature match the authority and durability claims it already documents.
| return False | ||
| note = "\n\n".join(format_peer_message(e) for e in entries) | ||
| try: | ||
| return bool(agent.steer(note)) |
There was a problem hiding this comment.
[P1] Do not use the user-role steer lane for peer-agent authority. format_peer_message() says this is not user authority, but AIAgent.steer() at this exact head is explicitly defined as queuing “user text … as its own user row,” and steer_user_row() constructs role:user. The textual disclaimer therefore sits inside a structurally higher-authority envelope and cannot enforce the claimed downgrade. Please deliver this through a typed/non-user peer lane (or an equivalent recipient-owned structural admission), and add an execution-level regression that inspects the actual message role/provenance reaching the model/persistence layer; the current stub only proves that a string was handed to steer().
| finally: | ||
| # Consume even unreadable files so a poison message can't wedge the inbox. | ||
| try: | ||
| path.unlink(missing_ok=True) |
There was a problem hiding this comment.
[P1] Keep the durable message until recipient delivery has settled. This finally unlinks every file before agent.steer(note) is attempted. If steer returns false/raises—or the process dies after unlink but before the in-memory _pending_steer is drained/persisted—the sender already received success: true but the only durable copy is gone. Atomic os.replace protects publication, not consumption. Please use a claim/ack (or equivalent durable handoff) and add a restart/fault regression proving the message is eventually delivered once, without loss or replay.
|
Would love this. I use separate Telegram topics as persistent work contexts within the same Hermes bot, and want one topic to hand work to another without me copying prompts between them. For example, Front Desk could pass a recruiter-related request to Career, which already has the relevant conversation and decisions. Peer discovery and explicit sender provenance are exactly the right direction. The important follow-up for this use case is optional wake-on-delivery: an idle target should be able to start a turn, not wait until I message it again. Ideally it could return a result to the originating session, with busy-session queueing, existing permissions preserved, and loop prevention. Happy to see the mailbox foundation land independently; adding a concrete vote for Telegram topic-to-topic handoffs. |
|
Also, agree. Peer messaging can be a great primitive to have when dealing with multiple threads that may need to interact. |
|
Update: supporting draft #123218 is now at The use case remains two independent local sessions coordinating one project. The draft provides project-scoped discovery, stable message IDs / reply correlation, and opt-in The latest increment is only two files: +47/-26 production lines and 160 lines of unit tests. No new tools, schemas, registrations, hooks or configuration since The surrounding host-injection (#83661), managed-session (#101057) and retained-worker (#106268) scopes remain separate. No automatic wake, session spawn, broadcast, team manager, shared board, new scheduler, generic host API or network transport is introduced. Related initial receive-loss issue: #123217; broader use case: #76221. Current validation: the 18 new unit cases were 16 fail / 2 pass before → 18 pass after. Together with 35 unchanged real-file/process mailbox cases, 53 passed in an isolated Linux runner. Existing lock/atomic-write helpers were source-extracted; registry imports and SessionDB lookups used explicit test seams. Full registry/SessionDB integration, live Desktop/model, Windows and Ruff remain unverified. Full test command and exact limitations are in #123218, which stays draft. |
…icit ACK Continue Teknium's NousResearch#106423 locally without introducing teams or another orchestrator. Keep peer_sessions and peer_send; add peer_receive read/ack inside the existing opt-in registry toolset. Peer content is read as tool data, while the activity hook emits only a fixed content-free inbox hint. Add stable message IDs and reply correlation, non-destructive bounded reads, explicit recipient acknowledgements, compression-lineage delivery, project-scoped discovery and live-owner metadata. Reuse the existing file lock and atomic JSON writer; serialize inbox capacity across processes. Restrict this increment to local CLI/Desktop/TUI conversations so it does not bypass managed-session or Bot authorization. No automatic wake, session spawn, shared board or new core hook. Reads are at-least-once and ACK is not task completion or an exactly-once guarantee. Local validation: 35 filesystem/process tests with isolated import support and 13 frontend contract checks with registry/DB doubles. Full SessionDB, registry and Desktop integration remains unverified; draft PR documents 26 additional integration cases requiring a complete checkout. All changed Python sources compile. Published blobs match the local source hashes. Refs NousResearch#106423 Refs NousResearch#123217
…state Keep this follow-up within NousResearch#106423's local-session coordination scope: no new tools, schemas, hooks, wake behavior or worker infrastructure. Validate each segment returned by the existing compression-chain resolver, not just the tip. Refuse archived/ineligible/conflicting-profile segments and changed branch/reset edges. Apply the same check when reading or acknowledging ancestor inboxes, preserving valid compression delivery. Use only derived activity from browse projections; stale metadata must not overwrite a freshly checked profile, workspace or label. Filter local sources before the 100-row browse limit and request compact rows. 18 new unit cases: 16 fail / 2 pass on ba2c9a2, all 18 pass after. Together with the 35 unchanged mailbox cases: 53 passed in an isolated Linux runner with real files/processes and extracted existing lock/write helpers. Registry imports and SessionDB lookups were test seams, not full Hermes integration. py_compile and diff whitespace checks passed; Ruff, full registry/SessionDB, Windows and live Desktop remain unverified. Refs NousResearch#123218 Refs NousResearch#106423
|
@andrexibiza I traced the comparable harness implementations to check the authority concern against actual delivery paths:
So user-role transport alone has precedent. The narrower question here is consistent attribution and the intended permission model. The draft below retains Teknium's steer-based approach and explicitly describes peer-written input as using that transport, without claiming that wording establishes a structural authority boundary or grants new human permission. On durability, the current real @teknium1 I've opened draft #136861 to bring your implementation onto current This continues your work and is not a request to close #106423. #123218's richer project/read/ACK design remains a separate proposal. Happy for you to take the updated commits back into this PR or use the draft as the current-main integration candidate. |
Follow up on NousResearch#106423 without changing its feature scope. Keep valid messages after read errors or failed steer admission, and remove only malformed payloads before admission. Successful admission consumes the corresponding files; this is not an exactly-once guarantee. Add receive-side regression coverage for read/reject/raise failures and poison payloads adjacent to retryable messages. Refs NousResearch#123217
…icit ACK Continue Teknium's NousResearch#106423 locally without introducing teams or another orchestrator. Keep peer_sessions and peer_send; add peer_receive read/ack inside the existing opt-in registry toolset. Peer content is read as tool data, while the activity hook emits only a fixed content-free inbox hint. Add stable message IDs and reply correlation, non-destructive bounded reads, explicit recipient acknowledgements, compression-lineage delivery, project-scoped discovery and live-owner metadata. Reuse the existing file lock and atomic JSON writer; serialize inbox capacity across processes. Restrict this increment to local CLI/Desktop/TUI conversations so it does not bypass managed-session or Bot authorization. No automatic wake, session spawn, shared board or new core hook. Reads are at-least-once and ACK is not task completion or an exactly-once guarantee. Local validation: 35 filesystem/process tests with isolated import support and 13 frontend contract checks with registry/DB doubles. Full SessionDB, registry and Desktop integration remains unverified; draft PR documents 26 additional integration cases requiring a complete checkout. All changed Python sources compile. Published blobs match the local source hashes. Refs NousResearch#106423 Refs NousResearch#123217
…state Keep this follow-up within NousResearch#106423's local-session coordination scope: no new tools, schemas, hooks, wake behavior or worker infrastructure. Validate each segment returned by the existing compression-chain resolver, not just the tip. Refuse archived/ineligible/conflicting-profile segments and changed branch/reset edges. Apply the same check when reading or acknowledging ancestor inboxes, preserving valid compression delivery. Use only derived activity from browse projections; stale metadata must not overwrite a freshly checked profile, workspace or label. Filter local sources before the 100-row browse limit and request compact rows. 18 new unit cases: 16 fail / 2 pass on ba2c9a2, all 18 pass after. Together with the 35 unchanged mailbox cases: 53 passed in an isolated Linux runner with real files/processes and extracted existing lock/write helpers. Registry imports and SessionDB lookups were test seams, not full Hermes integration. py_compile and diff whitespace checks passed; Ruff, full registry/SessionDB, Windows and live Desktop remain unverified. Refs NousResearch#123218 Refs NousResearch#106423
Follow up on NousResearch#106423 without changing its feature scope. Keep valid messages after read errors or failed steer admission, and remove only malformed payloads before admission. Successful admission consumes the corresponding files; this is not an exactly-once guarantee. Add receive-side regression coverage for read/reject/raise failures and poison payloads adjacent to retryable messages. Refs NousResearch#123217
…icit ACK Continue Teknium's NousResearch#106423 locally without introducing teams or another orchestrator. Keep peer_sessions and peer_send; add peer_receive read/ack inside the existing opt-in registry toolset. Peer content is read as tool data, while the activity hook emits only a fixed content-free inbox hint. Add stable message IDs and reply correlation, non-destructive bounded reads, explicit recipient acknowledgements, compression-lineage delivery, project-scoped discovery and live-owner metadata. Reuse the existing file lock and atomic JSON writer; serialize inbox capacity across processes. Restrict this increment to local CLI/Desktop/TUI conversations so it does not bypass managed-session or Bot authorization. No automatic wake, session spawn, shared board or new core hook. Reads are at-least-once and ACK is not task completion or an exactly-once guarantee. Local validation: 35 filesystem/process tests with isolated import support and 13 frontend contract checks with registry/DB doubles. Full SessionDB, registry and Desktop integration remains unverified; draft PR documents 26 additional integration cases requiring a complete checkout. All changed Python sources compile. Published blobs match the local source hashes. Refs NousResearch#106423 Refs NousResearch#123217
…state Keep this follow-up within NousResearch#106423's local-session coordination scope: no new tools, schemas, hooks, wake behavior or worker infrastructure. Validate each segment returned by the existing compression-chain resolver, not just the tip. Refuse archived/ineligible/conflicting-profile segments and changed branch/reset edges. Apply the same check when reading or acknowledging ancestor inboxes, preserving valid compression delivery. Use only derived activity from browse projections; stale metadata must not overwrite a freshly checked profile, workspace or label. Filter local sources before the 100-row browse limit and request compact rows. 18 new unit cases: 16 fail / 2 pass on ba2c9a2, all 18 pass after. Together with the 35 unchanged mailbox cases: 53 passed in an isolated Linux runner with real files/processes and extracted existing lock/write helpers. Registry imports and SessionDB lookups were test seams, not full Hermes integration. py_compile and diff whitespace checks passed; Ruff, full registry/SessionDB, Windows and live Desktop remain unverified. Refs NousResearch#123218 Refs NousResearch#106423
One session can now warn or unblock another of your live Hermes sessions on the same machine, without you retyping between terminals.
Source
Muse Code left beta on Aug 31 2026 with inter-session messaging: "When a change in one session affects what another is building, that session can pass a warning across. When one session settles a question another is blocked on, it can send the answer... The agent does the sending: it discovers peers and writes the message itself through two built-in tools." Muse Code uses a same-user Unix socket; nothing crosses the network.
What this adds
peer_messagingtoolset (hermes tools→ Peer Messaging) with two tools:peer_sessions— lists the user's other recently active sessions (id, title, source, cwd, last activity) from the session store; the current session and ended sessions are excluded.peer_send— queues a message for an exact session id in a filesystem mailbox at$HERMES_HOME/runtime/peer_messages/<session>/(0700 dir, atomicos.replacepublish — Windows-safe, unlike an AF_UNIX socket). Unknown targets are refused without creating a mailbox; inboxes cap at 50 pending messages; 8K chars/message.agent/activity_tracking._touch_activity) drains the inbox exactly the way kanban operator comments are already injected: a running peer gets the message mid-turn as an OUT-OF-BAND user row after the current tool batch; an idle session gets it at the start of its next turn. No new injection machinery, no cache/alternation risk beyond what /steer already carries.Hermes adaptations vs Muse Code
$HERMES_HOME(atomic rename; works on Windows; idle sessions receive on next turn instead of missing the message)is_dir()per rate-limited poll)$HERMES_HOME(0700) — the same trust domain asstate.dbitselfValidation
origin/main:registry.get_entry("peer_send")→None; nopeer_messagingtoolset — no way for one session to message anotherHERMES_HOME+ realSessionDB: discovery lists the peer and excludes self; send via fullhandle_function_callpath; receive via the production_touch_activityhook steers the message in with sender id + provenance; unknown-target / empty / self-send all refused; failed send creates no mailboxscripts/run_tests.sh tests/tools/test_peer_messaging.py tests/tools/test_registry.py tests/test_toolsets.py tests/agent/test_session_activity.pyruff, windows-footguns, compat pointers,git diff --check2 invariant tests: mailbox round-trip through the registry with provenance + consumption, and the unknown-target/no-inbox no-op contract.
Infographic