🐛 fix(kanban): self-announce transition wake with a unique greppable banner - #30
Conversation
dec552d to
d91f262
Compare
cwest
left a comment
There was a problem hiding this comment.
No changes needed. The banner mechanism holds up under scrutiny.
I ran the touched-area suites in a throwaway checkout at the head SHA: banner + prepend + e2e + emit + emit_http all green (21 passed), the full webhook suite green (121 passed), and the watcher/notify-transition suites green (14 passed). ruff clean on all six changed files. CI on the PR is CLEAN, mergeable, no unresolved threads.
I also verified the thing that matters most here by ground truth, not by reading code: does the banner actually survive as the FIRST line of the woken prompt when the real route is in play? The production route (webhook-ensure-kanban-transition-route.py) carries skills: [kanban-transition-orchestrate], and the webhook handler runs the banner prepend BEFORE the skill-injection block. I stood up the actual _handle_webhook route with that exact skills config and posted a status_changed body: the first line came back as the verbatim banner. The skill-injection branch does not fire for this skill in the webhook adapter's runtime, so it never re-wraps the prompt. Good — the contract is met on the config that ships.
One observation left inline about the fragility of that ordering. Not blocking.
…banner
A transition wake now leads its woken turn with a fixed-prefix, greppable
banner stamping task_id + kind + from->to lane + event_id
(`AUTONOMOUS-WAKE t_XXXX <kind> <from>-><to> evt=NNNN`), so the woken output
is unmistakable and can never be conflated with a late-delivered prior reply.
- kanban_transition_emit: add `build_wake_banner` (single source of truth for
the banner shape) and stamp `wake_banner` + `from_lane`/`to_lane` into the
emit payload. Lane ends degrade to `?` when a kind carries no from->to pair;
the banner is deterministic per (task_id, kind, from, to, event_id) so each
wake is unique and stable to grep.
- kanban_watchers: extract the event's `{"from","to"}` hop and pass it through
so status_changed/assigned wakes carry their real lane transition.
- webhook route: when a payload carries `wake_banner`, guarantee the woken
run's prompt LEADS with it verbatim as the first line, regardless of the
route's prompt template. Scoped to payloads that carry the banner, so
ordinary webhooks are untouched.
Tests: RED->GREEN for the banner builder, payload stamping, and the route
prepend (incl. empty-template and non-transition-untouched cases).
d91f262 to
2afcf34
Compare
…banner (#30) A transition wake now leads its woken turn with a fixed-prefix, greppable banner stamping task_id + kind + from->to lane + event_id (`AUTONOMOUS-WAKE t_XXXX <kind> <from>-><to> evt=NNNN`), so the woken output is unmistakable and can never be conflated with a late-delivered prior reply. - kanban_transition_emit: add `build_wake_banner` (single source of truth for the banner shape) and stamp `wake_banner` + `from_lane`/`to_lane` into the emit payload. Lane ends degrade to `?` when a kind carries no from->to pair; the banner is deterministic per (task_id, kind, from, to, event_id) so each wake is unique and stable to grep. - kanban_watchers: extract the event's `{"from","to"}` hop and pass it through so status_changed/assigned wakes carry their real lane transition. - webhook route: when a payload carries `wake_banner`, guarantee the woken run's prompt LEADS with it verbatim as the first line, regardless of the route's prompt template. Scoped to payloads that carry the banner, so ordinary webhooks are untouched. Tests: RED->GREEN for the banner builder, payload stamping, and the route prepend (incl. empty-template and non-transition-untouched cases). (cherry picked from commit d12ff0a)
Why
Piece 2 of the event-driven-autonomy gap. Once a kanban transition wakes the
orchestrator as its own turn, that woken output must be unmistakable so it
can be identified and acted on. Previously the woken reply looked like any other
message and got conflated with a late-delivered prior reply.
What
A transition wake now leads its woken turn with a fixed-prefix, greppable banner
carrying the full transition coordinates:
e.g.
AUTONOMOUS-WAKE t_abc blocked ready->blocked evt=4242.gateway/kanban_transition_emit.py— newbuild_wake_banner()(the singlesource of truth for the banner shape) plus a
WAKE_BANNER_PREFIXsentinel;build_transition_payload()now stampswake_banner+from_lane/to_laneinto the emit payload. Lane ends degrade to
?when a kind carries nofrom→to pair; the banner is deterministic per
(task_id, kind, from, to, event_id)so each wake is unique and stable to grep.gateway/kanban_watchers.py— extract the event's{"from","to"}hop andpass it through, so
status_changed/assignedwakes carry their real lanetransition.
gateway/platforms/webhook.py— when a payload carrieswake_banner,guarantee the woken run's prompt leads with it verbatim as the first line,
regardless of the route's prompt template. Scoped to payloads that carry the
banner, so ordinary webhooks are untouched.
Cheap and additive: no invariant breakage (prompt-cache / role-alternation
untouched — the banner rides the existing woken-turn prompt), byte-stable for the
no-banner case.
Tests
RED→GREEN:
tests/gateway/test_kanban_transition_wake_banner.py— banner prefix,full-coordinate shape, determinism/event-scoping, lane-unknown degradation
(never
None), and payload stamping.tests/gateway/test_webhook_wake_banner_prepend.py— the woken prompt's firstline is the banner (with a normal template, with an empty template), and a
non-transition webhook is left untouched.
25 focused kanban/webhook/transition/notifier/watcher tests pass together; the
touched-area modules are green (the full one-process gateway suite has a
pre-existing, change-independent contamination profile reproduced identically on
the untouched base).