From 7d9973405c8726d6aadc7d0e6b4f6076085ebc45 Mon Sep 17 00:00:00 2001 From: Dotta <34892728+cryppadotta@users.noreply.github.com> Date: Wed, 11 Nov 2026 21:00:33 -0600 Subject: [PATCH] fix(chat): ask for a missing name only when the request and saved brief give none (#21007) 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 talk to their agents in chat, and the chat guidance tells the agent how Paperclip works > - #20888 added a rule: ask for a missing name before building something that must carry it > - In a chat eval, "Start a Launch project and have Morgan build its landing page" made the agent create the project and then ask for the brand name. It did not create Morgan's task > - The person had named the project, so a question was not necessary > - This pull request narrows the rule: ask only when the request, the conversation and the work's saved brief give no name > - The benefit is one necessary question when a name is truly missing, and no extra question when the person already named the work ## Linked Issues or Issue Description Refs #20888 **What happened** For "Start a Launch project and have Morgan build its landing page", the agent created the Launch project and asked for the brand name. It did not create the landing-page task for Morgan. **Expected behavior** The agent uses the name the person gave (Launch), creates the project and the task for Morgan, and returns both links. For "Build a landing page for my new product." with no name anywhere, the agent still asks one free-text question for the product name. **Steps to reproduce** 1. Run the chat eval `tasks.website-without-project` on master. 2. Read the reply: the agent asks for a brand name and creates no task. ## What Changed - Chat guidance (`AGENT_CHAT_DIRECTIVE`): use the name that the request, the conversation or the work's saved brief gives. A project the person names in the request counts. Ask only when none of these gives a name. Do not take the name from unrelated records such as other existing projects or the company name. The rule against invented or placeholder names stays. - Tests: updated and new text checks in `agent-conversations.test.ts`. ## Verification - `cd server && npx vitest run src/__tests__/agent-conversations.test.ts`: 64 passed. The new test fails on master (red) and passes with this change (green). - `npx vitest run` for `board-access-denial` and `codex-local-execute`: passed. - Paid chat evals `tasks.website-without-project` and `questions-and-approvals.necessary-question` run on a combined throwaway branch; results are in a PR comment. The necessary-question fixture has an unrelated existing "Launch" project, so the guidance says not to take the name from other projects. ## Risks - Guidance text only. The model can still ask or not ask in edge cases. The paid evals check both directions. ## 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 | 9 ++++++++- server/src/services/agent-conversations.ts | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/server/src/__tests__/agent-conversations.test.ts b/server/src/__tests__/agent-conversations.test.ts index b94cfe3cb06..e51d83ed205 100644 --- a/server/src/__tests__/agent-conversations.test.ts +++ b/server/src/__tests__/agent-conversations.test.ts @@ -1145,12 +1145,19 @@ describe("chat prompt policy", () => { }); it("asks for a missing name before building or handing off work that must carry it", () => { - expect(AGENT_CHAT_DIRECTIVE).toContain("ask for that name before starting: use request_human_input with interactionKind: \"questions\" and one required free-text question"); + expect(AGENT_CHAT_DIRECTIVE).toContain("ask for it before starting: use request_human_input with interactionKind: \"questions\" and one required free-text question"); expect(AGENT_CHAT_DIRECTIVE).toContain("Do not build, delegate or brief the work with an invented or placeholder name"); const prompt = buildPaperclipTaskMarkdown({ issue: { id: "chat", identifier: null, title: "Chat", workMode: "ask", conversationAgentId: "agent" } }); expect(prompt).toContain("Do not build, delegate or brief the work with an invented or placeholder name"); }); + it("asks for a name only when the request, conversation and saved brief give none", () => { + expect(AGENT_CHAT_DIRECTIVE).toContain("a project the person names in the request counts, so \"Start a Launch project and build its landing page\" carries the name Launch and needs no question"); + expect(AGENT_CHAT_DIRECTIVE).toContain("Only when none of these gives a name, ask for it before starting"); + expect(AGENT_CHAT_DIRECTIVE).toContain("Do not take the name from unrelated records such as other existing projects or the company name."); + expect(AGENT_CHAT_DIRECTIVE).not.toContain("must carry a name they have not given"); + }); + it("routes a person's own profile change to their profile settings instead of an agent write", () => { expect(AGENT_CHAT_DIRECTIVE).toContain("only they can change them in their profile settings at //company/settings/instance/profile"); expect(AGENT_CHAT_DIRECTIVE).toContain("link that page instead of attempting the change"); diff --git a/server/src/services/agent-conversations.ts b/server/src/services/agent-conversations.ts index 023ef31d4ea..5ad146fe31b 100644 --- a/server/src/services/agent-conversations.ts +++ b/server/src/services/agent-conversations.ts @@ -90,7 +90,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. 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 the person asks you to build or hand off something that must carry a name they have not given, such as the product or brand name shown on a landing page, ask for that name before starting: use request_human_input with interactionKind: "questions" and one required free-text question, then end your turn. Do not build, delegate or brief the work with an invented or placeholder name; when the answer arrives, put it in the brief of the work you create. +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 the person asks you to build or hand off something that must carry a name, such as the product or brand name shown on a landing page, use the name their request, this conversation or the work's saved brief gives; a project the person names in the request counts, so "Start a Launch project and build its landing page" carries the name Launch and needs no question. Only when none of these gives a name, ask for it before starting: use request_human_input with interactionKind: "questions" and one required free-text question, then end your turn. Do not take the name from unrelated records such as other existing projects or the company name. Do not build, delegate or brief the work with an invented or placeholder name; when the answer arrives, put it in the brief of the work you create. 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.