Skip to content

✨ feat(kanban): wake origin thread session on every transition - #26

Merged
cwest merged 2 commits into
cwest/integrationfrom
topic/wake-origin-on-transition
Jul 1, 2026
Merged

✨ feat(kanban): wake origin thread session on every transition#26
cwest merged 2 commits into
cwest/integrationfrom
topic/wake-origin-on-transition

Conversation

@cwest

@cwest cwest commented Jul 1, 2026

Copy link
Copy Markdown
Owner

Why

The event-driven autonomy contract — a card transition wakes the ORIGIN thread's session so the orchestrator resumes in-thread and acts — was not met. Proven by a live E2E test: a card moving ready→running→review woke nothing into the origin thread, forcing manual polling. Two defects:

  1. The emit gate covered only blocked / block_loop_detected. A move to review fires status_changed + assigned — neither woke anything.
  2. The webhook route ignored the origin_* fields the emitter sends, so every wake ran in a contextless webhook:<route>:<delivery> session, never the origin thread.

What

  • webhook route: when a payload carries origin_platform/origin_chat_id/origin_thread_id, build a SessionSource targeting the origin thread instead of the synthetic webhook session, so the run resumes the origin session and reports back there. Falls back to the webhook session when origin fields are absent (backward-compatible). The origin source mirrors the live inbound session key (chat_type=thread, chat_id+thread_id set, no per-user suffix) so the wake lands in the exact origin session — not a phantom one (the key-mismatch trap).
  • emit gate: widen DEFAULT_EMIT_KINDS to include actionable lane-move kinds (status_changed, assigned, unblocked) alongside blocked/block_loop_detected. completed stays out deliberately (done is a close-loop chat ping, not a reasoning task).

Tests

  • test_wake_origin_on_transition.py — emit-kind coverage (status_changed/assigned/unblocked now wake; explicit config override still wins; disabled stays dark).
  • test_webhook_origin_routing.py — origin routing targets the origin thread; falls back to webhook session without origin fields; key-mirror invariant (origin source yields the exact live thread session key); unknown-platform + absent-field fallbacks.
  • RED before, GREEN after. Full gateway suite: 333 passed, 14 skipped, 0 failures.

Deploy note

Touches gateway/ modules cached in the running gateway — requires a restart to go live. Config kanban.transition_emit.emit_kinds should be cleared/updated to pick up the new defaults.

The transition-emit bridge stamped origin_* fields on its payload, but the
webhook route ignored them and always minted a contextless
webhook:<route>:<delivery> session — so a woken orchestrator never resumed the
thread the work was born in. And the emit gate covered only blocked/
block_loop_detected, so a card moving to review (status_changed + assigned)
woke nothing at all.

- webhook route: when a payload carries origin_platform/chat_id/thread_id,
  build a SessionSource targeting that origin thread instead of the synthetic
  webhook session, so the run resumes the origin session and reports back
  there. Falls back to the webhook session when origin fields are absent
  (backward-compatible). The origin source is shaped to mirror the live
  inbound session key (chat_type=thread, chat_id+thread_id set, no per-user
  suffix) so the wake lands in the exact origin session, not a phantom one.
- widen DEFAULT_EMIT_KINDS to include the actionable lane-move kinds
  (status_changed, assigned, unblocked) alongside blocked/block_loop_detected;
  completed stays out (done is a close-loop ping, not a reasoning task).
- tests: origin routing + key-mirror invariant + emit-kind coverage; RED
  before, GREEN after; full gateway suite green.

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

The origin-routing half is right and I verified it end to end. The webhook route now builds a SessionSource against the origin thread when origin_platform/chat_id/thread_id are present, falls back to the webhook session when they aren't, and the source is shaped to mirror the live inbound key so the wake lands in the real thread rather than a phantom session. I reran the two new test files against pre-fix source (7 fail) and at head (11 pass), so they genuinely exercise the change, and CI is green after clearing an unrelated flake.

One thing to fix before this lands: the widened wake set is missing the terminal-failure kinds. The emit gate runs inside the notifier loop that delivers NOTIFY_KINDS, which includes gave_up, crashed, and timed_out. Those three reach should_emit_transition exactly like status_changed does, but they're not in the new DEFAULT_EMIT_KINDS, so a worker that gives up, crashes, or times out still pings the chat and wakes no one to act on it. That's the same silent-escalation gap this change is meant to close, just on the failure path instead of the review path. Add them to the set. Detail inline.

Comment thread gateway/kanban_transition_emit.py
Comment thread tests/gateway/test_wake_origin_on_transition.py
DEFAULT_EMIT_KINDS omitted the terminal-failure kinds gave_up, crashed,
and timed_out. Those flow through the same gate as the chat-ping
notifier (should_emit_transition is invoked inside the loop over
NOTIFY_KINDS, whose TERMINAL_KINDS = completed, blocked, gave_up,
crashed, timed_out), so a worker that gave up, crashed, or timed out
pinged chat but woke no orchestrator — the same silent-escalation gap
this change set closes, left open on the failure path.

Add the three kinds to the default emit set so the failure path wakes
the orchestrator exactly like blocked/block_loop_detected do. completed
stays out by design (done is a close-loop chat ping, not a reasoning
task). Add a default-coverage test so the failure-path wake cannot
silently regress.
@cwest
cwest marked this pull request as ready for review July 1, 2026 19:04

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

The rework closes the gap from the last round. DEFAULT_EMIT_KINDS now carries gave_up, crashed, and timed_out, so a worker that fails terminally wakes the orchestrator the same way blocked does — the failure-path twin of the case this change exists to fix. I confirmed the wiring: should_emit_transition runs inside the notifier loop over NOTIFY_KINDS (which includes those three via TERMINAL_KINDS), so before this they pinged chat but woke nothing. The new test pins it: I removed the three kinds and it failed on gave_up, restored them and it passed.

Origin routing is unchanged and was already correct. Targeted suite is green here (12 passed across the wake-origin and webhook-origin-routing modules). Checks are all through, no conversation threads are open, and it merges clean.

Nothing left on my side.

@cwest
cwest merged commit 7f29695 into cwest/integration Jul 1, 2026
31 checks passed
@cwest
cwest deleted the topic/wake-origin-on-transition branch July 1, 2026 19:08
cwest added a commit that referenced this pull request Jul 26, 2026
* ✨ feat(kanban): wake origin thread session on every transition

The transition-emit bridge stamped origin_* fields on its payload, but the
webhook route ignored them and always minted a contextless
webhook:<route>:<delivery> session — so a woken orchestrator never resumed the
thread the work was born in. And the emit gate covered only blocked/
block_loop_detected, so a card moving to review (status_changed + assigned)
woke nothing at all.

- webhook route: when a payload carries origin_platform/chat_id/thread_id,
  build a SessionSource targeting that origin thread instead of the synthetic
  webhook session, so the run resumes the origin session and reports back
  there. Falls back to the webhook session when origin fields are absent
  (backward-compatible). The origin source is shaped to mirror the live
  inbound session key (chat_type=thread, chat_id+thread_id set, no per-user
  suffix) so the wake lands in the exact origin session, not a phantom one.
- widen DEFAULT_EMIT_KINDS to include the actionable lane-move kinds
  (status_changed, assigned, unblocked) alongside blocked/block_loop_detected;
  completed stays out (done is a close-loop ping, not a reasoning task).
- tests: origin routing + key-mirror invariant + emit-kind coverage; RED
  before, GREEN after; full gateway suite green.

* 🐛 fix(kanban): wake orchestrator on terminal-failure transitions

DEFAULT_EMIT_KINDS omitted the terminal-failure kinds gave_up, crashed,
and timed_out. Those flow through the same gate as the chat-ping
notifier (should_emit_transition is invoked inside the loop over
NOTIFY_KINDS, whose TERMINAL_KINDS = completed, blocked, gave_up,
crashed, timed_out), so a worker that gave up, crashed, or timed out
pinged chat but woke no orchestrator — the same silent-escalation gap
this change set closes, left open on the failure path.

Add the three kinds to the default emit set so the failure path wakes
the orchestrator exactly like blocked/block_loop_detected do. completed
stays out by design (done is a close-loop chat ping, not a reasoning
task). Add a default-coverage test so the failure-path wake cannot
silently regress.

(cherry picked from commit 7f29695)
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