Skip to content

fix(questions): keep the questions card open until the person answers - #212

Closed
TheGreatAxios wants to merge 4 commits into
mainfrom
cl-12168-answer-questions-as-the-calls-result
Closed

TheGreatAxios wants to merge 4 commits into
mainfrom
cl-12168-answer-questions-as-the-calls-result

Conversation

@TheGreatAxios

@TheGreatAxios TheGreatAxios commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

The questions card no longer disappears after a reload: the question is now a tool call that waits for the person's answer, and that answer becomes its result. Needed a vendored Interchange change, which also stops a second approval in one reply from being dropped.

Questions card after a reload, main vs this change

Verification

Scripted model, 2 runs each. All checks pass, including e2e.

Case main this change
Card after a reload gone open
Answer from the card or the box "Asked." answer is the result
Files sent while questions are open card stays open they answer it
Two approvals in one reply second missing both shown
Malformed questions error, no card error, no card

Closes CL-12168

When one model response made several calls that need approval, each
call parked, but the send settled on the first suspension. The others
never got an approval card or a tool result, and the run waited on them
forever.

An answered call no longer wakes the model while a sibling from the same
response still waits: the reactor announces the next parked call
instead, so the model continues only once every call has its result. The
calls surface one at a time, because a workflow step parks on one
signal.

Every gated call of a batch is now also parked durably before the first
one is announced. The announcement settles the send, and the agent's
state is mirrored at that point, so a sibling persisted only afterwards
could be lost to a restore. The results of calls from the same response
that ran or were refused land in that same commit.

The siblings queue behind the announced call with no armed gate, so each
one's deadline starts when it is announced rather than running out
while it waits its turn. The batch is recorded on each pending
operation, so a restored reactor still finds the next one.
When one of several gated calls from the same model response timed out,
its error result went straight to the director, so the model continued
while a sibling call was still parked. That sibling never got an
approval card or a tool result.

A timed-out call is now treated like an approved or rejected one: while
a sibling from its response still waits, the reactor announces that
sibling instead of waking the model.
A parked call could only be approved to run or rejected. A tool whose
result is the person's own input, such as a set of questions, needs a
third way out: the person supplies the result.

A tool declares `approval: "respond"` to opt in, and its approval
snapshot is marked respondable. POST /approvals/:id/respond resolves only
such a call: the decision carries the result, the row records the
responded status, and the reactor answers the parked call with it as a
normal tool result without running the tool. A respondable call is
responded to or rejected once; the approve route refuses it, and the
reject route refuses a standing rejection, so it can never run or block
later questions.

Such a call waits for the person with no deadline, as the hub's approval
does. Before, the reactor answered any parked call "approval timed out"
after an hour, which would have closed questions a person left open.

A gated call whose arguments fail its tool's own check, the new optional
ToolBundle.check, is answered with that error at once and never parks.
A check that throws is answered with its error, as a throwing
before-tool extension is.
ask_questions returned "Asked." at once and the answers followed as the
person's next message, so the page had to guess from later turns which
card was still open, and the card was gone after a reload.

The call now parks like an approval. The card is drawn from the pending
approval, so it survives a reload and leaving the project. Its answers,
or words typed in the composer while it is open, resolve the parked call
through the respond route as JSON (answers per question, or reply), so
they are the call's tool result. The conversation shows that result as
the person's turn, so the answers stay in the thread after a reload.
Files sent or an approach picked while questions are open answer them
too, instead of waiting unseen until the questions are answered.
Malformed questions are refused before the call parks, so every waiting
set of questions shows on its card and is never approved to run. Open
questions are not a safe point for a coordinator upgrade.
@linear-code

linear-code Bot commented Nov 6, 2026

Copy link
Copy Markdown

CL-12168

@TheGreatAxios

Copy link
Copy Markdown
Contributor Author

Closing: this needs further exploration into its Interchange touch points before we land it. The write-up and decision are on CL-12168.

@TheGreatAxios

Copy link
Copy Markdown
Contributor Author

The cleaned-up proposal is on two branches, without PRs, while the decision on CL-12168 is open:

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