Q3: record a structured handoff on every delegated task

create_task now pins upstream files by content hash and carries the
source task, source thread, verified facts, open questions and
acceptance criteria into the assignee's prompt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ZacharyZhang-NYandClaude Opus 5.5 committed 2026-10-08 02:35:12 -04:00
1 parent 9f2adce7b3
commit 6570492daa
22 files changed
+219 -28

No files matched your search

+1 -1
View File
@@ -57,7 +57,7 @@ function harness(options: { enableProvider: boolean }): Harness {
orgId,
agent: { id: ceoId, modelProviderId: null, modelId: null },
taskProjectId: null,
taskGoalId: null,
taskGoalId: null, taskId: null, sourceThreadId: null,
workspace: null,
},
};
+2 -2
View File
@@ -70,7 +70,7 @@ function argsFor(h: Harness, agentId: string, auditor: Auditor, toolCall: ToolCa
orgId: h.orgId,
agent: { id: agentId, modelProviderId: h.providerId, modelId: "gpt-5.4" },
taskProjectId: null,
taskGoalId: h.goalId,
taskGoalId: h.goalId, taskId: null, sourceThreadId: null,
workspace: { id: "wsp_audit", localPath: workspaceRoot, writeAccess: true },
};
return { deps: h.deps, ctx, providers: { runToolTurn: auditor }, providerId: h.providerId, workspaceId: null, call: toolCall };
@@ -158,7 +158,7 @@ function toolCtx(h: Harness, agentId: string): AgentToolContext {
orgId: h.orgId,
agent: { id: agentId, modelProviderId: h.providerId, modelId: "gpt-5.4" },
taskProjectId: null,
taskGoalId: null,
taskGoalId: null, taskId: null, sourceThreadId: null,
workspace: null,
};
}
+2 -2
View File
@@ -29,7 +29,7 @@ describe("agent messaging", () => {
const deps = { db: connection.db, organization, chat: new ChatService(connection.db) };
const ctx: AgentToolContext = {
orgId, agent: { id: ceoId, modelProviderId: null, modelId: null },
taskProjectId: null, taskGoalId: null, workspace: null,
taskProjectId: null, taskGoalId: null, taskId: null, sourceThreadId: null, workspace: null,
};
const first = dispatchAgentTool(deps, ctx, call("send_message", { channel: "engineering", body: "Assigning the booking API to Dana." }));
@@ -85,7 +85,7 @@ describe("agent messaging", () => {
const deps = { db: connection.db, organization, chat: new ChatService(connection.db) };
const ctx: AgentToolContext = {
orgId, agent: { id: ceoId, modelProviderId: null, modelId: null },
taskProjectId: null, taskGoalId: null, workspace: null,
taskProjectId: null, taskGoalId: null, taskId: null, sourceThreadId: null, workspace: null,
};
const result = dispatchAgentTool(deps, ctx, call("send_message", { channel: "engineering", body: "@Dana can you take this?" }));
+4 -4
View File
@@ -66,7 +66,7 @@ describe("agent run loop (real tool dispatch)", () => {
orgId,
agent: { id: ceoId, modelProviderId: providerId, modelId: "gpt-5.4" },
taskProjectId: null,
taskGoalId: null,
taskGoalId: null, taskId: null, sourceThreadId: null,
workspace: { id: workspaceId, localPath: workspaceRoot, writeAccess: true },
};
@@ -139,7 +139,7 @@ describe("agent run loop (real tool dispatch)", () => {
});
const ctx: AgentToolContext = {
orgId, agent: { id: ceoId, modelProviderId: providerId, modelId: "gpt-5.4" },
taskProjectId: null, taskGoalId: null, workspace: { id: "wsp", localPath: workspaceRoot, writeAccess: true },
taskProjectId: null, taskGoalId: null, taskId: null, sourceThreadId: null, workspace: { id: "wsp", localPath: workspaceRoot, writeAccess: true },
};
const script: ScriptedTurn[] = [
{ text: "escape", toolCalls: [call("write_deliverable", { path: "../escape.txt", content: "x" })] },
@@ -174,7 +174,7 @@ describe("agent run loop (real tool dispatch)", () => {
});
const ctx: AgentToolContext = {
orgId, agent: { id: ceoId, modelProviderId: providerId, modelId: "gpt-5.4" },
taskProjectId: null, taskGoalId: null, workspace: null,
taskProjectId: null, taskGoalId: null, taskId: null, sourceThreadId: null, workspace: null,
};
const script: ScriptedTurn[] = [
{ text: "staff", toolCalls: [call("hire_agent", { name: "Alex Rivera", role: "Engineer", department: "Engineering" })] },
@@ -226,7 +226,7 @@ describe("agent run loop (real tool dispatch)", () => {
});
const ctx: AgentToolContext = {
orgId, agent: { id: ceoId, modelProviderId: providerId, modelId: "gpt-5.4" },
taskProjectId: null, taskGoalId: null, workspace: null,
taskProjectId: null, taskGoalId: null, taskId: null, sourceThreadId: null, workspace: null,
};
await expect(runAgentLoop(
{ providers: fakeProvider([{ text: "", toolCalls: [] }], connection.db), tools: { db: connection.db, organization, chat: new ChatService(connection.db) } },
+96
View File
@@ -0,0 +1,96 @@
import { createHash, randomUUID } from "node:crypto";
import { rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { describe, expect, test } from "vitest";
import { DatabaseConnection } from "../../src/main/database/connection";
import { OrganizationService } from "../../src/main/services/organization-service";
import { ChatService } from "../../src/main/services/chat-service";
import { dispatchAgentTool, type AgentToolContext } from "../../src/main/services/agent-tools";
import { renderTaskHandoff } from "../../src/main/services/agent-task-handoff";
import { RISK_LEVEL, TASK_PRIORITY, TASK_STATUS } from "../../src/shared/domain";
import type { ToolCall } from "../../src/main/services/provider-tool-turn";
function sha256(text: string): string {
return createHash("sha256").update(text).digest("hex");
}
function call(name: string, args: Record<string, unknown>): ToolCall {
return { id: `call_${randomUUID()}`, name, arguments: args };
}
function harness() {
const connection = new DatabaseConnection(join(tmpdir(), `ac-handoff-${randomUUID()}.db`));
const organization = new OrganizationService(connection.db);
let orgId = "";
let ceoId = "";
let sourceTaskId = "";
connection.transaction(() => {
orgId = organization.createOrganization({ organizationName: "HandoffCo", employeeName: "Yang", mission: "m", goal: "g" });
const providerId = (connection.db.prepare("select id from providers where org_id = ? and provider_key = 'openai'").get(orgId) as { id: string }).id;
ceoId = organization.createAgent({
orgId, name: "CEO", role: "CEO", department: "Executive",
modelProviderId: providerId, modelId: "gpt-5.4", budgetCents: 25000, managerAgentId: null, roleLevel: "c_suite",
});
sourceTaskId = organization.createTask({
orgId, title: "Plan the launch", description: "d", status: TASK_STATUS.IN_PROGRESS, assigneeAgentId: ceoId,
risk: RISK_LEVEL.LOW, projectId: null, goalId: null, workspaceId: null, priority: TASK_PRIORITY.NORMAL, requiresFilesystem: false,
});
});
const specPath = join(tmpdir(), `ac-handoff-spec-${randomUUID()}.md`);
writeFileSync(specPath, "v1");
connection.db.prepare(`
insert into file_objects(id, org_id, name, kind, size_bytes, sha256, storage_path, owner_type, owner_id, created_at)
values ('fil_spec', ?, 'spec.md', 'document', 2, ?, ?, 'agent', ?, ?)
`).run(orgId, sha256("v1"), specPath, ceoId, new Date().toISOString());
const deps = { db: connection.db, organization, chat: new ChatService(connection.db) };
const ctx: AgentToolContext = {
orgId, agent: { id: ceoId, modelProviderId: null, modelId: null },
taskProjectId: null, taskGoalId: null, taskId: sourceTaskId, sourceThreadId: "thr_src", workspace: null,
};
return { connection, deps, ctx, orgId, sourceTaskId, specPath };
}
describe("structured task handoff", () => {
test("create_task pins inputs and carries facts, questions, and acceptance criteria", () => {
const h = harness();
const result = dispatchAgentTool(h.deps, h.ctx, call("create_task", {
title: "Build the API", description: "Ship it.",
input_file_ids: ["fil_spec"], facts: ["Postgres is the store"], open_questions: ["Rate limits?"],
acceptance_criteria: ["All endpoint tests pass"],
}));
expect(result).toContain("Created task");
const taskId = /task_id=([^)\s]+)\)/.exec(result)![1];
const row = h.connection.db.prepare("select handoff_json from tasks where id = ?").get(taskId) as { handoff_json: string };
expect(JSON.parse(row.handoff_json)).toEqual({
sourceTaskId: h.sourceTaskId,
sourceThreadId: "thr_src",
inputs: [{ fileId: "fil_spec", name: "spec.md", sha256: sha256("v1") }],
facts: ["Postgres is the store"],
openQuestions: ["Rate limits?"],
acceptanceCriteria: ["All endpoint tests pass"],
});
const before = renderTaskHandoff(h.connection.db, h.orgId, row.handoff_json).join("\n");
expect(before).toContain('Delegated from task "Plan the launch"');
expect(before).toContain('thread_id="thr_src"');
expect(before).toContain(`spec.md (file_id=fil_spec, sha256=${sha256("v1").slice(0, 12)}): unchanged since the handoff`);
expect(before).toContain("Verified: Postgres is the store");
expect(before).toContain("Open question: Rate limits?");
expect(before).toContain("Done when: All endpoint tests pass");
writeFileSync(h.specPath, "v2 edited outside the file registry");
expect(renderTaskHandoff(h.connection.db, h.orgId, row.handoff_json).join("\n")).toContain("CHANGED since the handoff");
rmSync(h.specPath);
expect(renderTaskHandoff(h.connection.db, h.orgId, row.handoff_json).join("\n")).toContain("deleted since the handoff");
h.connection.close();
});
test("an unknown input file id fails the call and creates nothing", () => {
const h = harness();
expect(dispatchAgentTool(h.deps, h.ctx, call("create_task", { title: "X", description: "d", input_file_ids: ["fil_missing"] })))
.toBe('ERROR: No file with file_id "fil_missing" exists in this company.');
expect(h.connection.db.prepare("select count(*) as n from tasks where title = 'X'").get()).toEqual({ n: 0 });
h.connection.close();
});
});
+2 -2
View File
@@ -30,7 +30,7 @@ function ctx(writeAccess: boolean): AgentToolContext {
orgId: "org_test",
agent: { id: "agt_test", modelProviderId: null, modelId: null },
taskProjectId: null,
taskGoalId: null,
taskGoalId: null, taskId: null, sourceThreadId: null,
workspace: { id: "wsp_test", localPath: workspaceRoot, writeAccess },
};
}
@@ -91,7 +91,7 @@ describe("agent workspace tools", () => {
const deps = { db: connection.db, organization, chat: new ChatService(connection.db) };
const toolCtx: AgentToolContext = {
orgId, agent: { id: ceoId, modelProviderId: providerId, modelId: "gpt-5.4" },
taskProjectId: null, taskGoalId: null, workspace: null,
taskProjectId: null, taskGoalId: null, taskId: null, sourceThreadId: null, workspace: null,
};
const result = dispatchAgentTool(deps, toolCtx, call("create_task", {
+1 -1
View File
@@ -162,7 +162,7 @@ describe("built-in skills", () => {
orgId: h.orgId,
agent: { id: ceoId, modelProviderId: providerId, modelId: "gpt-5.4" },
taskProjectId: null,
taskGoalId: null,
taskGoalId: null, taskId: null, sourceThreadId: null,
workspace: null,
};
const deps = { db: h.connection.db, organization: h.organization, chat: new ChatService(h.connection.db) };
+1 -1
View File
@@ -50,7 +50,7 @@ describe("queue claim priority", () => {
dispatchAgentTool(deps, {
orgId: h.orgId,
agent: { id: h.ceoId, modelProviderId: null, modelId: null },
taskProjectId: null, taskGoalId: null, workspace: null,
taskProjectId: null, taskGoalId: null, taskId: null, sourceThreadId: null, workspace: null,
}, { id: `call_${randomUUID()}`, name: "send_message", arguments: { to: "Dana", body: "Status?" } });
const danaRun = h.connection.db.prepare(
"select id from task_runs where org_id = ? and agent_id = ? and trigger = 'chat_mention'",
+1 -1
View File
@@ -44,7 +44,7 @@ function harness(): Harness {
orgId,
agent: { id: agentId, modelProviderId: null, modelId: null },
taskProjectId: null,
taskGoalId: null,
taskGoalId: null, taskId: null, sourceThreadId: null,
workspace: null,
});
return {
+1 -1
View File
@@ -24,7 +24,7 @@ describe("domain services", () => {
const connection = createConnection();
const rows = connection.db.prepare("select version from schema_migrations order by version asc").all() as Array<{ version: number }>;
expect(rows.map((row) => row.version)).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58]);
expect(rows.map((row) => row.version)).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59]);
connection.close();
});
+1 -1
View File
@@ -53,7 +53,7 @@ function harness(mode: "ai_ceo" | "ai_cofounder"): Harness {
orgId,
agent: { id: ceoId, modelProviderId: null, modelId: null },
taskProjectId: null,
taskGoalId: null,
taskGoalId: null, taskId: null, sourceThreadId: null,
workspace: { id: workspaceId, localPath: workspaceRoot, writeAccess: true },
},
deps: { db: connection.db, organization, chat: new ChatService(connection.db) },
+4 -4
View File
@@ -45,7 +45,7 @@ describe("mention resolution", () => {
const deps = { db: connection.db, organization, chat: new ChatService(connection.db) };
const ctx: AgentToolContext = {
orgId, agent: { id: ceoId, modelProviderId: providerId, modelId: "gpt-5.4" },
taskProjectId: null, taskGoalId: null, workspace: null,
taskProjectId: null, taskGoalId: null, taskId: null, sourceThreadId: null, workspace: null,
};
const result = dispatchAgentTool(deps, ctx, call("send_message", {
@@ -79,7 +79,7 @@ describe("mention resolution", () => {
const deps = { db: connection.db, organization, chat: new ChatService(connection.db) };
const ctx: AgentToolContext = {
orgId, agent: { id: ceoId, modelProviderId: null, modelId: null },
taskProjectId: null, taskGoalId: null, workspace: null,
taskProjectId: null, taskGoalId: null, taskId: null, sourceThreadId: null, workspace: null,
};
dispatchAgentTool(deps, ctx, call("send_message", { channel: "general", body: "Hello team!" }));
@@ -113,14 +113,14 @@ describe("mention resolution", () => {
const deps = { db: connection.db, organization, chat: new ChatService(connection.db) };
const ceoCtx: AgentToolContext = {
orgId, agent: { id: ceoId, modelProviderId: null, modelId: null },
taskProjectId: null, taskGoalId: null, workspace: null,
taskProjectId: null, taskGoalId: null, taskId: null, sourceThreadId: null, workspace: null,
};
dispatchAgentTool(deps, ceoCtx, call("send_message", { channel: "engineering", body: "@dana check the deployment" }));
const danaCtx: AgentToolContext = {
orgId, agent: { id: engineerId, modelProviderId: null, modelId: null },
taskProjectId: null, taskGoalId: null, workspace: null,
taskProjectId: null, taskGoalId: null, taskId: null, sourceThreadId: null, workspace: null,
};
const inbox = dispatchAgentTool(deps, danaCtx, call("read_messages", {}));
expect(inbox).toContain("check the deployment");
+1 -1
View File
@@ -31,7 +31,7 @@ describe("org structure", () => {
orgId,
agent: { id: agentId, modelProviderId: providerId, modelId: "gpt-5.4" },
taskProjectId: null,
taskGoalId: null,
taskGoalId: null, taskId: null, sourceThreadId: null,
workspace: null,
});
+1 -1
View File
@@ -44,7 +44,7 @@ function harness(): Harness {
orgId,
agent: { id: agentId, modelProviderId: null, modelId: null },
taskProjectId: null,
taskGoalId: null,
taskGoalId: null, taskId: null, sourceThreadId: null,
workspace: null,
});
return {