fix(tui): deliver Kanban notifications to TUI sessions - #66435
Conversation
kanban_create auto-subscribes TUI/desktop sessions with platform="tui" and chat_id=HERMES_SESSION_KEY, and tools/kanban_tools.py documents that the TUI notification poller (tui_gateway/server.py) reads kanban_notify_subs and posts completion messages into the running session — but that reader was never implemented. The poller only watched process_registry completion events, and the gateway notifier skips "tui" rows because no such messaging adapter exists. Result: subscriptions accumulate with last_event_id=0 forever and no task event is ever delivered (18 subs, 29 terminal events, 0 deliveries in the report). Implement the missing delivery path in the TUI notification poller: - every 5s, claim unseen terminal events for this session's platform="tui" subscriptions via claim_unseen_events_for_sub — the same atomic cursor-claim the gateway notifier uses, so an event is delivered exactly once even with a gateway polling the same board DB - format events with the same wording as the gateway notifier (done/blocked/gave up/crashed/timed out/status; archived and unblocked are claimed but silent, so they can't wedge the cursor) - emit a status.update for user visibility, then chain an agent turn when the session is idle — mirroring process-completion handling; claimed events buffer in the session until it goes idle since the cursor (unlike the process queue) cannot re-queue - unsubscribe only at a truly final task status (done/archived), matching the gateway rule so respawned tasks keep notifying - multi-board: iterate boards, polling each resolved DB path once Fixes NousResearch#59890
Covers the poller wiring above _collect_kanban_notifications, per the hermes-sweeper review: status.update emission, agent-turn dispatch via _run_prompt_submit when the session is idle, and the busy-session pending buffer that flushes once the session goes idle.
Related: #60085 is the stale predecessor for this current-main salvage, while #59963 is a competing narrower implementation. This branch uses the per-session key, handles multi-board delivery and terminal cleanup, and buffers/wakes the TUI session safely; maintainer selection is needed. |
|
Thank you — appreciate the careful salvage. Confirmed the cherry-pick is faithful: both commits carry the original |
teknium1
left a comment
There was a problem hiding this comment.
Thanks for preserving the original authorship and adding poller-level coverage. The current-main premise is verified: tools/kanban_tools.py:1204-1207 writes/document TUI subscriptions, while current tui_gateway/server.py:9123-9125 only polls process completions.
Problems
- Profile routing: the new collector resolves boards at
tui_gateway/server.py:9187, but does not bindsession["profile_home"]. Global-remote Desktop sessions explicitly retain this field and bind it around their agent construction attui_gateway/server.py:1522-1534;set_hermes_home_overrideis context-local (hermes_constants.py:23-35). Because the poller is a separate thread, a non-launch profile session will poll the launch profile's Kanban DB instead of the profile that created its subscription.
Suggested changes
- Scope the collector's Kanban reads to
session["profile_home"]using the existing override/reset helpers, and add a two-profile regression test proving the selected profile's subscription is claimed.
Automated hermes-sweeper review.
| return [] | ||
| texts: list = [] | ||
| try: | ||
| boards = _kb.list_boards(include_archived=False) |
There was a problem hiding this comment.
This board lookup must be scoped to session["profile_home"]. In global-remote Desktop mode, non-launch sessions persist that profile home and build turns under a context-local override; this poller runs in another thread and otherwise reads the launch profile's Kanban DB, so it cannot see the subscription created by the selected profile.
… override The sweeper review on #66435 flagged that the collector doesn't bind session["profile_home"]. That binding is intentionally unnecessary: the kanban board is shared across profiles by design — kanban_home() anchors on get_default_hermes_root(), which resolves the process env and ignores context-local profile overrides (see the kanban_db.py module docstring). Add a regression test that claims a subscription while a foreign-profile set_hermes_home_override() is active, proving delivery still works for non-launch-profile Desktop sessions.
|
Merged via PR #72177 with both commits cherry-picked onto current main and @falkoro's authorship preserved in git log (badb240, 6247712). Thanks @cucurigoo for the faithful current-main salvage and @falkoro for the original fix and poller-level tests. One note on the sweeper's profile_home concern: the suggested binding turned out to be intentionally unnecessary — the kanban board is shared across profiles by design ( |
… override The sweeper review on NousResearch#66435 flagged that the collector doesn't bind session["profile_home"]. That binding is intentionally unnecessary: the kanban board is shared across profiles by design — kanban_home() anchors on get_default_hermes_root(), which resolves the process env and ignores context-local profile overrides (see the kanban_db.py module docstring). Add a regression test that claims a subscription while a foreign-profile set_hermes_home_override() is active, proving delivery still works for non-launch-profile Desktop sessions.
Summary
Current Hermes
mainstill createsplatform="tui"Kanban notification subscriptions but has no TUI consumer for them. Terminal task events therefore remain intask_eventswhilekanban_notify_subs.last_event_idstays at zero, so a TUI/Desktop orchestrator never receives the documented callback or wake turn.This is a current-main salvage/rebase of #60085. It preserves @falkoro's authorship and resolves the one docstring conflict introduced by later multi-session notification ownership hardening on
main.kanban_notify_subsfor the exact live TUI session key;done/archived;unblocked/archivedsilently so they cannot wedge later events.Fixes #59890.
Also addresses the TUI half of #59960.
Supersedes #60085 only if maintainers prefer this current-main salvage branch.
Reproduction confirmed
On stock Docker Hermes v2026.7.7.2, a TUI-created card correctly wrote a
platform="tui"subscription. The worker then emitted ablockedevent, but the subscription cursor remained0and no orchestrator turn ran. Currentmainstill contains the producer-side comment claimingtui_gateway/server.pyconsumes these rows, whiletui_gateway/server.pyhas no such consumer.Verification on current main
Rebased onto
594308d4bbe95548c9fe418bb10c449099426f93.pytest tests/tui_gateway/test_kanban_notify_poller.py ...— 10 focused regression tests pass.ruff check tui_gateway/server.py tests/tui_gateway/test_kanban_notify_poller.py— passed.python -m py_compile ...— passed.git diff --check— passed.The broader parallel TUI/gateway slice shows the existing order-dependent
test_prompt_submit_rejected_while_child_run_activefailure. The same failure reproduces on untouched currentmain; it passes in the focused serial run and is unrelated to this patch.Scope
Two files only:
tui_gateway/server.pytests/tui_gateway/test_kanban_notify_poller.pyNo gateway platform behavior, Kanban schema, model tool schema, or deployed runtime configuration changes.