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

@@ -0,0 +1,63 @@
import type { ProviderService } from "./provider-service";
import { AGENT_TOOLS, dispatchAgentTool, type AgentToolContext, type AgentToolDeps } from "./agent-tools";
import type { AgentTurnMessage } from "./provider-tool-turn";
const MAX_ITERATIONS = 12;
export interface AgentRunLoopInput {
orgId: string;
providerId: string;
workspaceId: string | null;
instructions: string;
initialUserTurn: string;
toolContext: AgentToolContext;
}
export interface AgentRunLoopResult {
finalText: string;
iterations: number;
toolCallCount: number;
lastRequestId: string;
}
export interface AgentRunLoopDeps {
providers: Pick<ProviderService, "runToolTurn">;
tools: AgentToolDeps;
}
/**
* Drives a real tool-use loop against the live provider: prompt the model with
* the agent tools, execute every tool call it makes, feed results back, and
* repeat until the model returns a turn with no tool calls (it is done) or the
* iteration cap is hit (which throws rather than silently truncating).
*/
export async function runAgentLoop(deps: AgentRunLoopDeps, input: AgentRunLoopInput): Promise<AgentRunLoopResult> {
const messages: AgentTurnMessage[] = [{ role: "user", text: input.initialUserTurn }];
let toolCallCount = 0;
let lastRequestId = "";
for (let iteration = 1; iteration <= MAX_ITERATIONS; iteration += 1) {
const turn = await deps.providers.runToolTurn({
orgId: input.orgId,
providerId: input.providerId,
workspaceId: input.workspaceId,
instructions: input.instructions,
messages,
tools: AGENT_TOOLS,
});
lastRequestId = turn.requestId;
if (turn.toolCalls.length === 0) {
return { finalText: turn.text, iterations: iteration, toolCallCount, lastRequestId };
}
messages.push({ role: "assistant", text: turn.text, toolCalls: turn.toolCalls });
const results = turn.toolCalls.map((call) => {
toolCallCount += 1;
return { toolCallId: call.id, toolName: call.name, output: dispatchAgentTool(deps.tools, input.toolContext, call) };
});
messages.push({ role: "tool", results });
}
throw new Error(`Agent run did not converge within ${MAX_ITERATIONS} iterations.`);
}