mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-13 07:12:00 +01:00
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - An agent can ask the person a question and wait for the answer. Native task status comes from the server's status arbiter, not from model prose > - When a native turn ends without `paperclip_finish`, the Runner sends a default report: `done`, objective satisfied, with the agent's final message as the only evidence > - A later comment from the person moves an open question into history (#15392), so the question no longer gates completion > - Together these close a task as `done` while the agent is still waiting for its own answer and has produced nothing. No Retry appears and nothing flags it > - This pull request keeps such a task `in_review` on the agent's own unanswered question when the only completion evidence is the agent's final message > - The benefit is that a "waiting for your answer" turn can no longer become a silent false completion ## Linked Issues or Issue Description Refs #15392 **What happened?** Restart-chaos wave on master7e8901aa60, OpenCode task t06 (W2Q), no fault injected. Run fec9530f asks a question (interaction c2d3b3f2); the task goes `in_review`. A board comment says "include NOTE-238ef87a in the brief; do not write the brief yet; I will answer shortly". Run f6cbefa8 replies "I will not write the brief until the board's answer arrives. Ending turn to await the codename answer." and ends its turn without `paperclip_finish`. The Runner's default report (`provider:opencode:agent-message` as the only evidence, `reportedWorkDisposition: done`) is accepted, and the low-risk claim policy sets the task `done`. The question is still unanswered and the `brief` document does not exist. **What did you expect to happen?** The task keeps waiting for the answer. The answer continues the task, and the agent writes the brief. **Steps to reproduce** 1. An agent asks a question on its task. 2. The person posts a comment before answering. 3. The agent ends its turn without a completion report, saying it waits for the answer. 4. The task is `done`; the question stays pending. ## What Changed - New `server/src/services/native-runtime/own-question-wait.ts`: - `reportsOnlyProviderMessage(result)`: true when every evidence reference in the report is `provider:<provider>:agent-message` and the report has no artifact and no verification. This is the shape of the Runner's default report for a turn that ended without `paperclip_finish`. - `pendingOwnQuestionId(db, …)`: the agent's own pending, undismissed ordinary question (`ask_user_questions`, not a governed tool, secret or connection request) on the task. - `finalizeNativeRun` (`native-run-finalizer.ts`): when the proposed decision is `done`, the task is not an Agent Chat conversation, the report cites only the agent's final message, and the agent's own question is still pending, the decision becomes `in_review` with reason `own_question_unanswered` and releases the checkout. The question stays pending, so the person's answer continues the task through the existing answer path. - Dismissal (`POST /issues/:id/interactions/:interactionId/dismiss`, `dismissInteraction`): new `enqueueOwnQuestionDismissalWake` runs inside the dismissal transaction, only when the card moves from pending to dismissed. When the task is `in_review` on an applied `own_question_unanswered` decision of the assignee's run, the dismissed card is the assignee's own question, and no other pending, undismissed question of that agent remains, it saves a durable wake intent (`issue_question_dismissed`, idempotency key per question) in the existing native status wake outbox (`enqueueWake`, now exported). The outbox dispatcher delivers and retries it, so the agent continues without the answer. Dismissal of any other request keeps the no-wake rule. - `assignmentDenial` (`agent-interaction-delivery.ts`): an `issue_question_dismissed` wake counts as a person's wake when its `dismissedInteractionId` names the recipient's own question on that task with a `dismissedByUserId`, so a recipient that refuses agent assignments still receives it. - `doc/execution-semantics.md` describes the exception to the historical-question rule. ## Verification - New case in `server/src/services/native-runtime/historical-task-questions.test.ts` ("keeps the agent's own unanswered question open after an unreported turn"), run through the real `PaperclipControlPlanePort.completeRun` and `finalizeNativeRun` with a low-risk `agent_claim_policy` contract, as on staging: - Red on master (finalizer hunk removed, test kept): `unreported` fails, `expected 'done' to be 'in_review'`. - Green: `unreported` rests `in_review` with `own_question_unanswered` and the question still pending. `unreported-dismissed`, `unreported-answered` and `verified` (work-product evidence) still rest `done`, so the #15392 behavior is unchanged. - Dismissal after finalization (same test): the release check is null while a second own question is open and returns the agent once both are dismissed. A repeated dismissal saves no second intent; the saved intent is a queued `issue_question_dismissed` outbox row. Delivery test `delivers a dismissal wake only for a person's dismissal of the recipient's question` (30 passed). Route test `saves the dismissal wake intent in the dismissal transaction only when the card is dismissed` (147 passed). - `cd server && pnpm exec vitest run src/services/native-runtime/historical-task-questions.test.ts src/services/native-runtime/native-run-finalizer`: all passed. - `cd server && pnpm exec vitest run src/services/native-runtime/paperclip-control-plane-port.test.ts src/services/native-runtime/status-arbiter.test.ts`: 119 passed. - `cd server && pnpm exec tsc --noEmit -p tsconfig.json`: passes. - Chaos, Daytona OpenCode, seed 6201, W2Q K0, 4 tasks (wave w-20261111T211421-s6201, run from master7e8901aa60plus this change because the pinned runner image must match runner sources): no false completion. t02 and t04 PASS (in_review -> in_progress -> done after the answer). t01 failed `R3:retry_needed` through the blocked path fixed separately in #20931 (not in this build). t03 timed out because its rewake OpenCode turn stayed active for 20 minutes without ending (provider turn never finished; this change acts only at finalization, which never ran). The unreported-done path did not occur in this sample, so the red/green proof for this change is the integration test. Before: wave w-20261111T200718-s6201 t06 closed done with the question unanswered. ## Risks - Low. Only a `done` decision whose report cites nothing but the agent's final message changes, and only while the same agent's own ordinary question is pending and undismissed. Explicit completion reports with their own evidence keep the current rule. A person can still dismiss the question to release it. - An agent that really finished without `paperclip_finish` while its own old question is open now rests `in_review` instead of `done`. The open question is visible on the task; answering or dismissing it settles the task. - No schema change. A result saved by an older release is finalized by the same rule because it reads only the saved report and interaction rows. - Related observation (not changed here): in 5 Claude/OpenCode W2/W2C cells the agent wrote "the task remains in_progress as instructed" yet the task became `done` (Claude t06, wave s6101). The run's `paperclip_finish` input itself sent `reportedWorkDisposition: "done"`, `objectiveSatisfied: true`, with the deferred phase listed as `blocksCompletion: false`. The server did not map a yielded disposition to done. That is model behavior on a contradictory report, a different root from this change. ## Model Used - Claude Opus 5.5 (`claude-opus-5-5`), with tool use and code execution in an agent harness. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>