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:
1 parent
1c7b81830e
commit
0575712e02
15 files changed
+1008
-74
No files matched your search
@@ -1,10 +1,13 @@
|
||||
import type { DatabaseSync } from "node:sqlite";
|
||||
import type { RunTaskInput } from "../../shared/commands";
|
||||
import { APPROVAL_STATE, PROVIDER_EXECUTION_MODE, RISK_LEVEL } from "../../shared/domain";
|
||||
import { APPROVAL_STATE, RISK_LEVEL } from "../../shared/domain";
|
||||
import { createId } from "../../shared/ids";
|
||||
import { AuditService } from "./audit-service";
|
||||
import { ProviderService } from "./provider-service";
|
||||
import { OrganizationService } from "./organization-service";
|
||||
import { SkillService } from "./skill-service";
|
||||
import { runAgentLoop } from "./agent-run-loop";
|
||||
import type { AgentToolContext } from "./agent-tools";
|
||||
|
||||
const TASK_RUN_STATE = {
|
||||
RUNNING: "running",
|
||||
@@ -82,8 +85,19 @@ interface TaskRunIsolation {
|
||||
secretScopeJson: string;
|
||||
}
|
||||
|
||||
interface RunRow {
|
||||
task_id: string;
|
||||
agent_id: string;
|
||||
provider_id: string;
|
||||
execution_context_id: string;
|
||||
service_identity_id: string;
|
||||
workspace_mount_json: string;
|
||||
secret_scope_json: string;
|
||||
}
|
||||
|
||||
export class TaskExecutionService {
|
||||
private readonly audit: AuditService;
|
||||
private readonly organization: OrganizationService;
|
||||
|
||||
constructor(
|
||||
private readonly db: DatabaseSync,
|
||||
@@ -91,6 +105,7 @@ export class TaskExecutionService {
|
||||
private readonly skills: SkillService = new SkillService(db, ""),
|
||||
) {
|
||||
this.audit = new AuditService(db);
|
||||
this.organization = new OrganizationService(db);
|
||||
}
|
||||
|
||||
async runTask(input: RunTaskInput): Promise<string> {
|
||||
@@ -101,35 +116,79 @@ export class TaskExecutionService {
|
||||
const workspace = this.workspace(task.workspace_id);
|
||||
const isolation = this.createIsolationContext(input.orgId, task, agent, provider, workspace);
|
||||
const runId = this.createRun(input.orgId, task.id, agent.id, provider.id, isolation);
|
||||
return this.executeAgentRun({ orgId: input.orgId, runId, task, agent, provider, workspace, isolation });
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes an existing `running` task_run (claimed by the autonomous queue
|
||||
* worker). System-initiated, so it does not re-gate on the manual execution
|
||||
* approval — the operator configured the company to run autonomously.
|
||||
*/
|
||||
async executeRun(orgId: string, runId: string): Promise<string> {
|
||||
const run = this.db.prepare(`
|
||||
select task_id, agent_id, provider_id, execution_context_id, service_identity_id, workspace_mount_json, secret_scope_json
|
||||
from task_runs where id = ? and org_id = ?
|
||||
`).get(runId, orgId) as RunRow | undefined;
|
||||
if (!run) throw new Error("Task run was not found.");
|
||||
const task = this.requireTask(orgId, run.task_id);
|
||||
const agent = this.requireAssignedAgent(orgId, task);
|
||||
const provider = this.requireAgentProvider(orgId, agent);
|
||||
const workspace = this.workspace(task.workspace_id);
|
||||
const isolation: TaskRunIsolation = {
|
||||
executionContextId: run.execution_context_id,
|
||||
serviceIdentityId: run.service_identity_id,
|
||||
workspaceMountJson: run.workspace_mount_json,
|
||||
secretScopeJson: run.secret_scope_json,
|
||||
};
|
||||
return this.executeAgentRun({ orgId, runId, task, agent, provider, workspace, isolation });
|
||||
}
|
||||
|
||||
private async executeAgentRun(args: {
|
||||
orgId: string;
|
||||
runId: string;
|
||||
task: TaskExecutionTaskRow;
|
||||
agent: TaskExecutionAgentRow;
|
||||
provider: TaskExecutionProviderRow;
|
||||
workspace: WorkspaceRow | null;
|
||||
isolation: TaskRunIsolation;
|
||||
}): Promise<string> {
|
||||
const { orgId, runId, task, agent, provider, workspace, isolation } = args;
|
||||
let providerRequestId: string | null = null;
|
||||
let skillRuntimeSnapshot: SkillRuntimeSnapshotRow | null = null;
|
||||
|
||||
try {
|
||||
skillRuntimeSnapshot = this.resolveSkillRuntimeSnapshot(input.orgId, agent.id, runId);
|
||||
const requestId = await this.providers.createResponse({
|
||||
orgId: input.orgId,
|
||||
providerId: provider.id,
|
||||
workspaceId: task.workspace_id,
|
||||
prompt: buildTaskPrompt({
|
||||
task,
|
||||
agent,
|
||||
provider,
|
||||
project: this.project(task.project_id),
|
||||
goal: this.goal(task.goal_id),
|
||||
workspace,
|
||||
isolation,
|
||||
skillRuntimeSnapshot,
|
||||
}),
|
||||
instructions: buildAgentInstructions(agent),
|
||||
executionMode: PROVIDER_EXECUTION_MODE.FOREGROUND,
|
||||
onRequestCreated: (requestId) => {
|
||||
providerRequestId = requestId;
|
||||
this.linkProviderRequest(input.orgId, runId, requestId);
|
||||
skillRuntimeSnapshot = this.resolveSkillRuntimeSnapshot(orgId, agent.id, runId);
|
||||
const toolContext: AgentToolContext = {
|
||||
orgId,
|
||||
agent: { id: agent.id, modelProviderId: agent.model_provider_id, modelId: agent.model_id },
|
||||
taskProjectId: task.project_id,
|
||||
taskGoalId: task.goal_id,
|
||||
workspace: workspace ? { id: workspace.id, localPath: workspace.local_path } : null,
|
||||
};
|
||||
const result = await runAgentLoop(
|
||||
{ providers: this.providers, tools: { db: this.db, organization: this.organization } },
|
||||
{
|
||||
orgId,
|
||||
providerId: provider.id,
|
||||
workspaceId: task.workspace_id,
|
||||
instructions: buildAgentInstructions(agent),
|
||||
initialUserTurn: buildTaskPrompt({
|
||||
task,
|
||||
agent,
|
||||
provider,
|
||||
project: this.project(task.project_id),
|
||||
goal: this.goal(task.goal_id),
|
||||
workspace,
|
||||
isolation,
|
||||
skillRuntimeSnapshot,
|
||||
hasReports: this.agentHasReports(orgId, agent.id),
|
||||
}),
|
||||
toolContext,
|
||||
},
|
||||
});
|
||||
this.completeRun(input.orgId, runId, requestId);
|
||||
);
|
||||
providerRequestId = result.lastRequestId;
|
||||
this.completeRun(orgId, runId, result.lastRequestId);
|
||||
this.audit.record({
|
||||
orgId: input.orgId,
|
||||
orgId,
|
||||
entityType: "task_run",
|
||||
entityId: runId,
|
||||
actorType: "system",
|
||||
@@ -139,7 +198,9 @@ export class TaskExecutionService {
|
||||
taskId: task.id,
|
||||
agentId: agent.id,
|
||||
providerId: provider.id,
|
||||
providerRequestId: requestId,
|
||||
providerRequestId: result.lastRequestId,
|
||||
iterations: result.iterations,
|
||||
toolCalls: result.toolCallCount,
|
||||
executionContextId: isolation.executionContextId,
|
||||
serviceIdentityId: isolation.serviceIdentityId,
|
||||
skillRuntimeSnapshotId: skillRuntimeSnapshot?.id ?? null,
|
||||
@@ -148,9 +209,9 @@ export class TaskExecutionService {
|
||||
return runId;
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
this.failRun(input.orgId, runId, providerRequestId, message);
|
||||
this.failRun(orgId, runId, providerRequestId, message);
|
||||
this.audit.record({
|
||||
orgId: input.orgId,
|
||||
orgId,
|
||||
entityType: "task_run",
|
||||
entityId: runId,
|
||||
actorType: "system",
|
||||
@@ -252,9 +313,10 @@ export class TaskExecutionService {
|
||||
return runId;
|
||||
}
|
||||
|
||||
private linkProviderRequest(orgId: string, runId: string, providerRequestId: string): void {
|
||||
this.db.prepare("update task_runs set provider_request_id = ? where id = ? and org_id = ?")
|
||||
.run(providerRequestId, runId, orgId);
|
||||
private agentHasReports(orgId: string, agentId: string): boolean {
|
||||
const row = this.db.prepare("select 1 from agents where org_id = ? and manager_agent_id = ? and lifecycle = 'active' limit 1")
|
||||
.get(orgId, agentId);
|
||||
return Boolean(row);
|
||||
}
|
||||
|
||||
private completeRun(orgId: string, runId: string, providerRequestId: string): void {
|
||||
@@ -345,10 +407,9 @@ export class TaskExecutionService {
|
||||
|
||||
function buildAgentInstructions(agent: TaskExecutionAgentRow): string {
|
||||
return [
|
||||
`You are ${agent.name}, ${agent.title || agent.role}.`,
|
||||
`Department: ${agent.department}.`,
|
||||
`Role level: ${agent.role_level}.`,
|
||||
"Return an auditable execution result with concrete output and next action.",
|
||||
`You are ${agent.name}, ${agent.title || agent.role} (${agent.department}, ${agent.role_level}) operating an autonomous AI company.`,
|
||||
"You act by calling tools: hire_agent staffs your team, create_task delegates work and assigns it by agent name, update_task_status tracks progress, and write_deliverable produces real files in the company workspace.",
|
||||
"Take concrete action with the tools instead of only describing a plan. When your team is staffed, the work is delegated, and the first operating deliverable is written, give a brief closing summary with no further tool calls.",
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
@@ -361,6 +422,7 @@ function buildTaskPrompt(input: {
|
||||
workspace: WorkspaceRow | null;
|
||||
isolation: TaskRunIsolation;
|
||||
skillRuntimeSnapshot: SkillRuntimeSnapshotRow | null;
|
||||
hasReports: boolean;
|
||||
}): string {
|
||||
const lines = [
|
||||
"Execute this AgentCompany task.",
|
||||
@@ -399,6 +461,13 @@ function buildTaskPrompt(input: {
|
||||
`Skill revisions: ${input.skillRuntimeSnapshot.source_revision_ids}`,
|
||||
);
|
||||
}
|
||||
lines.push("", "Deliverable: completed work, decisions made, evidence needed, and next action.");
|
||||
if (!input.hasReports) {
|
||||
lines.push(
|
||||
"",
|
||||
"Your team is currently empty. Hire 3-5 direct reports for the roles this goal needs (hire_agent), create and assign their first tasks (create_task), and write the initial operating plan into the workspace (write_deliverable). Then summarize.",
|
||||
);
|
||||
} else {
|
||||
lines.push("", "Deliver completed work: make decisions, delegate or update tasks, and write real files with write_deliverable where appropriate.");
|
||||
}
|
||||
return lines.join("\n");
|
||||
}
|
||||
Reference in new issue
Block a user