Skip to content

feat: deliver spawn approvals to the originating telegram channel - #9242

Open
cschnidr wants to merge 3 commits into
kirodotdev:mainfrom
cschnidr:feat/telegram-spawn-approval-delivery-2381
Open

feat: deliver spawn approvals to the originating telegram channel#9242
cschnidr wants to merge 3 commits into
kirodotdev:mainfrom
cschnidr:feat/telegram-spawn-approval-delivery-2381

Conversation

@cschnidr

@cschnidr cschnidr commented Sep 7, 2026

Copy link
Copy Markdown

Problem / Motivation

Follow-up to merged PR #8914 on issue #2381. #8914 fixed the 1800s hang: a subagent spawn whose approval prompt reaches no surface now fast-fails instead of stalling until the reaper. But issue #2381 item 1 stayed open: a spawn approval still could not be answered from its originating Telegram channel. The single host-wide spawn gate raced only a Slack owner DM and the dashboard, so a spawn parented on a Telegram conversation had no in-channel way to be approved. A Telegram user either watched the dashboard or the spawn fast-failed.

Why it matters

Subagent orchestration is effectively unusable from Telegram: every spawn_run from a Telegram-originated turn cannot be approved where the user actually is. Post-#8914 it fails fast rather than hanging, which is better than a 30-minute silent stall, but the user still cannot say "yes, run it" from the channel that started the turn. Delivering the prompt in-channel (with an optional Trust action) restores spawn orchestration for chat-channel users and realizes the issue's suggested fixes (a) deliver the prompt to the originating channel and (b) let a trusted session auto-approve future spawns.

What changed (motivation → approach → change)

Goal: let a Telegram user answer a spawn approval in the originating channel, without weakening the #8914 fast-fail backstop and without adding the per-agent auto-approve rung (issue item 2, deferred by the commenter to #4751/#4693).

Approach: rather than teaching the host spawn gate about every channel (the coupling the gate exists to avoid), introduce a channel-neutral delivery seam modelled on messaging/session_trust.py. The host callback consults the seam first; a channel that owns the parent session registers a hook and answers in-channel; anything else falls through to the unchanged Slack-DM/dashboard path, and then to the #8914 fast-fail backstop. Fall-through is fail-open toward the existing gate: no hook, an unowned/non-channel key, a hook returning None, or a hook that raises all collapse to the same "fall through", so a channel-delivery bug can never turn an answerable spawn into a hard failure and can never bypass the backstop.

What was built:

  • New seam src/kiro_crew/messaging/spawn_approval_delivery.py: an in-memory, process-global registry mapping a channel namespace (telegram, slack, …) to an async delivery hook (request_id, description, parent_session_key) -> bool | None. Register/unregister/resolve/deliver, keyed by channel_namespace_of(parent_session_key), raise-contained to None.
  • Host spawn gate (src/kiro_crew/slack/gateway.py _spawn_approve) now consults the seam first; True/False is the in-channel decision, None falls through to the unchanged _approve_spawn_gate (Slack DM + dashboard, still raising SpawnApprovalUnreachable when nothing is attached — the fix(subagent): refuse a spawn whose approval prompt has no surface (#2381) #8914 backstop, unchanged).
  • Telegram delivery hook (src/kiro_crew/telegram/transport_dispatch.py deliver_spawn_approval + _spawn_chat_target): reconstructs the originating chat from the parent session key grammar, posts an Approve/Deny/Trust inline keyboard for spawn:<id>, and awaits the press via the existing TelegramApprovalDecider/on_callback a: path. Trust routes through add_trusted_session so future spawns from that session auto-approve, scoped to exactly the parent session key the prompt was armed under.
  • Lifecycle (src/kiro_crew/telegram/gateway.py, src/kiro_crew/telegram/client.py, src/kiro_crew/telegram/renderer.py): the hook is registered on gateway startup and unregistered on client close (new on_close); TelegramApprovalDecider.retire() drops an armed nonce whose prompt never posted.
  • Docs updated in the same change: docs/system-specs/modules/subagent.md and docs/system-specs/modules/messaging.md document the delivery order (channel hook → Slack-DM/dashboard fallback → fix(subagent): refuse a spawn whose approval prompt has no surface (#2381) #8914 fast-fail backstop) and the seam.

Tests

  • test/test_spawn_approval_channel_delivery_2381.py (new): the seam's register/resolve/unregister/isolation/replace, raise-containment to None, and the None fall-through; the wrapped host callback's True/False/None/no-hook behavior; a source ratchet that _spawn_approve consults the seam before the gate; the Telegram hook's Approve→True, Deny→False, Trust→True-plus-add_trusted_session (with is_session_trusted asserted), the three-button keyboard shape, non-telegram fall-through, failed-post retiring the nonce, generation-rotation expiry, forum-Topic threading, and cancellation-leak; and a precedence test that a trusted/auto parent never reaches the channel prompt.
  • test/test_spawn_approval_no_surface_2381.py (the fix(subagent): refuse a spawn whose approval prompt has no surface (#2381) #8914 regression tests): unchanged and still green, confirming the fast-fail backstop and the separate tool-vs-spawn callbacks are intact.
  • test/test_messaging_driver.py: unchanged, still green.

Manual verification

N/A — unit coverage is sufficient. The delivery seam, the wrapped host callback, the real TelegramApprovalDecider.deliver_spawn_approval, and the on_callback press path (including Trust → add_trusted_session) are all driven directly by the new tests rather than mocked away; the commenter on #2381 also offered to test the follow-up against their headless Telegram setup.

Related Issues

Refs #2381
Follows #8914 (fast-fail backstop, preserved unchanged)
Defers the per-agent auto-approve rung (issue item 2) to #4751 / #4693

…#2381)

Address the four non-blocking review issues on the approved v1:

- Pin the armed-key vs recomputed-key coupling: add a test that rotates
  the parent conversation's generation between spawn and press and asserts
  the prompt deny-by-defaults; note the invalidation in the
  deliver_spawn_approval docstring.
- Cover the CHAT_TYPE_FORUM branch of _spawn_chat_target with a forum-parent
  test asserting the prompt is threaded into the originating Topic.
- Confirm a cancelled awaiting decider mid-prompt leaks no registry/nonce
  entry (the __call__ finally already covers cancellation).
- Align the seam-level delivery-failure warning to also name the request id,
  matching the dispatcher-level warning's granularity.
@cschnidr
cschnidr requested a review from a team as a code owner September 7, 2026 13:15
@cschnidr
cschnidr requested a review from cixuuz September 7, 2026 13:15
@github-actions github-actions Bot added fork Pull request from a fork (external contributor) readiness: action required A blocking check or review needs attention labels Sep 7, 2026
@dwu96

dwu96 commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

👋 Hi! This PR's description is missing some required sections from our PR template. Workflow runs won't be auto-approved until the description is updated.

Missing sections:

  • ## Problem / Motivation
  • ## Why it matters
  • ## Tests

Please update your PR description to include these sections, then push or re-save the description. The workflows will be approved on the next cycle.

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Design Review (Fable 5.1, fork) — ✅ PASS

Design-level review of 8954ee8b9484764746befdfc6b72564b9f074189 via the fork AI-review pipeline — updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

Design-Verdict: PASS

Sound, proportionate fix: a channel-neutral seam gives Telegram-parented spawns the originating channel's own approval keyboard, mirroring the established session_trust inversion, with clean fall-through and failure containment.

Suggestions

  • transport_dispatch.py docstring (patch lines 464–466) says a generation-rotation timeout means "the gate simply falls through to Slack/dashboard when the wait elapses." It doesn't: TelegramApprovalDecider.__call__ returns False on timeout (renderer.py:848), so the hook returns False — a hard deny the gate uses verbatim, no fall-through. Correct the docstring to "deny-by-default at timeout" so the None-vs-decision contract stays accurately documented.

[DESIGN-REVIEWED] 8954ee8

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

First Principles Review (Fable 5.1, fork) — ✅ PASS

Premise-level review of 8954ee8b9484764746befdfc6b72564b9f074189 via the fork AI-review pipeline — why this exists and whether the shipped surface is the smallest honest version. Updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

Verified: channel_namespace_of, add_trusted_session, the existing Approve/Deny/Trust keyboard (renderer.py:1864), and the base _spawn_approve all exist as the patch describes. The seam reuses existing machinery rather than inventing a parallel path, and Discord/Teams/WhatsApp exist as real sibling channels sharing the one host spawn gate — so the channel-neutral design is justified generality, not premature.

First-Principles-Verdict: PASS

Delivers spawn approvals to the Telegram chat that started the turn via a seam every channel can register into; each item removes a named harm and reuses existing infra.

What this change ships

Intent: let a Telegram user answer a sub-agent spawn approval in the originating chat instead of watching the dashboard or eating the #8914 fast-fail. ADDITION (completes issue #2381 item 1).

  1. Telegram users can approve/deny/trust a spawn from the originating chat — justified (Telegram orchestration was unusable; only fast-fail before)
  2. Channel-neutral delivery registry (spawn_approval_delivery.py) keyed by namespace — justified (siblings discord/teams/whatsapp share the same host gate)
  3. Host spawn gate consults the channel seam before the Slack-DM/dashboard fallback — justified (mechanism-level, fail-open to the unchanged gate + fix(subagent): refuse a spawn whose approval prompt has no surface (#2381) #8914 backstop)
  4. "Trust this conversation" on the spawn prompt auto-approves future spawns — justified (reuses existing add_trusted_session + a: callback path, not a new rung)
  5. TelegramClient.on_close teardown hook — justified (unregisters the process-global hook so the gate stops routing to a dead dispatcher)
  6. TelegramApprovalDecider.retire() drops an armed nonce when the post fails — justified (arm-without-wait would leak a stale nonce)
  7. subagent.md / messaging.md updated same-commit — derived (AGENTS.md invariant)

Generality is bounded by counted siblings, the seam falls open to the preserved #8914 backstop, and every rider is declared. No item's zero option is free; none duplicates an existing mechanism; the fix sits at mechanism/cause level.

[FIRST-PRINCIPLES-REVIEWED] 8954ee8

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

GPT 5.6 Review (fork) — ✅ no blocking findings

Reviewed 8954ee8b9484764746befdfc6b72564b9f074189 via the fork AI-review pipeline; updated in place on each push.

Review details

FINDING -- src/kiro_crew/telegram/transport_dispatch.py:2733 -- "falls through" contradicts the timeout returning False, which denies the spawn -> Fix: document that timeout denies.

FINDING -- src/kiro_crew/telegram/transport_dispatch.py:2769 -- ignoring send_message() returning None on API failure waits on an invisible prompt instead of falling through -> Fix: retire and return None when sending returns None. (origin: validation)

[GPT-REVIEWED] 8954ee8

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Opus 4.8 Review (fork) — ✅ no blocking findings

Reviewed 8954ee8b9484764746befdfc6b72564b9f074189 via the fork AI-review pipeline; updated in place on each push.

Review details

No findings.

[OPUS-REVIEWED] 8954ee8

@cschnidr cschnidr changed the title Deliver spawn approvals to the originating channel (Telegram) (#2381) feat: deliver spawn approvals to the originating telegram channel Sep 7, 2026
@github-actions github-actions Bot added readiness: checking Automated validation is still running and removed readiness: action required A blocking check or review needs attention labels Sep 7, 2026

cschnidr commented Sep 7, 2026

Copy link
Copy Markdown
Author

Heads-up for a maintainer: this is a fork PR and the latest workflow run is sitting at action_required, waiting for an "Approve and run" click.

Context: the only red gate on the previous run was PR Hygiene → "Validate PR title (conventional commits)". The title lacked a type prefix. I've fixed it to feat: deliver spawn approvals to the originating telegram channel, which satisfies the gate. Every other check on the head commit was green (all backend/Windows/frontend tests, builds, SAST, coverage, docs lint, harness parity, and all five AI reviews).

Once someone approves the pending workflow run, PR Hygiene should re-run with the corrected title and PR Readiness should flip green. Thanks!

@github-actions github-actions Bot added readiness: action required A blocking check or review needs attention and removed readiness: checking Automated validation is still running labels Sep 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fork Pull request from a fork (external contributor) readiness: action required A blocking check or review needs attention

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants