fix: record unanswered questions an unattended turn could not ask - #807
Conversation
A one-shot listed only the calls a gate refused. A sub-agent turn with no page subscribed bound no asker, and the sentence telling the model to proceed was the only trace, so opening the session later showed nothing.
gloryfromca
left a comment
There was a problem hiding this comment.
Blocking: the no-broker one-shot path must record its unanswered questions before this can merge.
I left one inline finding. I reviewed the actual PR delta against GitHub's current base (2f5027cdf); the local github/main ref was stale, so I also separated the two already-merged intervening commits from this PR. I covered the repository rules and runtime/TUI domain guidance, the diff, production callers and persistence paths, relevant history, backward compatibility, test quality (including whether the new tests bypass production behavior), and the runtime architecture boundary.
Verification:
uv run pytest tests/test_acp_ask_user.py tests/test_acp_elicitation.py tests/test_agent_loop_session_stamps.py tests/test_ask_user_tool.py tests/test_cli_agent_commands.py tests/test_cli_one_shot_spine.py -x- 235 passed.uv run python scripts/check_source_language.py 2f5027cdfc279a96ba3fb69ac351f5652b63f0dc...HEAD- passed.git diff --check 2f5027cdfc279a96ba3fb69ac351f5652b63f0dc...HEAD- passed.- A direct no-broker
AskUserToolreproduction returned the existing configuration error and leftturn.unansweredempty.
make check-source-language could not run because make is unavailable in this environment; the canonical checker it wraps passed when invoked directly.
The no-broker return is the path a one-shot call takes, so the note on the empty-answer branch never ran and the report stayed empty. The notice is filed by a function, because a stub that borrows _save_turn has no loop method of its own.
gloryfromca
left a comment
There was a problem hiding this comment.
No blockers; this can merge as far as I am concerned.
The prior blocker is fixed: the production no-broker and missing-conversation paths now record normalized, valid questions before preserving their existing error returns, and the one-shot regression test exercises that path through AskUserTool and the runner. Moving unanswered-notice filing to a module-level helper also preserves callers that borrow _save_turn on a stub.
For this revision I reviewed the delta from 73725bb71050, the updated full PR diff, the affected callers and persistence path, compatibility of existing error returns, the regression tests (including whether they reach production behavior), repository rules/domain terminology, and the existing runtime architecture boundary. I found no additional issue.
Verification:
uv run pytest tests/test_acp_ask_user.py tests/test_acp_elicitation.py tests/test_agent_loop_session_stamps.py tests/test_ask_user_tool.py tests/test_cli_agent_commands.py tests/test_cli_one_shot_spine.py tests/test_agents_research_flow_report_shape.py -x- 283 passed.uv run python scripts/check_source_language.py 2f5027cdfc279a96ba3fb69ac351f5652b63f0dc...HEAD- passed.git diff --check 2f5027cdfc279a96ba3fb69ac351f5652b63f0dc...HEAD- passed.
## Summary Bump the package version from 0.2.2 to 0.2.3 for the next release. Only pyproject.toml and uv.lock change. Since v0.2.2, main merged 13 PRs: three features (#802 an experimental Curator that cultivates harness-of-harnesses workers, #805 one header per desk pane, #806 upgrade from the page with progress), four fixes (#792 and #799 name grok and copilot connection failures, #807 records the questions an unattended turn could not ask, #808 keeps the report shape on interrupted research turns) and six docs PRs. None carries a BREAKING CHANGE footer, so this is a patch bump, the way v0.2.1 and v0.2.2 were for their features. The tag and release notes follow once this lands. ## Type - [ ] Fix - [ ] Feature - [ ] Docs - [ ] CI / tooling - [ ] Refactor - [x] Other ## Verification - `uv lock` on a clean worktree cut from origin/main: Resolved 251 packages, Updated raven v0.2.2 -> v0.2.3 - `git diff --stat origin/main..HEAD`: pyproject.toml and uv.lock, 2 insertions, 2 deletions - `PYTHONPATH=. python scripts/check_commit_messages.py origin/main..HEAD`: exit 0 - `npx commitlint --from origin/main --to HEAD --config commitlint.config.cjs`: exit 0 - `uv version` in the worktree: raven 0.2.3 - No test run: the change is version metadata only. - [ ] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [ ] User-facing docs or screenshots are updated when needed ## Risk No code change. Rollback is reverting this commit before any v0.2.3 tag is pushed. - [ ] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes ## Related Issues N/A Co-authored-by: Claude (claude-fable-5-1) <noreply@anthropic.com>
Summary
An unattended turn could ask a question and leave no trace a person can read.
raven agent -malready lists calls the permission gate refused (#708). It said nothing about a question nobody could answer. On the web, a sub-agent turn with no page subscribed to the session binds no asker (start_ask_turn(None)inraven/rpc/spine.py). The hostask_usertool still tells the model "user did not answer; proceed with best judgment". That sentence is the tool result the model reads. Opening the session later showed no event for the question.Questions that were never put to anyone are now recorded on the turn: a sub-agent
ask_useror elicitation with no asker, a question that gives up waiting for the conversation lock, a round trip that is structurally unavailable, and a hostask_usercall whose answer comes back empty. A one-shot call has no broker at all, so it returns at that guard; the questions_prepareaccepts are recorded there too, and the same error string is what the model still reads. A call_preparerejects is not recorded. The turn's save writes one assistant entry with aquestion_unanswerednotice, which is what a reopened session draws. A one-shot prints the same questions after the reply. That list does not change the exit status: the turn continued, which is what the model was told to do. A refusal is still the run that exits 3.Type
Verification
Reverting
raven/agent/tools/ask_user.pyto its parent makes both new no-broker tests fail with an empty record. Revertingraven/agent/loop/turn_path.pyto the parent, where filing was a method, makestest_the_bounce_scaffolding_never_becomes_historyraiseAttributeError: '_Loop' object has no attribute '_file_unanswered'. Both files were restored afterwards. The earlier reverts (notice not stored, ACP ask and elicitation not recorded, one-shot report not printed) still stand for those paths.The full suite was not run. A run of the touched CLI modules in this terminal also failed existing assertions that search Rich output for a plain substring (help flags, and
1 action(s) were refused). Those assertions fail the same way with this diff's refusal printer left unchanged. The type checker was not rerun locally; CI's python lint job runs it on the head.Risk
The model is still told to continue. Nothing new is approved. The one-shot exit status is unchanged: unanswered questions are reported and do not by themselves exit 3. The notice is one more assistant entry on the session, with
notice.kindquestion_unanswered, so a client that already draws runtime notices draws this one. Rollback is reverting the commit.Related Issues
#708