From 152af6161c088881b004504dfebe7541ec5bd0c2 Mon Sep 17 00:00:00 2001 From: Dotta Date: Sun, 8 Nov 2026 05:02:35 -0600 Subject: [PATCH] fix(runner): tell agents a comment on a running task waits for its next turn A chat lead asked to change an existing website task whose assignee was mid-run read comment_on_task's "without reopening or interrupting it" as "the worker will never see it", then spent its turn inspecting the assignee's workspace and considering writing the worker's input file directly. State the actual product behavior: a comment on a running task is queued and delivered at the start of the assignee's next turn, it is the way to route a changed requirement to an existing task, and it should be reported as saved, not applied. Co-Authored-By: Claude Opus 5.5 --- .../native-runtime/runner-bridge-tools.test.ts | 10 ++++++++++ .../src/services/native-runtime/runner-bridge-tools.ts | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/server/src/services/native-runtime/runner-bridge-tools.test.ts b/server/src/services/native-runtime/runner-bridge-tools.test.ts index d06839777ac..11151ffe319 100644 --- a/server/src/services/native-runtime/runner-bridge-tools.test.ts +++ b/server/src/services/native-runtime/runner-bridge-tools.test.ts @@ -86,6 +86,16 @@ describe("Runner workspace bridge", () => { expect(await readFile(join(directory, "result.txt"), "utf8")).toBe("hello"); }); + it("tells agents that a comment on a running task waits for the assignee's next turn", () => { + // A chat lead routing a requirement change to busy work otherwise reads "without interrupting" + // as "never delivered" and reaches for the assignee's workspace or a duplicate task instead. + const comment = runnerBridgeDefinitions({ workspace: false, skills: false, api: true, mode: "standard" }).find(tool => tool.name === "comment_on_task"); + expect(comment?.description).toContain("existing task's assignee a new or changed requirement"); + expect(comment?.description).toContain("rather than creating a duplicate task or editing that task's workspace files"); + expect(comment?.description).toContain("delivers it at the start of the assignee's next turn on that task"); + expect(comment?.description).toContain("not as already applied"); + }); + it.skipIf(process.platform === "linux")("does not expose or execute commands without descendant containment", async () => { const directory = await root(); expect(workspaceCommandSandboxAvailable()).toBe(false); diff --git a/server/src/services/native-runtime/runner-bridge-tools.ts b/server/src/services/native-runtime/runner-bridge-tools.ts index fab6e460af7..20a079fcf8e 100644 --- a/server/src/services/native-runtime/runner-bridge-tools.ts +++ b/server/src/services/native-runtime/runner-bridge-tools.ts @@ -14,7 +14,7 @@ const specs = [ ["get_identity", "Read your agent identity, responsible person, permissions and currently available tools.", z.object({}).strict(), "read"], ["list_people", "List active company people and their IDs for human assignment; roles identify the owner.", z.object({ after: z.string().optional(), limit: z.number().int().min(1).max(100).default(50) }).strict(), "read"], ["get_task", "Read another task through normal task permissions.", z.object({ taskId: z.uuid() }).strict(), "read"], - ["comment_on_task", "Post an attributed comment on an authorized task, without reopening or interrupting it.", z.object({ taskId: z.uuid(), body: z.string().trim().min(1).max(20000) }).strict(), "mutation"], + ["comment_on_task", "Post an attributed comment on an authorized task, without reopening or interrupting it. Use it to give an existing task's assignee a new or changed requirement, rather than creating a duplicate task or editing that task's workspace files. If the assignee is running on that task now, the comment does not reach the run in progress: Paperclip saves it in the task's queue and delivers it at the start of the assignee's next turn on that task. Report it as saved for that next turn, not as already applied.", z.object({ taskId: z.uuid(), body: z.string().trim().min(1).max(20000) }).strict(), "mutation"], ["list_task_documents", "List documents on an authorized task.", z.object({ taskId: z.uuid() }).strict(), "read"], ["read_task_document", "Read a document on an authorized task.", z.object({ taskId: z.uuid(), key: documentKey }).strict(), "read"], ["write_task_document", "Write a document on an authorized task using its current revision. Permissions and document locks apply.", z.object({ taskId: z.uuid(), key: documentKey, title: z.string().min(1).max(200), body: z.string().max(20000), baseRevisionId: z.uuid().nullable() }).strict(), "mutation"],