Skip to content

✨ feat(kanban): notify on every card transition, never silent - #25

Merged
cwest merged 1 commit into
cwest/integrationfrom
topic/kanban-notify-all-transitions
Jul 1, 2026
Merged

✨ feat(kanban): notify on every card transition, never silent#25
cwest merged 1 commit into
cwest/integrationfrom
topic/kanban-notify-all-transitions

Conversation

@cwest

@cwest cwest commented Jul 1, 2026

Copy link
Copy Markdown
Owner

Why

The kanban notifier gate delivered only five terminal event kinds
(completed/blocked/gave_up/crashed/timed_out) and silently dropped
every other lane change — assigned, unblocked, and critically
block_loop_detected, the auto-escalate-to-triage signal (the system asking
for a human). Proven live: a card hit BLOCK_RECURRENCE_LIMIT, auto-escalated to
triage, emitted block_loop_detected — not in the terminal set — so no delivery
and no wake fired. The card sat silent in triage. Compounding: a card carrying no
origin subscription had nowhere for even a terminal event to land.

What

  • Widen the delivery gate to NOTIFY_KINDS: the terminal five plus the
    meaningful lane changes (block_loop_detected, unblocked, assigned,
    promoted, reclaimed, stale, dependency_wait). High-frequency bookkeeping
    kinds (heartbeat/claimed/commented/spawned/…) stay excluded so routine churn
    does not ping.
  • Per-kind message wording for the new kinds, with a generic transition line
    for any remaining NOTIFY_KIND so a lane change is never silent.
  • Fallback delivery target for a card with NO subscription: lazily register a
    fallback subscription to the default channel, cursor-seeded to the latest
    notifiable event so only the live transition fires (no historical backfill
    flood). Channel/platform config-overridable via kanban.notify_fallback; empty
    chat_id opts out.
  • initial_cursor seed in add_notify_sub (INSERT-only) so the fallback sub
    never replays a task's entire event history.
  • Agent-run bridge default kinds extended to include block_loop_detected so
    the triage escalation wakes the orchestrator, carrying origin session/thread
    context (unchanged F1/F2/F3 payload contract).

Verification

New E2E tests against a temp HERMES_KANBAN_DB (real notifier path, not mocked
green):

  • an assigned lane change delivers a chat ping;
  • a block_loop_detected / triage escalation delivers a ping AND fires the
    agent-run bridge;
  • a card with no subscription surfaces to the fallback channel.

All three RED before the change, GREEN after. The terminal-kind + origin-payload
contract is unregressed (test_kanban_transition_emit.py /
test_thread_origin_autonomy.py: 34 passed). Full kanban suite: the only
failures are pre-existing cross-file test-pollution present identically on the
base (verified — they pass in isolation and appear with 796 passed on base vs 799
with this change); this change adds +3 passing and regresses nothing.

The notifier gate delivered only five terminal event kinds
(completed/blocked/gave_up/crashed/timed_out) and silently dropped every
other lane change — assigned, unblocked, and critically
block_loop_detected, the auto-escalate-to-triage signal (the system
asking for a human). A card could exhaust its block-recurrence limit,
escalate to triage, and sit completely silent.

Changes:
- Widen the delivery gate to NOTIFY_KINDS: the terminal five plus the
  meaningful lane changes (block_loop_detected, unblocked, assigned,
  promoted, reclaimed, stale, dependency_wait). High-frequency
  bookkeeping kinds stay excluded so routine churn does not ping.
- Add per-kind message wording for the new kinds, with a generic
  transition line for any remaining NOTIFY_KIND so a lane change is
  never silent.
- Guarantee a delivery target for a card with NO subscription: lazily
  register a fallback subscription to the default channel, cursor-seeded
  to the latest notifiable event so only the live transition fires (no
  historical backfill flood). Channel/platform config-overridable via
  kanban.notify_fallback; empty chat_id opts out.
- Seed support in add_notify_sub via initial_cursor (INSERT-only) so the
  fallback sub never replays a task's entire event history.
- Extend the agent-run bridge default kinds to include
  block_loop_detected so the triage escalation wakes the orchestrator,
  carrying origin session/thread context.

Tests: new E2E tests against a temp HERMES_KANBAN_DB prove an assigned
lane change delivers, a block_loop_detected escalation delivers AND
wakes, and a no-subscription card surfaces to the fallback channel. All
RED before the change, GREEN after; the terminal-kind and origin-payload
contract is unregressed.
@cwest
cwest marked this pull request as ready for review July 1, 2026 17:39

@cwest cwest left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

No changes needed.

The delivery gate widening is the right shape: NOTIFY_KINDS keeps the terminal five and adds the lane changes that actually matter (block_loop_detected above all), while leaving the high-frequency bookkeeping kinds out so routine churn stays quiet. The per-kind wording plus the generic fallback line means a kind in the set can't slip through unhandled.

The no-subscription fallback holds up under the scrutiny it needs. The cursor is seeded to the second-newest notifiable event (or one below a lone event), so a card that transitions with no origin sub delivers only its latest transition, never a replay of its history. The seed applies on INSERT only, so re-registering never rewinds a live cursor, and once the fallback sub exists the card flows through the normal claim/cursor path on later ticks with no duplicate. The send-failure key matches the delivery loop's key, and done/archived cards are skipped so a completed task can't re-fire through the fallback.

add_notify_sub's initial_cursor is INSERT-only and defaults to 0, so existing callers are unaffected. block_loop_detected in the emit default kinds carries the origin session and thread, so the triage escalation wakes the orchestrator in the thread the work came from.

Verification: the three new tests fail on the base and pass with the change; assigned/block_loop_detected/no-sub-fallback are each exercised against a temp DB through the real notifier path. The kanban suite runs 799 passed with the change. The 19 failures are present identically on the base commit and don't touch the notifier code path (cross-file test pollution that passes in isolation, plus two environment- and timing-sensitive tests). Signed commit, mergeable and CLEAN, no open threads.

@cwest
cwest merged commit 1520500 into cwest/integration Jul 1, 2026
31 checks passed
@cwest
cwest deleted the topic/kanban-notify-all-transitions branch July 1, 2026 17:51
cwest added a commit that referenced this pull request Jul 26, 2026
The notifier gate delivered only five terminal event kinds
(completed/blocked/gave_up/crashed/timed_out) and silently dropped every
other lane change — assigned, unblocked, and critically
block_loop_detected, the auto-escalate-to-triage signal (the system
asking for a human). A card could exhaust its block-recurrence limit,
escalate to triage, and sit completely silent.

Changes:
- Widen the delivery gate to NOTIFY_KINDS: the terminal five plus the
  meaningful lane changes (block_loop_detected, unblocked, assigned,
  promoted, reclaimed, stale, dependency_wait). High-frequency
  bookkeeping kinds stay excluded so routine churn does not ping.
- Add per-kind message wording for the new kinds, with a generic
  transition line for any remaining NOTIFY_KIND so a lane change is
  never silent.
- Guarantee a delivery target for a card with NO subscription: lazily
  register a fallback subscription to the default channel, cursor-seeded
  to the latest notifiable event so only the live transition fires (no
  historical backfill flood). Channel/platform config-overridable via
  kanban.notify_fallback; empty chat_id opts out.
- Seed support in add_notify_sub via initial_cursor (INSERT-only) so the
  fallback sub never replays a task's entire event history.
- Extend the agent-run bridge default kinds to include
  block_loop_detected so the triage escalation wakes the orchestrator,
  carrying origin session/thread context.

Tests: new E2E tests against a temp HERMES_KANBAN_DB prove an assigned
lane change delivers, a block_loop_detected escalation delivers AND
wakes, and a no-subscription card surfaces to the fallback channel. All
RED before the change, GREEN after; the terminal-kind and origin-payload
contract is unregressed.

(cherry picked from commit 1520500)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant