Skip to content

Question cards: per-question and whole-card skip, skip ≠ blank answer, Slack parity (OPE-153) - #600

Open
devikaverma wants to merge 2 commits into
mainfrom
issue/skip-question-card
Open

Question cards: per-question and whole-card skip, skip ≠ blank answer, Slack parity (OPE-153)#600
devikaverma wants to merge 2 commits into
mainfrom
issue/skip-question-card

Conversation

@devikaverma

Copy link
Copy Markdown
Collaborator

What

  • Question card: a question can now be declined. Single cards get Skip; grouped cards get "Skip question" (records the skip, advances the stepper) and "Skip all" (resolves the rest). Skip all keeps answers already given — it means "don't ask me the rest", not "discard". Skip renders regardless of allow_text: a card with exhaustive options and no free-text escape was the one place with no control on screen but the options themselves. Both surfaces inherit it — the inline attended card and the parked Inbox card are the same component.

…card

Questions were the only Inbox item kind with no way out. Approvals have Deny,
directory requests have Deny, plans have Reject, notifications have Dismiss —
a question had nothing, so a user who couldn't answer had to invent an answer
that misleads the agent or abandon the session. Worst on a card with exhaustive
options and allow_text:false: literally no control but the options themselves.

The card now carries Skip, and a grouped card carries both "Skip question" (this
one, advance) and "Skip all" (resolve the rest). Skip all KEEPS answers already
given — it means "don't ask me the rest", not "discard". Skip is present whatever
allow_text says, since that's exactly where the user was stuck.

The button was the easy half. answer_result() returned {"answer": ""} both for a
blank resolution and for anything unparseable, so a skip could not be told from an
empty answer. A skipped question now answers null — never "" — alongside `skipped`
naming what was declined. Two consequences in the engine: an all-skipped card reads
"denied" rather than "ok" (a non-empty answers map is no longer proof anyone
answered), and _note_ask_replies drops nulls, else str(None) files the literal
"None" as something the user said.

What the agent is told about a skip is deliberately asymmetric. "Do not put this
question back to them" is absolute — that anti-loop guarantee is what the skip is
for. "Pick a default" is conditional: the same note fires for "you choose the chart
colour" and for "Staging or Production?", and a model that follows instructions
literally would deploy off a shrug. Opus 4.8 refused that guess on its own; smaller
models are likelier to comply, so proceeding without deciding is offered as a third
path rather than left to whichever model is driving.

Verified against a live model, not just fixtures: the options-only card skipped
cleanly, and a grouped card kept its answered step while reporting the skipped ones
as skipped. Four e2e specs and four Python tests cover both surfaces.
An unattended session mirrors its questions to a bound channel, and buttons_for()
rendered one button per option and nothing else. So the dead end this issue closed
in the app was still wide open in Slack: a reader whose answer wasn't on the list
could only click a wrong option or leave the agent suspended.

Skip now trails the options, carrying the sentinel answer_result() already maps to
a null answer — that text-only-surface branch was written for exactly this caller
and had a test before it had a sender.

It grants nobody new authority. Anyone who can click an option in that channel can
already decide the question; this only adds declining to what they could already do,
which is the gentler of the two.

Grouped questions stay button-less on purpose. Any button at all flips the mirror
from the plain-text path to the interactive one, which would cost them the
"(Open the app to respond.)" hint — and the app is where their per-question skip
lives anyway.
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