Run the CEO agent autonomously with a real tool-use loop

The new app reached a real LLM but never ran an agent: the kickoff task_run
sat queued forever and runTask was a single text completion with no tools.

This adds the missing runtime:
- provider-tool-turn: OpenAI Responses + Anthropic Messages tool-use turns
- agent-tools + deliverable-writer: hire_agent, create_task,
  update_task_status, write_deliverable (workspace-scoped, traversal-safe)
- agent-run-loop: real tool-use loop, capped iterations
- task-queue-worker: claims queued runs in a transaction and executes them

task-execution now drives the loop; a background worker auto-runs the
bootstrap CEO run so it hires its team, delegates tasks, and writes the first
deliverable into the bound workspace. Autonomy is gated by AGENTCOMPANY_NEW_AUTORUN
(tests run with it off to stay hermetic).
This commit is contained in:
ZacharyZhang-NY committed 2026-05-29 21:08:04 -04:00
1 parent 1c7b81830e
commit 0575712e02
15 files changed
+1008 -74

No files matched your search

@@ -7,7 +7,8 @@ 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, type ProviderAdapterRegistry } from "../../src/main/services/provider-adapters";
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";
@@ -33,21 +34,12 @@ describe("task execution service", () => {
const organization = new OrganizationService(connection.db);
const snapshot = new SnapshotService(connection.db);
let capturedPrompt = "";
const adapters: ProviderAdapterRegistry = {
...providerAdapters,
openai: {
...providerAdapters.openai,
createResponse: async ({ prompt, model }) => {
capturedPrompt = prompt;
return {
text: `executed:${model}`,
rawResponseJson: JSON.stringify({ id: "resp-task-1" }),
rateLimitState: null,
};
},
},
const fakeToolTurn = async (input: ProviderToolTurnInput): Promise<AgentTurnResult> => {
const first = input.messages[0];
if (first?.role === "user") capturedPrompt = first.text;
return { text: "executed:gpt-5.4", toolCalls: [], rawResponseJson: JSON.stringify({ id: "resp-task-1" }) };
};
const providers = new ProviderService(connection.db, adapters, testVault);
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()}`);
@@ -149,18 +141,12 @@ describe("task execution service", () => {
const organization = new OrganizationService(connection.db);
const approvals = new ApprovalService(connection.db);
const snapshot = new SnapshotService(connection.db);
const adapters: ProviderAdapterRegistry = {
...providerAdapters,
openai: {
...providerAdapters.openai,
createResponse: async ({ model }) => ({
text: `executed:${model}`,
rawResponseJson: JSON.stringify({ id: "resp-task-approved" }),
rateLimitState: null,
}),
},
};
const providers = new ProviderService(connection.db, adapters, testVault);
const fakeToolTurn = async (): Promise<AgentTurnResult> => ({
text: "executed:gpt-5.4",
toolCalls: [],
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 = "";
@@ -231,16 +217,10 @@ describe("task execution service", () => {
const connection = createConnection();
const organization = new OrganizationService(connection.db);
const snapshot = new SnapshotService(connection.db);
const adapters: ProviderAdapterRegistry = {
...providerAdapters,
openai: {
...providerAdapters.openai,
createResponse: async () => {
throw new Error("provider rejected task");
},
},
const fakeToolTurn = async (): Promise<AgentTurnResult> => {
throw new Error("provider rejected task");
};
const providers = new ProviderService(connection.db, adapters, testVault);
const providers = new ProviderService(connection.db, providerAdapters, testVault, undefined, fakeToolTurn);
const execution = new TaskExecutionService(connection.db, providers);
let orgId = "";
let taskId = "";
@@ -282,7 +262,7 @@ describe("task execution service", () => {
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.id === run?.providerRequestId);
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" });