[Improve] Check the work before the agent reports to a person or ships, not only at turn end - #3085
Conversation
…s, not only at turn end
|
No code issues found. See task
Reviewed 6b283de |
| 'send_chat_reply', | ||
| 'send_chat_message', | ||
| ]); | ||
| const SHIP_MCP_ACTIONS = /^(create|update)_pull_request$/; |
There was a problem hiding this comment.
manage_source_control does not emit either action this regex accepts: its public action is create_or_update_pull_request. The plugin will call the procedure, but this classifier returns null, so the default autonomous PR delivery path is allowed through without the pre-ship completion check. Include the actual action here and cover it with a regression test.
| const SHIP_MCP_ACTIONS = new Set([ | ||
| 'create_or_update_pull_request', | ||
| 'update_pull_request', | ||
| 'reopen_pull_request', |
There was a problem hiding this comment.
reopen_pull_request is not a shipping action: it only changes the existing PR's state and neither pushes code nor creates/updates its content. Including it here makes an unrelated reopen wait for, and potentially be denied by, the completion check; this also contradicts the preceding comment. Restrict the set to the actual create/update delivery actions.
Problem
The completion check from #3071 runs once, when the OpenCode session goes idle. On an autonomous task that is the very end: implement, validate, proof, push, open the pull request, report. A 30-minute turn is common. So a wrong claim mid-turn is not caught until everything downstream of it has happened, and on the delivery turn the check lands after the push, where a flag can only become a follow-up commit.
Change
The same check now also runs at the two moments that matter, and at those moments it can hold the action:
report_to_parent_session,send_chat_reply,send_chat_message)git push,gh pr create/ready/edit,glab mr create,manage_source_controlcreate/update pull request)How it holds a call. A new OpenCode plugin (
roomote-completion-gate.js, written next to the existing Slack, tool-safety, and identity plugins) implementstool.execute.before. For candidate tools it calls a new sandbox-server procedure,commands.checkCompletionBeforeTool, under the run token; the harness runs the check and answersallowedor a denial with the reasons; the plugin throws the reason, which the agent reads as the tool result. The classification of tools lives in the harness, not the plugin, so it is unit-tested TypeScript.Never stuck, never doubled. A denial is issued at most once per piece of work (request generation plus diff identity); the next call for the same work goes through. A check at report or ship time records the diff it saw, so the turn-end check does not repeat for an unchanged diff. Concurrent tool calls share one evaluation. Any failure (server unreachable, timeout, check skipped) allows the call. The plugin is inert unless the run carries
ROOMOTE_COMPLETION_GATE=true, the same flag that turns on the turn-end check.Agent instructions and docs describe the three moments and that a held call goes through on the second attempt.
Turn end on delegated tasks. On nightly, a delegated UI task (
01az6027gjdzb) ended three of its four turns with an empty assistant message: the report went out throughreport_to_parent_sessionand nothing followed it. The turn-end check requires a report, so it never ran for that task. It now falls back to the agent's last non-empty message when the closing message is empty. The report trigger above covers the report itself.Validation
git pushwith global options,gh prforms, platform PR creation; non-triggers (git status, test runs, PR comments, reads).pnpm lint:fast,pnpm check-types:fast,pnpm knip; worker sandbox-server and run-task suites (the three failures are the known local-only ones: command-executor UTF-8 tail, tail-file-path symlink, OpenRouter variant flake).Not in this PR
completion check held a <trigger> tool call.