Files
AgentCompany/AgentCompany-New/tests/unit/task-execution-service.test.ts
T
ZacharyZhang-NYandClaude Opus 4.8 f7543e506d Compel agents to keep acting until they explicitly finish
Forcing only the opening turn let a weak model take one token action and
stop, so a kickoff hired one agent (or none) and went quiet. Now every turn
is forced (tool_choice required) and the agent ends only by calling the new
`finish` tool — so it must keep hiring, delegating, and messaging until the
work is actually done. Finishing with zero actions, or returning no tool
call at all, fails the run loudly instead of faking success.

Prompts tell each agent it must call a tool every turn and finish only when
done. Tests drive weak-model stubs that act then finish, proving a full team
forms rather than a single gesture.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 22:17:49 -04:00

324 lines
15 KiB
TypeScript

import { mkdirSync, rmSync, writeFileSync } from "node:fs";
import { randomUUID } from "node:crypto";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { describe, expect, test } from "vitest";
import { DatabaseConnection } from "../../src/main/database/connection";
import { ApprovalService } from "../../src/main/services/approval-service";
import { OrganizationService } from "../../src/main/services/organization-service";
import { ProviderService } from "../../src/main/services/provider-service";
import { providerAdapters } from "../../src/main/services/provider-adapters";
import type { AgentTurnResult, ProviderToolTurnInput } from "../../src/main/services/provider-tool-turn";
import { SkillService } from "../../src/main/services/skill-service";
import { SnapshotService } from "../../src/main/services/snapshot-service";
import { TaskExecutionService } from "../../src/main/services/task-execution-service";
import { APPROVAL_STATE, RISK_LEVEL, TASK_PRIORITY, TASK_STATUS } from "../../src/shared/domain";
function createConnection(): DatabaseConnection {
return new DatabaseConnection(join(tmpdir(), `agentcompany-new-task-execution-${randomUUID()}.db`));
}
function openAiProviderId(connection: DatabaseConnection, orgId: string): string {
return (connection.db.prepare("select id from providers where org_id = ? and provider_key = 'openai'")
.get(orgId) as { id: string }).id;
}
const testVault = {
encryptSecret: (secret: string) => Buffer.from(secret, "utf8"),
decryptSecret: (ciphertext: Buffer) => ciphertext.toString("utf8"),
};
describe("task execution service", () => {
test("runs an assigned task through the provider gateway", async () => {
const connection = createConnection();
const organization = new OrganizationService(connection.db);
const snapshot = new SnapshotService(connection.db);
let capturedPrompt = "";
const fakeToolTurn = async (input: ProviderToolTurnInput): Promise<AgentTurnResult> => {
const first = input.messages[0];
if (first?.role === "user") capturedPrompt = first.text;
const acted = input.messages.some((message) => message.role === "tool");
const toolCall = acted
? { id: `call_${randomUUID()}`, name: "finish", arguments: {} }
: { id: `call_${randomUUID()}`, name: "create_document", arguments: { title: "Run note", doc_type: "memo", content: "Executed." } };
return { text: "executed:gpt-5.4", toolCalls: [toolCall], rawResponseJson: JSON.stringify({ id: "resp-task-1" }) };
};
const providers = new ProviderService(connection.db, providerAdapters, testVault, undefined, fakeToolTurn);
const skills = new SkillService(connection.db, join(tmpdir(), `agentcompany-task-skills-${randomUUID()}`));
const execution = new TaskExecutionService(connection.db, providers);
const skillDir = join(tmpdir(), `agentcompany-task-skill-${randomUUID()}`);
let orgId = "";
let agentId = "";
let providerId = "";
let taskId = "";
let workspaceId = "";
let revisionId = "";
mkdirSync(skillDir, { recursive: true });
writeFileSync(join(skillDir, "SKILL.md"), "# Release Execution\n\nVerify release readiness and list concrete evidence.\n");
writeFileSync(join(skillDir, "skill.manifest.json"), JSON.stringify({
name: "release-execution",
version: "1.0.0",
entrypoint: "SKILL.md",
capabilities: ["release-readiness"],
required_tools: ["file_search"],
compatible_models: ["openai/*"],
risk_level: "medium",
inputs: { task: "markdown" },
outputs: { result: "markdown" },
tests: [],
}));
connection.transaction(() => {
orgId = organization.createOrganization({
organizationName: "Acme Operations",
employeeName: "Dana Lee",
mission: "Coordinate agents with auditable approvals.",
goal: "Ship a verified control plane.",
});
providerId = openAiProviderId(connection, orgId);
providers.saveCredential({ orgId, providerId, secret: "sk-live-provider", model: "gpt-5.4" });
workspaceId = "wsp_task_execution";
const now = new Date().toISOString();
connection.db.prepare(`
insert into workspaces(id, org_id, name, local_path, repo_url, repo_ref, project_id, is_primary, created_at, updated_at)
values (?, ?, ?, ?, ?, ?, null, ?, ?, ?)
`).run(workspaceId, orgId, "Task Workspace", "C:/work/task", "https://example.com/task.git", "main", 1, now, now);
agentId = organization.createAgent({
orgId,
name: "Ada",
role: "Execution lead",
department: "Engineering",
modelProviderId: providerId,
modelId: "gpt-5.4",
budgetCents: 10000,
managerAgentId: null,
});
revisionId = skills.importDirectory({ orgId, directoryPath: skillDir, versionSemver: "1.0.0" });
const skill = connection.db.prepare("select id from skill_definitions where org_id = ? and slug = ?")
.get(orgId, "release-execution") as { id: string };
skills.bind({ orgId, agentId, skillId: skill.id, revisionId });
taskId = organization.createTask({
orgId,
title: "Draft launch checklist",
description: "Prepare an auditable checklist for release readiness.",
status: TASK_STATUS.IN_PROGRESS,
assigneeAgentId: agentId,
risk: RISK_LEVEL.MEDIUM,
projectId: null,
goalId: null,
workspaceId,
priority: TASK_PRIORITY.HIGH,
requiresFilesystem: false,
});
});
const runId = await execution.runTask({ orgId, taskId });
const state = snapshot.load();
const run = state.taskRuns.find((item) => item.id === runId);
const request = state.providerRequests.find((item) => item.id === run?.providerRequestId);
const skillRuntimeSnapshot = state.skillRuntimeSnapshots.find((item) => item.runId === runId);
expect(run).toMatchObject({ taskId, agentId: expect.any(String), providerId, state: "completed", errorMessage: null });
expect(request).toMatchObject({ providerId, workspaceId, state: "succeeded", responseText: "executed:gpt-5.4" });
expect(skillRuntimeSnapshot).toMatchObject({ agentId, runId });
expect(run?.executionContextId).toMatch(/^ctx_/);
expect(run?.serviceIdentityId).toMatch(/^sid_/);
expect(JSON.parse(run?.workspaceMountJson ?? "[]")).toEqual([expect.objectContaining({ workspaceId, mode: "read_only" })]);
expect(JSON.parse(run?.secretScopeJson ?? "{}")).toMatchObject({ providerIds: [providerId], credentialAccess: "provider_runtime_only" });
expect(skillRuntimeSnapshot?.compiledPrompt).toContain("Verify release readiness");
expect(skillRuntimeSnapshot?.sourceRevisionIds).toContain(revisionId);
expect(capturedPrompt).toContain("Task: Draft launch checklist");
expect(capturedPrompt).toContain("Assignee: Ada (Execution lead)");
expect(capturedPrompt).toContain("Workspace: Task Workspace");
expect(capturedPrompt).toContain("Execution context:");
expect(capturedPrompt).toContain("Secret scope:");
expect(capturedPrompt).toContain("Skill runtime:");
expect(capturedPrompt).toContain("Tool allowlist:");
expect(state.auditEvents.some((event) => event.entityType === "task_run" && event.action === "completed")).toBe(true);
rmSync(skillDir, { recursive: true, force: true });
connection.close();
});
test("requires approved execution approval for high risk tasks", async () => {
const connection = createConnection();
const organization = new OrganizationService(connection.db);
const approvals = new ApprovalService(connection.db);
const snapshot = new SnapshotService(connection.db);
const fakeToolTurn = async (input: ProviderToolTurnInput): Promise<AgentTurnResult> => {
const acted = input.messages.some((message) => message.role === "tool");
const toolCall = acted
? { id: `call_${randomUUID()}`, name: "finish", arguments: {} }
: { id: `call_${randomUUID()}`, name: "create_document", arguments: { title: "Run note", doc_type: "memo", content: "Executed." } };
return { text: "executed:gpt-5.4", toolCalls: [toolCall], rawResponseJson: JSON.stringify({ id: "resp-task-approved" }) };
};
const providers = new ProviderService(connection.db, providerAdapters, testVault, undefined, fakeToolTurn);
const execution = new TaskExecutionService(connection.db, providers);
let orgId = "";
let employeeId = "";
let taskId = "";
let approvalId = "";
let workspaceId = "";
connection.transaction(() => {
orgId = organization.createOrganization({
organizationName: "Acme Operations",
employeeName: "Dana Lee",
mission: "Coordinate agents with auditable approvals.",
goal: "Ship a verified control plane.",
executionMode: "ai_cofounder",
});
employeeId = (connection.db.prepare("select id from employees where org_id = ?").get(orgId) as { id: string }).id;
const providerId = openAiProviderId(connection, orgId);
providers.saveCredential({ orgId, providerId, secret: "sk-live-provider", model: "gpt-5.4" });
workspaceId = "wsp_production";
const now = new Date().toISOString();
connection.db.prepare(`
insert into workspaces(id, org_id, name, local_path, repo_url, repo_ref, project_id, is_primary, created_at, updated_at)
values (?, ?, ?, ?, ?, ?, null, ?, ?, ?)
`).run(workspaceId, orgId, "Production", "C:/work/production", "https://example.com/production.git", "main", 1, now, now);
const agentId = organization.createAgent({
orgId,
name: "Ada",
role: "Execution lead",
department: "Engineering",
modelProviderId: providerId,
modelId: "gpt-5.4",
budgetCents: 10000,
managerAgentId: null,
});
taskId = organization.createTask({
orgId,
title: "Production deploy",
description: "Deploy a high-risk release.",
status: TASK_STATUS.IN_PROGRESS,
assigneeAgentId: agentId,
risk: RISK_LEVEL.HIGH,
projectId: null,
goalId: null,
workspaceId,
priority: TASK_PRIORITY.HIGH,
requiresFilesystem: true,
});
approvalId = (connection.db.prepare(`
select id from approvals
where org_id = ? and source_entity_type = 'task' and source_entity_id = ?
`).get(orgId, taskId) as { id: string }).id;
});
await expect(execution.runTask({ orgId, taskId })).rejects.toThrow("Approved task execution approval is required.");
expect(connection.db.prepare("select count(*) as count from task_runs").get()).toEqual({ count: 0 });
connection.transaction(() => approvals.decide({ orgId, approvalId, state: APPROVAL_STATE.APPROVED, decidedByEmployeeId: employeeId }));
const runId = await execution.runTask({ orgId, taskId });
const state = snapshot.load();
expect(state.approvals.find((approval) => approval.id === approvalId)).toMatchObject({ state: APPROVAL_STATE.APPROVED });
expect(state.taskRuns.find((run) => run.id === runId)).toMatchObject({ taskId, state: "completed" });
expect(JSON.parse(state.taskRuns.find((run) => run.id === runId)?.workspaceMountJson ?? "[]"))
.toEqual([expect.objectContaining({ workspaceId, mode: "read_write" })]);
connection.close();
});
test("marks task runs failed when provider execution fails", async () => {
const connection = createConnection();
const organization = new OrganizationService(connection.db);
const snapshot = new SnapshotService(connection.db);
const fakeToolTurn = async (): Promise<AgentTurnResult> => {
throw new Error("provider rejected task");
};
const providers = new ProviderService(connection.db, providerAdapters, testVault, undefined, fakeToolTurn);
const execution = new TaskExecutionService(connection.db, providers);
let orgId = "";
let taskId = "";
connection.transaction(() => {
orgId = organization.createOrganization({
organizationName: "Acme Operations",
employeeName: "Dana Lee",
mission: "Coordinate agents with auditable approvals.",
goal: "Ship a verified control plane.",
});
const providerId = openAiProviderId(connection, orgId);
providers.saveCredential({ orgId, providerId, secret: "sk-live-provider", model: "gpt-5.4" });
const agentId = organization.createAgent({
orgId,
name: "Ada",
role: "Execution lead",
department: "Engineering",
modelProviderId: providerId,
modelId: "gpt-5.4",
budgetCents: 10000,
managerAgentId: null,
});
taskId = organization.createTask({
orgId,
title: "Draft launch checklist",
description: "Prepare an auditable checklist for release readiness.",
status: TASK_STATUS.IN_PROGRESS,
assigneeAgentId: agentId,
risk: RISK_LEVEL.MEDIUM,
projectId: null,
goalId: null,
workspaceId: null,
priority: TASK_PRIORITY.HIGH,
requiresFilesystem: false,
});
});
await expect(execution.runTask({ orgId, taskId })).rejects.toThrow("provider rejected task");
const state = snapshot.load();
const run = state.taskRuns[0];
const request = state.providerRequests.find((item) => item.state === "failed");
expect(run).toMatchObject({ taskId, state: "failed", errorMessage: "provider rejected task" });
expect(request).toMatchObject({ state: "failed", errorMessage: "provider rejected task" });
expect(state.auditEvents.some((event) => event.entityType === "task_run" && event.action === "failed")).toBe(true);
connection.close();
});
test("requires task assignees to have a model provider", async () => {
const connection = createConnection();
const organization = new OrganizationService(connection.db);
const execution = new TaskExecutionService(connection.db);
let orgId = "";
let taskId = "";
connection.transaction(() => {
orgId = organization.createOrganization({
organizationName: "Acme Operations",
employeeName: "Dana Lee",
mission: "Coordinate agents with auditable approvals.",
goal: "Ship a verified control plane.",
});
const agentId = organization.createAgent({
orgId,
name: "Ada",
role: "Execution lead",
department: "Engineering",
modelProviderId: null,
modelId: null,
budgetCents: 10000,
managerAgentId: null,
});
taskId = organization.createTask({
orgId,
title: "Draft launch checklist",
description: "Prepare an auditable checklist for release readiness.",
status: TASK_STATUS.IN_PROGRESS,
assigneeAgentId: agentId,
risk: RISK_LEVEL.MEDIUM,
projectId: null,
goalId: null,
workspaceId: null,
priority: TASK_PRIORITY.HIGH,
requiresFilesystem: false,
});
});
await expect(execution.runTask({ orgId, taskId })).rejects.toThrow("Task assignee requires a model provider.");
expect(connection.db.prepare("select count(*) as count from task_runs").get()).toEqual({ count: 0 });
connection.close();
});
});