Q116: a task without file access cannot write files, and its prompt says to use documents

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ZacharyZhang-NYandClaude Opus 5.5 committed 2026-10-08 19:00:42 -04:00
1 parent 938c8cad79
commit d67996fd25
5 files changed
+29 -4

No files matched your search

@@ -14,6 +14,10 @@ const REVIEW_EXCERPT_CHARS = 4000;
export function writeDeliverableTool(deps: AgentToolDeps, ctx: AgentToolContext, args: JsonRecord): string {
if (!ctx.workspace) throw new Error("No workspace is bound to this task; cannot write deliverables.");
// Writing a file is writing the workspace: only a task the operator approved for file access may, as with run_command.
if (!ctx.workspace.writeAccess) {
throw new Error("This task has read-only workspace access, so it cannot write files. Put the output in a document (create_document), or ask for a task with requires_filesystem, which the operator approves.");
}
const path = requireString(args.path, "path");
const content = requireString(args.content, "content");
if (reviewsDeliverables(deps.db, ctx.orgId)) {
+1 -1
View File
@@ -112,7 +112,7 @@ export const AGENT_TOOLS: ToolDefinition[] = [
},
{
name: "write_deliverable",
description: "Write a deliverable file into the company workspace folder (relative path). Use this to produce real output.",
description: "Write a deliverable file into the company workspace folder (relative path). Only tasks with filesystem access may write files; otherwise use create_document.",
parameters: {
type: "object",
additionalProperties: false,
+10 -3
View File
@@ -267,7 +267,14 @@ export function buildTaskPrompt(input: {
);
return lines.join("\n");
}
if (input.task.requires_filesystem === 1 && input.workspace) {
// Only a task with approved file access can write files; every other task delivers through documents.
const writesFiles = input.task.requires_filesystem === 1 && input.workspace !== null;
const planStep = writesFiles
? "write the initial operating plan into the workspace (write_deliverable). Then use send_message with /handoff @Name and attachment_file_ids to pass the operating plan to the relevant report."
: "write the initial operating plan as a document (create_document). Then point each relevant report to its doc_id in your message.";
const outputStep = writesFiles ? "write real output with write_deliverable" : "write real output as documents (create_document)";
const handoffStep = writesFiles ? " Hand files over with /handoff and attachment_file_ids." : " Point teammates to documents by doc_id.";
if (writesFiles) {
lines.push(
"",
"This task carries filesystem access: read the existing code with list_files and read_file before changing anything, write changes with write_deliverable, and prove they work with run_command (install, build, test). Real output is code that builds and tests that pass — a document describing the work does not close an engineering task.",
@@ -296,14 +303,14 @@ export function buildTaskPrompt(input: {
: `Tell your manager the plan in a direct message. Then ${park}`;
lines.push(
"",
`Your team is currently empty. First ${firstHiresFor(input.agent.role_level)}, create and assign their first tasks (create_task), and write the initial operating plan into the workspace (write_deliverable). Then use send_message with /handoff @Name and attachment_file_ids to pass the operating plan to the relevant report. Brief EACH report in a direct message (send_message with to="Their Name") about the work you assigned them — note the thread_id each returns so you can follow up in the same conversation. ${announce}`,
`Your team is currently empty. First ${firstHiresFor(input.agent.role_level)}, create and assign their first tasks (create_task), and ${planStep} Brief EACH report in a direct message (send_message with to="Their Name") about the work you assigned them — note the thread_id each returns so you can follow up in the same conversation. ${announce}`,
);
} else {
const manager = input.managerName ?? "your manager";
const managerReference = input.managerName ? `${manager} (${agentMention(input.managerName)})` : manager;
lines.push(
"",
`First, read_messages to check if anyone has said something you should respond to — reply to them like a colleague, not a report. Then do the work: write real output with write_deliverable, raise decisions and blockers with ${managerReference} in a direct message (send_message with to=), and keep team-wide topics in channels. Hand files over with /handoff and attachment_file_ids. Keep conversation going — ask questions, share your thinking, push back if something seems wrong. Before you finish: if the work is genuinely done, call update_task_status(task_id="${input.task.id}", status="done", evidence="what you produced and where"); if it needs another session, move it to "in_progress" and say in the thread what remains — an open task will be run again.`,
`First, read_messages to check if anyone has said something you should respond to — reply to them like a colleague, not a report. Then do the work: ${outputStep}, raise decisions and blockers with ${managerReference} in a direct message (send_message with to=), and keep team-wide topics in channels.${handoffStep} Keep conversation going — ask questions, share your thinking, push back if something seems wrong. Before you finish: if the work is genuinely done, call update_task_status(task_id="${input.task.id}", status="done", evidence="what you produced and where"); if it needs another session, move it to "in_progress" and say in the thread what remains — an open task will be run again.`,
);
}
lines.push("", RECORD_LEARNINGS_INSTRUCTION);
+3
View File
@@ -226,6 +226,9 @@ describe("agent instruction wiring", () => {
expect(capturedPrompt).toContain("Tell your manager the plan in a direct message.");
expect(capturedPrompt).not.toContain("#general");
expect(capturedPrompt).not.toContain("Then do the work: write real output");
// A task without file access plans in a document; it is never told to write files.
expect(capturedPrompt).toContain("write the initial operating plan as a document (create_document)");
expect(capturedPrompt).not.toContain("(write_deliverable)");
connection.close();
});
});
+11
View File
@@ -191,6 +191,17 @@ describe("execution modes", () => {
expect(db.prepare("select state from approvals where id = ?").get(review)).toEqual({ state: "pending" });
});
test("a task without file access cannot write files, directly or by holding them for review", () => {
for (const mode of ["ai_ceo", "ai_cofounder"] as const) {
const h = harness(mode);
const readOnly = { ...h.ctx, workspace: { ...h.ctx.workspace!, writeAccess: false } };
expect(dispatchAgentTool(h.deps, readOnly, call("write_deliverable", { path: "notes.md", content: "x" })))
.toBe("ERROR: This task has read-only workspace access, so it cannot write files. Put the output in a document (create_document), or ask for a task with requires_filesystem, which the operator approves.");
expect(existsSync(join(h.ctx.workspace!.localPath, "notes.md"))).toBe(false);
expect(h.connection.db.prepare("select count(*) as n from held_deliverables").get()).toEqual({ n: 0 });
}
});
test("AI CEO mode ships deliverables without review", () => {
const h = harness("ai_ceo");
const wrote = dispatchAgentTool(h.deps, h.ctx, call("write_deliverable", { path: "plan/business-plan.md", content: "# Plan" }));