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
---
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("//agents/");
});
+ 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.