From 47dc3dd9d08a9c4aa995b90b0944ef776cd06548 Mon Sep 17 00:00:00 2001 From: Dotta <34892728+cryppadotta@users.noreply.github.com> Date: Sat, 7 Nov 2026 21:00:57 -0600 Subject: [PATCH] fix(chat): ask which record is meant when a change matches several (#19942) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - People use the agent chat to change records, for example task priority > - A request can name one record when several accessible records match it > - The chat guidance tells the agent to ask focused questions, but it does not say what to do when a reference matches several records > - In an eval run, the agent spent more than two minutes. It considered picking one task, changing both tasks, and asking. It did not reply before the run was cancelled > - This pull request adds one rule: ask which record is meant before changing any of them, and link each candidate > - The benefit is a fast, safe reply that does not change the wrong record ## Linked Issues or Issue Description **What happened** The person wrote "Mark the landing-page task as high priority." Two accessible tasks have the title "Landing page", in the projects Launch and Experiments. Nothing in the chat selected one. The agent searched, read both tasks, listed projects and searched the API. Its reasoning wavered between "ask", "change both" and "pick one". After about 130 seconds it had not replied, and the run was cancelled. **Expected behavior** The agent asks which task the person means. The question links both tasks, names what makes them different, and keeps the requested change. The agent changes no task until the person answers. Then it changes only the selected task. **Steps to reproduce** 1. Create two tasks with the same title in two different projects. 2. In the agent chat, ask to set "the task" to high priority. 3. See that the guidance gives no rule for several matches, and the agent can deliberate, guess, or change both. Refs: chat-feature eval `tasks.ambiguous-task`. ## What Changed - `server/src/services/agent-conversations.ts`: `AGENT_CHAT_DIRECTIVE` now says: when a change request matches several accessible records and nothing selects one, ask which one before changing any. Link each candidate, name the difference, and restate the change. Do not pick the first match, change every match, or keep searching for a hidden answer. - `server/src/__tests__/agent-conversations.test.ts`: a regression test checks the rule in the directive and in the built chat prompt. ## Verification - `cd server && npx vitest run src/__tests__/agent-conversations.test.ts -t "chat prompt policy"` passes (10 tests). - End-to-end: the chat-feature eval `tasks.ambiguous-task` runs with this commit on an eval branch. The result is in a comment below. ## Risks - Low risk. This is a prompt-guidance change only. No API, schema or permission changes. - The agent can ask in more cases. The rule applies only when several records match and nothing in the conversation selects one. ## Model Used - Anthropic Claude Opus 5.5 (`claude-opus-5-5`), tool use and code execution in Claude Code. ## 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> --- server/src/__tests__/agent-conversations.test.ts | 8 ++++++++ server/src/services/agent-conversations.ts | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/server/src/__tests__/agent-conversations.test.ts b/server/src/__tests__/agent-conversations.test.ts index 2a4ceafb6dc..eb0835c6738 100644 --- a/server/src/__tests__/agent-conversations.test.ts +++ b/server/src/__tests__/agent-conversations.test.ts @@ -994,6 +994,14 @@ describe("chat prompt policy", () => { expect(prompt).toContain("/<prefix>/agents/<urlKey or id>"); }); + it("asks which record is meant before changing one of several matches", () => { + expect(AGENT_CHAT_DIRECTIVE).toContain("matches several records you can access and nothing in the conversation selects one, ask which one before changing any of them"); + expect(AGENT_CHAT_DIRECTIVE).toContain("link each candidate"); + expect(AGENT_CHAT_DIRECTIVE).toContain("Do not pick the first match, change every match"); + const prompt = buildPaperclipTaskMarkdown({ issue: { id: "chat", identifier: null, title: "Chat", workMode: "ask", conversationAgentId: "agent" } }); + expect(prompt).toContain("ask which one before changing any of them"); + }); + it("preserves explicit plan approval while avoiding ritual confirmations for ordinary chat", () => { expect(AGENT_CHAT_DIRECTIVE).toContain("When the user asks to approve a plan before handoff"); expect(AGENT_CHAT_DIRECTIVE).toContain('interactionKind: "confirmation"'); diff --git a/server/src/services/agent-conversations.ts b/server/src/services/agent-conversations.ts index 84592ac13f4..c974dfbced3 100644 --- a/server/src/services/agent-conversations.ts +++ b/server/src/services/agent-conversations.ts @@ -88,7 +88,7 @@ export function isConversationReset(body: string): boolean { return body.trim() === "/new"; } -export const AGENT_CHAT_DIRECTIVE = `You are in an ongoing conversation with the user. Help them clarify the outcome they want. Ask focused questions when missing information materially affects the task; when the request is already clear, do not require a ritual confirmation. +export const AGENT_CHAT_DIRECTIVE = `You are in an ongoing conversation with the user. Help them clarify the outcome they want. Ask focused questions when missing information materially affects the task; when the request is already clear, do not require a ritual confirmation. When a request to change one record matches several records you can access and nothing in the conversation selects one, ask which one before changing any of them: link each candidate, name what distinguishes them (such as project or status), and restate the requested change. Do not pick the first match, change every match, or keep searching for a hidden answer; asking in your reply is enough, and you finish the change when the person answers. When changing defaults or policy for new or future work, update the resource that owns those defaults. Leave existing items and active work unchanged unless the user explicitly includes them in the requested change. This boundary includes metadata and association changes, even when they seem harmless or helpful. Do not broaden the request to align related existing items. Explain which subsequent work inherits the saved defaults and whether existing work changed.