From 834ac913ca4d314650e503122d159b859a39b2e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Sun, 19 Apr 2026 21:10:28 -0400 Subject: [PATCH] Chain CEO runs through self-wake during team building MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit triggerSelfWake fired only when the completing agent had at least one actionable task (todo / in_progress). A CEO in team_building mode has zero tasks by definition — each heartbeat hired one or two agents, returned, and then waited for the next 2-minute timer tick. Operators watched bootstrap crawl: in the captured field log, six heartbeats burned 12 minutes before the first hire landed. Teach triggerSelfWake to also chain when the agent is a top-level CEO with active goals, zero active direct reports, and no already-pending hire_agent approvals — the exact state team_building.en.md is telling the model to resolve. The existing circuit breaker (MAX_CONSECUTIVE_WAKES without progress) still applies, so a CEO that keeps returning without filing hires gets paused and its manager wake — the board / human — is raised instead. Once reports activate or all intended hires are filed, the predicate flips to false and runs resume the normal heartbeat cadence. --- .../worker/handlers/run-finished-helpers.ts | 38 ++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/src/main/worker/handlers/run-finished-helpers.ts b/src/main/worker/handlers/run-finished-helpers.ts index 498218b6..37d8efd0 100644 --- a/src/main/worker/handlers/run-finished-helpers.ts +++ b/src/main/worker/handlers/run-finished-helpers.ts @@ -295,7 +295,8 @@ export function triggerSelfWake( try { const actionableTasks = db.listAgentTasks(run.agentId, run.companyId, ["todo", "in_progress"]); - if (actionableTasks.length === 0) return; + const hasWorkToContinue = actionableTasks.length > 0 || hasUnfinishedTeamBuilding(db, run.agentId, run.companyId); + if (!hasWorkToContinue) return; const allowed = cb.tryWake(run.agentId, run.companyId); if (!allowed) { @@ -322,3 +323,38 @@ export function triggerSelfWake( ); } } + +/** + * Returns true when a top-level agent (CEO) still has team-building work + * in flight: the company has an active goal, zero active direct reports, + * and no pending hire approvals. Each wake in that window files at most a + * handful of hire_agent POSTs, so chaining runs via self-wake instead of + * waiting for the 2-minute heartbeat timer takes bootstrap from + * 10-plus minutes down to the LLM's round-trip time. + * + * The circuit breaker on the caller side still applies, so a CEO that + * keeps completing runs without actually creating hires gets paused + * after MAX_CONSECUTIVE_WAKES attempts. + */ +function hasUnfinishedTeamBuilding( + db: HandlerDeps["db"], + agentId: string, + companyId: string, +): boolean { + try { + const agent = db.getAgent(agentId); + if (agent.reportsTo !== null) return false; + const activeReports = db + .getDirectReports(agentId, companyId) + .filter((r) => r.status !== "pending_approval" && r.status !== "terminated"); + if (activeReports.length > 0) return false; + const goalCount = db.countActiveGoalsForCompany(companyId); + if (goalCount === 0) return false; + const pendingHires = db + .listCompanyApprovalsByType(companyId, "hire_agent") + .filter((a) => a.state === "pending"); + return pendingHires.length === 0; + } catch { + return false; + } +}