Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions src/agent/loop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,11 +210,27 @@ export async function runAgentLoop(

// Local worker pool: runs inference-driven agents in-process
// as async tasks. Falls back from Conway sandbox spawning.
// Adapt parent tools for worker use. Workers get the same custom tools
// as the orchestrator so they can perform outreach, publish to GitHub,
// send emails, call external APIs, and perform any action that requires
// the parent's credentials/context.
const workerCustomTools = tools
.filter((t) => !["create_goal", "list_goals", "get_plan", "cancel_goal",
"orchestrator_status", "sleep", "modify_heartbeat",
"review_upstream_changes", "pull_upstream"].includes(t.name))
.map((t) => ({
name: t.name,
description: t.description,
parameters: t.parameters,
execute: async (args: Record<string, unknown>) => t.execute(args, toolContext),
}));

const workerPool = new LocalWorkerPool({
db: db.raw,
inference: workerInference,
conway,
workerId: `pool-${identity.name}`,
customTools: workerCustomTools,
});

orchestrator = new Orchestrator({
Expand Down
32 changes: 30 additions & 2 deletions src/orchestration/local-worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,14 @@ interface LocalWorkerConfig {
conway: ConwayClient;
workerId: string;
maxTurns?: number;
/**
* Additional tools from the parent agent that workers can use.
* These are merged with the built-in worker tools (exec, read_file,
* write_file, task_done) to give workers access to the parent's
* custom capabilities — e.g. GitHub, email, web search, or external
* API integrations like Obol (https://obolagents.com).
*/
customTools?: WorkerTool[];
}

interface WorkerToolResult {
Expand Down Expand Up @@ -115,6 +123,13 @@ export class LocalWorkerPool {
})
.finally(() => {
this.activeWorkers.delete(workerId);
// Mark child as terminated in DB so the orchestrator doesn't
// re-assign new tasks to this dead worker address.
try {
this.config.db.prepare(
"UPDATE children SET status = 'terminated' WHERE address = ?",
).run(address);
} catch { /* ignore DB errors during cleanup */ }
});

this.activeWorkers.set(workerId, { promise: workerPromise, taskId: task.id, abortController });
Expand Down Expand Up @@ -281,6 +296,11 @@ export class LocalWorkerPool {

private buildWorkerSystemPrompt(task: TaskNode): string {
const role = task.agentRole ?? "generalist";
const customToolNames = (this.config.customTools ?? []).map(t => t.name);
const customToolSection = customToolNames.length > 0
? `\n- You also have custom tools from the parent agent: ${customToolNames.join(", ")}. Use them when the task requires external actions (API calls, outreach, publishing).`
: "";

return `You are a worker agent with the role: ${role}.

You have been assigned a specific task by the parent orchestrator. Your job is to
Expand All @@ -290,7 +310,7 @@ RULES:
- Focus ONLY on the assigned task. Do not deviate.
- Use exec to run shell commands (install packages, run scripts, etc.)
- Use write_file to create or modify files.
- Use read_file to inspect existing files.
- Use read_file to inspect existing files.${customToolSection}
- When done, provide a clear summary of what you accomplished as your final message.
- If you cannot complete the task, explain why in your final message.
- Do NOT call tools after you are done. Just give your final text response.
Expand All @@ -317,7 +337,7 @@ RULES:
}

private buildWorkerTools(): WorkerTool[] {
return [
const builtinTools: WorkerTool[] = [
{
name: "exec",
description: "Execute a shell command and return stdout/stderr. Use for installing packages, running scripts, building code, etc.",
Expand Down Expand Up @@ -418,5 +438,13 @@ RULES:
},
},
];

// Merge parent-provided custom tools so workers can use the same
// integrations as the orchestrator (GitHub, email, web APIs, etc.)
const custom = this.config.customTools ?? [];
if (custom.length > 0) {
logger.info(`Worker has ${custom.length} custom tool(s) from parent: ${custom.map(t => t.name).join(", ")}`);
}
return [...builtinTools, ...custom];
}
}
4 changes: 4 additions & 0 deletions src/orchestration/orchestrator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,10 @@ export class Orchestrator {
this.params.db.prepare(
"UPDATE task_graph SET status = 'pending', assigned_to = NULL, started_at = NULL WHERE id = ?",
).run(task.id);
// Also terminate the dead child to prevent re-assignment
this.params.db.prepare(
"UPDATE children SET status = 'terminated' WHERE address = ?",
).run(task.assignedTo);
}
}
}
Expand Down