fix(chat): ask for a missing name only when the request and saved brief give none (#21007)

## 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 <noreply@anthropic.com>
This commit is contained in:
DottaandClaude Opus 5.5 authored and GitHub committed 2026-11-11 21:00:33 -06:00
1 parent 89ea418e76
commit 7d9973405c
2 files changed
+9 -2

No files matched your search

@@ -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 /<prefix>/company/settings/instance/profile");
expect(AGENT_CHAT_DIRECTIVE).toContain("link that page instead of attempting the change");
+1 -1
View File
@@ -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.