fix(inbox): a mirrored prompt must be answerable where it arrives - #625
Open
djpentz wants to merge 2 commits into
Open
fix(inbox): a mirrored prompt must be answerable where it arrives#625djpentz wants to merge 2 commits into
djpentz wants to merge 2 commits into
Conversation
`buttons_for` returns buttons for approvals and option questions, and `mirror_inbox_item` branched on that alone. But only an adapter implementing `send_interactive` can draw them — the base class quietly falls back to plain text, and Telegram (like any adapter that hasn't implemented it) takes that path. The result on those platforms: the reader gets a question with no buttons, no `[ow:<id>]` tag for `_resolve_inbox_reply` to correlate a reply against, and no instructions — while the text branch, used for free-text questions, carries both. Approvals and option questions are exactly the prompts that suspend an agent, so the effect is an agent waiting forever on a question nobody could answer. Found in production: a scheduled task parked a `web_fetch` approval at 07:00, its Telegram card could not be answered, and the task stayed suspended for 36 hours — taking the next day's run with it, since skip-on-overlap declines to start a run while the previous one is still going. Adapters now declare `supports_interactive` (True on Slack, False by default), the gateway answers that question per target, and the mirror picks the branch on capability rather than on the presence of buttons. The text fallback names the words the reply parser accepts — "approve"/"deny", or the option labels — and keeps the tag. Slack is unchanged. Tests cover all three paths and fail against the previous behaviour.
Author
The card is answerable now, but only with approve or deny — and for a scheduled run those are both one-offs. The answer that makes a routine stop asking, "always", was reachable solely as a button in an app, so a prompt arriving on a phone at 07:00 could be cleared but never silenced, and the same question came back the next morning. That is the whole complaint about unattended work: it is not unattended if it needs a laptop. The reply parser learns a third intent, mapping to the same `always_task` resolution the in-app card sends. It is gated where it means nothing: on a question it is taken as a plain yes rather than stored as the answer text, and the card only advertises it when the item belongs to a routine — in a plain session it would resolve as a one-off and read as a lie.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

The bug
mirror_inbox_itemdecides how to send a prompt by asking whether the item hasbuttons. But having buttons and being able to draw them are different questions:
BasePlatformAdapter.send_interactivefalls back to plainsend(), and onlySlackAdapteroverrides it. On Telegram — and on any adapter that hasn't implementedit — the interactive branch therefore sends bare text, dropping two things the other
branch includes:
[ow:<id>]tag, which is the only thing_resolve_inbox_replycan correlate areply against, and
buttons_forreturns buttons for approvals and option questions, which are exactly theprompts that suspend an agent. So on those platforms the reader is shown a question they
cannot answer from the surface it arrived on, and the agent waits indefinitely. Free-text
questions are unaffected — they take the text branch and work fine, which is why this can
go unnoticed in testing.
How we found it
A scheduled task on a headless deployment parked a
web_fetchapproval at 07:00. ItsTelegram card had no buttons, no tag and no instructions. The run stayed suspended for
36 hours, and because
skip-on-overlapwon't start a run while the previous one isstill going, the next day's run never happened either. The task's
run_countwas still0 two days after it was armed, while the UI showed it enabled and healthy.
Answering by hand with
approve [ow:<id>]— after reading the id out of the database —released it immediately, which confirmed the parser was fine and the message was the
problem.
The change
BasePlatformAdapter.supports_interactive = False,SlackAdapter.supports_interactive = True.Gateway.supports_interactive(target)answers the question per target.approve/deny, or theoption labels — and keeps the tag.
Slack behaviour is unchanged. Any future adapter is safe by default: it gets an
answerable text prompt until it implements buttons.
Tests
tests/test_inbox_mirror_answerable.pycovers all three paths — approval withoutbuttons, option question without buttons, and buttons where the platform draws them.
The first two fail against
main.Ran locally on 3.12: the new file plus the 196 tests matching
inbox|connector|mirror|telegram|slack|interactionall pass.Follow-up, not in this PR
Telegram inline keyboards would be the nicer answer for these two item kinds, mirroring
what
SlackAdapteralready does. That's a bigger change and this one stands on its own —it makes every non-Slack platform answerable today, and keeps the fallback honest for
whatever adapter comes next.
Screenshots of the before/after cards follow in a comment.