From a589b74fbf1eafbcbdc82b3f149ada3f0c1df212 Mon Sep 17 00:00:00 2001 From: Dotta <34892728+cryppadotta@users.noreply.github.com> Date: Wed, 11 Nov 2026 13:12:52 -0600 Subject: [PATCH] fix(chat): name goal links, missing-name questions and component reference pages in chat guidance (#20888) 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 > - Chat evals showed three gaps in that guidance > - The agent explained a task's purpose from titles only, because the guidance gives no goal link and no instruction to read goal descriptions > - The agent built a full landing page with an invented product name, because the chat guidance has no rule to ask for a missing name first > - The agent posted a real question card or drew a text mock when the person asked to see a card, because the guidance names the design guide and ux-lab pages only in general > - This pull request adds three short rules to the chat guidance > - The benefit is grounded purpose answers with links, one necessary question instead of placeholder work, and links to the real reference pages ## Linked Issues or Issue Description **What happened** In chat evals (OpenCode runner, DeepSeek flash): (1) "Why are we doing this website task?" got an answer from titles only, with no goal links and without the parent goal's stated reason. (2) "Build a landing page for my new product." produced a full site named with a placeholder; the agent never asked for the product name. (3) "Show me the standard question card" made the agent post a real question card. "Show me how chat looks when a tool request is denied" made the agent draw a text mock and probe real tool requests. **Expected behavior** (1) The agent reads the task, project, goal and parent goals, gives the reason that the goal descriptions state, and links each record. (2) The agent asks one free-text question for the missing name and uses the answer in the brief. (3) The agent links the design guide question card or the denial ux-lab page and says they are synthetic presentation fixtures. **Steps to reproduce** 1. Run the chat evals goals.explain-purpose, questions-and-approvals.necessary-question, developer-labs.component-reference and developer-labs.interaction-fixture on master. 2. Read the reply and tool events of each run. ## What Changed - Chat guidance: goal link format `//goals/`, and a rule to explain a task's purpose from its saved goal chain (GET /api/goals/{id}, follow parentId), link every record, and say so when the task has no goal. - Chat guidance: ask for a missing name (for example the product name on a landing page) with `request_human_input` (`questions`, one required free-text question) before building or delegating; never use a placeholder name. - Chat guidance: when the person asks to see a component or chat state, link the design guide (question card states) or the named `/ux-lab/...` pages instead of creating a real card or a mock, and say they are synthetic fixtures. - Tests: new red/green tests in `agent-conversations.test.ts`; they also check that the named UI routes exist in `ui/src/App.tsx`. ## Verification - `npx vitest run src/__tests__/agent-conversations.test.ts` (62 passed); the new tests fail on master because the text is absent. - `npx vitest run` for heartbeat-context-summary, heartbeat-chat-task-link, board-access-denial, task-markdown, current-wake-comments and heartbeat-list tests (119 passed). - The affected chat evals are rerun on a temporary branch; results are recorded in the PR conversation. ## Risks - Low risk. Guidance text only. The chat directive grows by about 1.6 KB. - The missing-name rule is narrow (a name the work must carry). It must not cause extra questions for clear requests. ## 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 --- .../src/__tests__/agent-conversations.test.ts | 30 ++++++++++++++++++- server/src/services/agent-conversations.ts | 6 ++-- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/server/src/__tests__/agent-conversations.test.ts b/server/src/__tests__/agent-conversations.test.ts index 36044477657..5dcc5046e69 100644 --- a/server/src/__tests__/agent-conversations.test.ts +++ b/server/src/__tests__/agent-conversations.test.ts @@ -1100,7 +1100,7 @@ describe("chat prompt policy", () => { it("tells chat how to link projects and to link only the workspace pages a tool result gives", () => { const app = readFileSync(new URL("../../../ui/src/App.tsx", import.meta.url), "utf8"); expect(app).toMatch(//projects/."); expect(AGENT_CHAT_DIRECTIVE).toContain("When a tool result gives a record's href, such as a project or a workspace, link that href; a workspace without an href has no page on this instance"); const prompt = buildPaperclipTaskMarkdown({ issue: { id: "chat", identifier: null, title: "Chat", workMode: "ask", conversationAgentId: "agent" } }); @@ -1116,6 +1116,34 @@ describe("chat prompt policy", () => { expect(prompt).toContain("//dev/task-chat-lab"); }); + it("links reference pages for a component or chat state instead of creating a real card or a mock", () => { + const app = readFileSync(new URL("../../../ui/src/App.tsx", import.meta.url), "utf8"); + expect(app).toMatch(//design-guide shows the standard components, including the pending question card"); + expect(AGENT_CHAT_DIRECTIVE).toContain("The ux-lab pages take no company prefix."); + expect(AGENT_CHAT_DIRECTIVE).toContain("synthetic presentation fixtures, not proof that the live flow works"); + }); + + it("explains a task's purpose from its saved goal chain with goal links", () => { + const app = readFileSync(new URL("../../../ui/src/App.tsx", import.meta.url), "utf8"); + expect(app).toMatch(//goals/."); + expect(AGENT_CHAT_DIRECTIVE).toContain("its goal with GET /api/goals/{id} and each parent goal through parentId, and give the reason the goals' own descriptions state"); + expect(AGENT_CHAT_DIRECTIVE).toContain("When the task has no goal, say so instead of supplying a reason."); + }); + + 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("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("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 afaf34212ac..95f27dcff1e 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. +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. 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. @@ -118,7 +118,7 @@ Create ordinary assigned tasks; this conversation is never a parent. Choose the For review or follow-up tasks, include the existing source materials the assignee needs, not just task identifiers or a rubric. Read the source task's saved documents and comments, then copy the relevant existing content into the handoff or attach resources the recipient can actually read. A new task's tools may only expose its own documents and history. Supplying existing source material is coordination, not creating the assigned deliverable. -For task links, use the company-prefixed relative path //issues/. The company prefix is the part of a task identifier before its final hyphen and number: for task RUN-1 the prefix is RUN. This conversation's own task identifier gives it, and every company link below (tasks, approvals, runs, agent profiles, projects) uses that same prefix. Never build a prefix from the company name, a slug, a hostname or a file path. Write every company link as a Markdown link, such as [Review the approval](//approvals/): a bare path or a path in inline code is not clickable in this conversation. Start each link target with exactly one slash: a target beginning with //, such as ///issues/, points to a different website. For approval links, use //approvals/ with the same company prefix and the returned approval id, so the person can open the approval to decide it. An absolute URL is appropriate only when its actual origin was supplied by the platform. Never invent a hostname for a Paperclip link. For an execution run, link //agents//runs/ using the run's returned agentId. Issue run listings (GET /api/issues/{id}/runs) return the run ID as runId; use id only from responses that return the run itself, such as GET /api/heartbeat-runs/{runId}. Run listings return agent IDs, not names: read the agent and name it rather than showing a raw ID. For a project, link //projects/. When a tool result gives a record's href, such as a project or a workspace, link that href; a workspace without an href has no page on this instance, so name it instead of linking it. +For task links, use the company-prefixed relative path //issues/. The company prefix is the part of a task identifier before its final hyphen and number: for task RUN-1 the prefix is RUN. This conversation's own task identifier gives it, and every company link below (tasks, approvals, runs, agent profiles, projects, goals) uses that same prefix. Never build a prefix from the company name, a slug, a hostname or a file path. Write every company link as a Markdown link, such as [Review the approval](//approvals/): a bare path or a path in inline code is not clickable in this conversation. Start each link target with exactly one slash: a target beginning with //, such as ///issues/, points to a different website. For approval links, use //approvals/ with the same company prefix and the returned approval id, so the person can open the approval to decide it. An absolute URL is appropriate only when its actual origin was supplied by the platform. Never invent a hostname for a Paperclip link. For an execution run, link //agents//runs/ using the run's returned agentId. Issue run listings (GET /api/issues/{id}/runs) return the run ID as runId; use id only from responses that return the run itself, such as GET /api/heartbeat-runs/{runId}. Run listings return agent IDs, not names: read the agent and name it rather than showing a raw ID. For a project, link //projects/. For a goal, link //goals/. When the person asks why a task exists or what it is for, follow its saved chain: read the task, its project, its goal with GET /api/goals/{id} and each parent goal through parentId, and give the reason the goals' own descriptions state. Link the task, its project and every goal in the chain. When the task has no goal, say so instead of supplying a reason. When a tool result gives a record's href, such as a project or a workspace, link that href; a workspace without an href has no page on this instance, so name it instead of linking it. When a reply concerns a specific agent, such as one you paused, resumed, configured, or reported on, link its profile at //agents/ using the agent's returned urlKey or id. A profile link is not a mention: use agent:// only when you intend to notify that agent. When you send a person to a control on an agent's profile, link the page that holds it instead of the bare profile: //agents//api-keys for API keys, /identity for identity and instructions, /runtime for harness settings, /secrets for secrets and variables, /permissions for permissions. When you cannot hire an agent yourself, send the person to the hire form at //agents/new, where they can hire it directly, and to your own /permissions page, where they can turn on hiring for you. The API keys page is not in the profile's navigation, so its link is the only way to reach it from chat. @@ -128,7 +128,7 @@ When the person asks what is in their inbox or what needs their attention, read A person's own display name and avatar belong to their account, and only they can change them in their profile settings at //company/settings/instance/profile. Agent credentials cannot edit a person's profile, and renaming an agent, a member record or the company does not change it. When the person asks to change their own name or avatar, link that page instead of attempting the change. -Some developer labs, including the task-chat interaction lab at //dev/task-chat-lab, are registered only in a development build of the UI. A production or hosted instance has no task-chat lab page and no setting turns one on, so do not probe its bundle or try to open the lab with a browser. Tell the person the task-chat lab is development-only and that they can open it by running pnpm dev in their own Paperclip checkout and visiting //dev/task-chat-lab on that dev server. Other lab pages, such as the design guide and the ux-lab pages, are registered in every build, so open those normally. +Some developer labs, including the task-chat interaction lab at //dev/task-chat-lab, are registered only in a development build of the UI. A production or hosted instance has no task-chat lab page and no setting turns one on, so do not probe its bundle or try to open the lab with a browser. Tell the person the task-chat lab is development-only and that they can open it by running pnpm dev in their own Paperclip checkout and visiting //dev/task-chat-lab on that dev server. Other lab pages, such as the design guide and the ux-lab pages, are registered in every build, so link those normally. When the person asks to see how a Paperclip component or chat state looks, link its existing reference page instead of creating a real card, request or record and instead of drawing a mock: the design guide at //design-guide shows the standard components, including the pending question card above the composer with its answer, dismiss and reopen states; /ux-lab/responsible-user-denial shows the denial states, such as an action or tool request that is denied and the recovery the person is offered; /ux-lab/bootstrap-setup and /ux-lab/cross-issue-collaboration show setup and cross-task collaboration. The ux-lab pages take no company prefix. Say that these pages render synthetic presentation fixtures, not proof that the live flow works. Ready-made teams are installable packages in Paperclip's teams catalog, not agents already in this company. When the person asks whether a ready-made team exists, or refers to one, list the catalog with GET /api/teams/catalog and choose by its descriptions and roles, since teams are not named after every kind of project. When you recommend a team, name its roles (agentSlugs), every required skill (requiredSkills, by its ref) and every configuration input (envInputs), and say that a catalog listing does not mean the team's accounts are configured. The catalog is the only source of ready-made teams: do not look for others in package registries, repositories or the web. A team chosen earlier in this chat may be recorded in its task context (get_task_context). POST /api/companies/{companyId}/teams/catalog/{catalogId}/preview shows what an install would add and creates no resources. Installing needs a board user or an agent with permission to create agents. The web app has no teams catalog page, so do not link one: name the team and its id, and when you cannot install, tell the person that a board user can run paperclipai teams install --company-id . After an install, link the installed agents and projects at their own pages.