fix(kanban): route notifications via owning profile + wake creator agent (salvage #54872) - #56531
Merged
kshitijk4poor merged 3 commits intoJul 1, 2026
Conversation
…gent
Three connected changes that fix kanban notifications in multiplex_profile
gateways and enable event-driven agent collaboration:
1. Session profile propagation
- Add HERMES_SESSION_PROFILE ContextVar (session_context.py)
- Gateway stamps source.profile at dispatch time (run.py)
- _maybe_auto_subscribe reads profile from ContextVar instead of
os.environ which is unset in the gateway main process (kanban_tools.py)
2. Notifier profile-aware routing (kanban_watchers.py)
- Adapter selection: prefer _profile_adapters[sub.notifier_profile]
so each profile's bot delivers its own task notifications
- Relax profile skip-filter: process cross-profile subscriptions when
the gateway has an adapter for the owning profile
- Extend TERMINAL_KINDS with status/archived/unblocked
3. Creator agent wakeup on terminal events (kanban_watchers.py)
- After delivering completed/blocked/gave_up/crashed/timed_out
notifications, inject a synthetic MessageEvent into the creator's
session via adapter.handle_message to trigger their agent loop
- SessionSource built from subscription metadata — no session_store
lookup needed
…arch#54872 Addresses @tonydwb's review on PR NousResearch#54872 (12:05 UTC, 2026-06-29): > the hardcoded Chinese text in the wake messages (lines 118-128 of > the diff) should be replaced with English or internationalized. > The rest of the codebase uses English for user-facing messages, > and hardcoded Chinese will confuse non-Chinese users. Consider > using a constants dict or the existing i18n infrastructure. Used the existing i18n infrastructure (agent/i18n.py::t()) — the same surface gateway/run.py and slash_commands.py already use for static user-facing strings. ## Changes - gateway/kanban_watchers.py: import `t` from agent.i18n; replace the hardcoded Chinese strings in the synthetic wake-up message with t("gateway.kanban.wake.*") lookups. Behavior unchanged for zh users (zh catalog preserves the original Chinese phrasing). - locales/en.yaml: new `gateway.kanban.wake.*` baseline keys (English): completed / gave_up / crashed / timed_out / blocked / status_default / status_joiner / message (with {task_id} {status} {title} {assignee} {board} placeholders). - locales/zh.yaml: Chinese translation of the new keys, preserving the exact wording the original code used (so existing zh users see no visible change). - locales/{zh-hant,ja,de,es,fr,tr,uk,af,ko,it,ga,pt,ru,hu}.yaml: added the same key set with English fallback values. The i18n invariant test (tests/agent/test_i18n.py::test_catalog_keys_match_english) requires every catalog to carry the same key set as en.yaml; native translations can land incrementally without breaking users (the loader falls back to en.yaml per-key when a translation is missing, but the key must still exist). ## Verification - scripts/run_tests.sh tests/agent/test_i18n.py tests/gateway/test_kanban_watchers_mixin.py tests/gateway/test_kanban_notifier.py tests/gateway/test_kanban_notifier_watcher_dispatch_gate.py → 60 passed, 0 failed (i18n catalog parity + placeholders parity + existing kanban notifier behavior). - Manual: with HERMES_LANGUAGE=en, t("gateway.kanban.wake.completed") returns "completed"; with HERMES_LANGUAGE=zh, returns "已完成"; with HERMES_LANGUAGE=ja (translation pending), falls back to "completed" per-key.
…w findings Follow-up review fixes on the salvage of NousResearch#54872 (原作者 张满良/@zmlgit): 1. [HIGH] Adapter selection now goes through the shared _authorization_adapter chokepoint (gateway/authz_mixin.py) instead of a local inline lookup that fell back to the DEFAULT profile's same-platform adapter when the owning profile had a registry entry but no adapter for that platform. That fallback re-introduced the exact cross-profile mis-delivery ([230002] Bot can NOT be out of the chat) this change exists to fix. Adds a mutation-verified guard test (test_notifier_owning_profile_adapter_no_default_fallback). 2. [HIGH→documented] The creator-wake SessionSource cannot faithfully reconstruct a DM/thread creator's session key because chat_type is neither persisted on the subscription nor carried on the session-context bridge. Documented the limitation inline; behavior degrades to a fresh group session (never an exception). The end-to-end fix (stamp + persist chat_type) is a scoped follow-up, not bundled into this salvage. 3. [MED] Documented that archived/unblocked are intentionally claimed (cursor hygiene) but silent, and excluded from wake kinds. 4. [MED] Wake-injection failure now logs at WARNING with exc_info=True (the cursor has already advanced, so a broken wake must not be a silent no-op).
kshitijk4poor
force-pushed
the
salvage-kanban-profile-54872
branch
from
July 1, 2026 18:30
d380a7a to
53388e7
Compare
kshitijk4poor
enabled auto-merge (rebase)
July 1, 2026 18:33
4 tasks
Open
4 tasks
7 tasks
ianks
added a commit
to ianks/hermes-agent
that referenced
this pull request
Jul 15, 2026
Wake-ups for kanban notifications and background delegation completions were injected via handle_message() using a build_session_key()-derived key, which can never match the raw X-Hermes-Session-Id key that api_server sessions run under — so the wake landed in a session nobody was reading. On top of that, ApiServerAdapter.send() reports failure without raising, and that was treated as a successful delivery, so the notify cursor advanced past events that were permanently lost; and background delegation was forced synchronous on api_server since there was no way to wake the session afterward. Fix: route wake-ups for non-push adapters through a self-post to /v1/chat/completions with the original session id, treat non-raising send failures as failures (rewind instead of advancing the cursor), and re-enable background delegation whenever a session id is available to wake. The origin session id is captured from the request-scoped api_server chat_id binding rather than HERMES_SESSION_ID: constructing a child agent calls set_current_session_id() with the subagent's internal id, clobbering that variable right before dispatch would read it and misrouting the wake into the subagent's own session. Related: NousResearch#56580, NousResearch#64609, NousResearch#53027, NousResearch#63169, NousResearch#56531, NousResearch#50319, NousResearch#64113
teknium1
pushed a commit
that referenced
this pull request
Jul 23, 2026
Wake-ups for kanban notifications and background delegation completions were injected via handle_message() using a build_session_key()-derived key, which can never match the raw X-Hermes-Session-Id key that api_server sessions run under — so the wake landed in a session nobody was reading. On top of that, ApiServerAdapter.send() reports failure without raising, and that was treated as a successful delivery, so the notify cursor advanced past events that were permanently lost; and background delegation was forced synchronous on api_server since there was no way to wake the session afterward. Fix: route wake-ups for non-push adapters through a self-post to /v1/chat/completions with the original session id, treat non-raising send failures as failures (rewind instead of advancing the cursor), and re-enable background delegation whenever a session id is available to wake. The origin session id is captured from the request-scoped api_server chat_id binding rather than HERMES_SESSION_ID: constructing a child agent calls set_current_session_id() with the subagent's internal id, clobbering that variable right before dispatch would read it and misrouting the wake into the subagent's own session. Related: #56580, #64609, #53027, #63169, #56531, #50319, #64113
1 task
randlee
pushed a commit
to randlee/hermes-agent
that referenced
this pull request
Aug 11, 2026
Wake-ups for kanban notifications and background delegation completions were injected via handle_message() using a build_session_key()-derived key, which can never match the raw X-Hermes-Session-Id key that api_server sessions run under — so the wake landed in a session nobody was reading. On top of that, ApiServerAdapter.send() reports failure without raising, and that was treated as a successful delivery, so the notify cursor advanced past events that were permanently lost; and background delegation was forced synchronous on api_server since there was no way to wake the session afterward. Fix: route wake-ups for non-push adapters through a self-post to /v1/chat/completions with the original session id, treat non-raising send failures as failures (rewind instead of advancing the cursor), and re-enable background delegation whenever a session id is available to wake. The origin session id is captured from the request-scoped api_server chat_id binding rather than HERMES_SESSION_ID: constructing a child agent calls set_current_session_id() with the subagent's internal id, clobbering that variable right before dispatch would read it and misrouting the wake into the subagent's own session. Related: NousResearch#56580, NousResearch#64609, NousResearch#53027, NousResearch#63169, NousResearch#56531, NousResearch#50319, NousResearch#64113
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
In a
multiplex_profilesgateway, kanban task notifications now deliver via the owning profile's bot adapter (not always the default profile's), and terminal task events wake the creating agent with a synthetic internal message so it can act on the result.Salvage of #54872 by @zmlgit onto current
main(the original branch was ~600 commits behind). Contributor authorship preserved via rebase.Root cause
_maybe_auto_subscribestampednotifier_profilefromos.environ["HERMES_PROFILE"], which is unset in the gateway main process — multiplex profiles use ContextVars, not env vars. The notifier then filtered out cross-profile subscriptions and always delivered via the default adapter, which may not be in the task's chat ([230002] Bot can NOT be out of the chat).Changes
session_context.py: addHERMES_SESSION_PROFILEContextVar (profile=kwarg onset_session_vars, backward-compatible default"").run.py: stampsource.profileat dispatch time.kanban_tools.py:_maybe_auto_subscribereads profile from the ContextVar first, falls back toos.environ(kanban workers).kanban_watchers.py: prefer_profile_adapters[notifier_profile]for delivery; relax the profile skip-filter to only skip when no adapter exists for the owning profile; extendTERMINAL_KINDS; wake the creator agent oncompleted/gave_up/crashed/timed_out/blockedvia aMessageEvent(internal=True)dispatched through the normalhandle_message()pipeline.locales/*.yaml: i18n the wake messages (addresses reviewer feedback on the original PR).Validation
maintest_session_context_inheritance.py+test_local_env_session_leak.py+test_kanban_tools.pyset_session_varscallers"")handle_message, matches existing precedent)Credit: @zmlgit (张满良). Reviewer feedback on hardcoded Chinese text was addressed by the contributor's own follow-up commit (i18n).
Closes #54872