Skip to content

feat(tools): peer session messaging — one session can warn or unblock another (inspired by Muse Code) - #106423

Open
teknium1 wants to merge 1 commit into
mainfrom
muse-code-inspired/peer-session-messaging
Open

teknium1 wants to merge 1 commit into
mainfrom
muse-code-inspired/peer-session-messaging

Conversation

@teknium1

@teknium1 teknium1 commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

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

  • New opt-in peer_messaging toolset (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, atomic os.replace publish — Windows-safe, unlike an AF_UNIX socket). Unknown targets are refused without creating a mailbox; inboxes cap at 50 pending messages; 8K chars/message.
  • Delivery rides the existing mid-turn steer channel. The activity hook (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.
  • Provenance is explicit. The injected text attributes the message to the sending session id and states it carries peer-agent — not user — authority.

Hermes adaptations vs Muse Code

Muse Code Hermes
Unix socket between processes Filesystem mailbox under $HERMES_HOME (atomic rename; works on Windows; idle sessions receive on next turn instead of missing the message)
Always-on Opt-in toolset, and receive-side is inert until a mailbox exists (one is_dir() per rate-limited poll)
Socket peer-cred trust Same-user $HERMES_HOME (0700) — the same trust domain as state.db itself

Validation

Check Result
Live repro (before) On origin/main: registry.get_entry("peer_send") → None; no peer_messaging toolset — no way for one session to message another
Live (after) Fresh-interpreter E2E against a temp HERMES_HOME + real SessionDB: discovery lists the peer and excludes self; send via full handle_function_call path; receive via the production _touch_activity hook steers the message in with sender id + provenance; unknown-target / empty / self-send all refused; failed send creates no mailbox
scripts/run_tests.sh tests/tools/test_peer_messaging.py tests/tools/test_registry.py tests/test_toolsets.py tests/agent/test_session_activity.py 79 passed, 0 failed
ruff, windows-footguns, compat pointers, git diff --check clean

2 invariant tests: mailbox round-trip through the registry with provenance + consumption, and the unknown-target/no-inbox no-op contract.

Infographic

Peer Session Messaging

…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.
@github-actions

github-actions Bot commented Sep 9, 2026 •

Copy link
Copy Markdown

૮ >ﻌ< ა ci review

ran on cff14a4 — feat(tools): peer session messaging — sessions message each

⚠️ Warnings

CI timings · View report · View job

Wall time 11m15s vs 4m39s (+141.9%). 8 job(s) slower, 5 faster, 2 unchanged.

  • Docs Site / docs-site-checks: -8.0s
  • Detect affected areas: +8.0s
  • Check no committed infographics / check-no-committed-infographics: -8.0s
  • Python lints / Windows footguns (blocking): +7.0s
  • Check no case-colliding filenames / check-case-collisions: -6.0s

OSV vulnerability scan · View job

71 known vulnerabilities found in pinned dependencies.

How to fix:

Review the findings in the Security tab. Update the affected dependencies if a patched version is available.

@alt-glitch alt-glitch added type/feature New feature or request P3 Low — cosmetic, nice to have comp/tools Tool registry, model_tools, toolsets comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint labels Sep 9, 2026

@andrexibiza andrexibiza left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

  1. 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, and agent/prompt_builder.py::steer_user_row() constructs the standalone role:user row. 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 a role:user envelope. 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 stub steer().

  2. 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_steer is drained into the conversation, peer_send has 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))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

@barronlroth

Copy link
Copy Markdown

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.

@adithyabsk

Copy link
Copy Markdown

Also, agree. Peer messaging can be a great primitive to have when dealing with multiple threads that may need to interact.

Xipong commented Sep 26, 2026 •

Copy link
Copy Markdown

Update: supporting draft #123218 is now at 27dbf4d, still based on this feature branch and preserving your original commit. This is not a replacement PR or a request to close #106423.

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 peer_receive read/ACK. Peer bodies are obtained through normal tool results; the existing activity hook emits only a fixed content-free inbox hint. Reads do not consume envelopes; explicit ACK does. ACK is not task completion or exactly-once execution.

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 ba2c9a29. It fixes stale browse snapshots overwriting fresh profile/workspace/activity metadata, filters eligible local sources before the browse limit, and validates every selected compression segment rather than skipping directly to its tip. Ancestor reads/ACKs use the same checks; valid compression delivery remains covered by positive controls.

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.

Xipong added a commit to Xipong/hermes-agent that referenced this pull request Sep 26, 2026
…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
Xipong added a commit to Xipong/hermes-agent that referenced this pull request Sep 26, 2026
…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
@barronlroth

Copy link
Copy Markdown

@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 steer() accepts nonempty input; a rejecting test double does not establish a normal production rejection. The stronger concern is the crash window after mailbox deletion and before transcript persistence. I reproduced that with real steering in a receiving process that was deliberately terminated before persistence. The draft documents this as best-effort delivery: success means queued, not read/completed, and drained messages can be lost on receiver restart. It does not add an ACK protocol or claim guaranteed delivery.

@teknium1 I've opened draft #136861 to bring your implementation onto current main, preserving your authorship in the first cherry-picked commit. It resolves the three conflicts, adds a recipient-profile compatibility fix, clarifies discovery/send opt-in versus receiving, and adds actual activity → steer → SQLite coverage. Local validation is 107 passing tests, all 12 blocking lint checks passing, and a separate-process mailbox smoke passing. The smoke stops at model-ready message construction/persistence; live-model and Windows runtime validation are still pending.

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.

@alt-glitch alt-glitch added area/sessions Session lifecycle, resume, persistence, history sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state labels Oct 12, 2026
Xipong added a commit to Xipong/hermes-agent that referenced this pull request Oct 15, 2026
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
Xipong added a commit to Xipong/hermes-agent that referenced this pull request Oct 15, 2026
…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
Xipong added a commit to Xipong/hermes-agent that referenced this pull request Oct 15, 2026
…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
Xipong added a commit to Xipong/hermes-agent that referenced this pull request Oct 15, 2026
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
Xipong added a commit to Xipong/hermes-agent that referenced this pull request Oct 15, 2026
…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
Xipong added a commit to Xipong/hermes-agent that referenced this pull request Oct 15, 2026
…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

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/sessions Session lifecycle, resume, persistence, history comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint comp/tools Tool registry, model_tools, toolsets P3 Low — cosmetic, nice to have sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants