fix(quick-260328-48g): fix heartbeat task duplication with Paperclip-style coalescing

- Remove self-defeating heartbeat task filter that excluded heartbeat tasks from candidates
- Add Paperclip-style task reuse: recycle existing done/in_review heartbeat tasks instead of creating new ones
- Fix status bug: new heartbeat tasks created as "todo" instead of "in_progress" so CAS checkout succeeds
- Wire one-time duplicate cleanup sweep into heartbeat scheduler startup

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
ZacharyZhang-NYandClaude Opus 4.6 committed 2026-03-28 03:17:17 -04:00
1 parent 4b5492ea34
commit 758bc077ff
1 file changed
+48 -24
+48 -24
View File
@@ -357,9 +357,7 @@ export function createOrchestrationService({
const assignedTasks = db.listAgentTasks(agentId, companyId, ["todo", "in_progress", "blocked"]); const assignedTasks = db.listAgentTasks(agentId, companyId, ["todo", "in_progress", "blocked"]);
const isTimer = trigger === "timer"; const isTimer = trigger === "timer";
const candidateTasks = isTimer const candidateTasks = assignedTasks;
? assignedTasks.filter((entry) => !entry.title.startsWith("[Heartbeat]"))
: assignedTasks;
let task = candidateTasks.find((entry) => !entry.activeRunId) ?? null; let task = candidateTasks.find((entry) => !entry.activeRunId) ?? null;
@@ -382,28 +380,41 @@ export function createOrchestrationService({
return null; return null;
} }
const taskTitle = isCeo // Paperclip-style task reuse: coalesce into existing heartbeat task instead of creating duplicates
? `[Heartbeat] ${agent.name} — Strategic planning & task delegation` const reusableTask = db.findReusableHeartbeatTask(agentId, companyId);
: isManager if (reusableTask) {
? `[Heartbeat] ${agent.name} — Review team & delegate work` // Reset to "todo" so checkoutTask CAS succeeds (requires status IN ('todo', 'backlog'))
: `[Heartbeat] ${agent.name} — Check for work`; db.advanceTaskStatus(reusableTask.id, "todo", `Heartbeat task recycled for ${trigger} cycle.`);
const taskDescription = isCeo task = db.getTask(reusableTask.id);
? "Review active company goals. Break each goal into projects and actionable tasks. Assign tasks to department leads and team members. Create tasks using the API. This is your primary responsibility as CEO — ensure every goal has a clear execution plan with assigned owners." logger.info(`[heartbeat] Reusing existing heartbeat task for ${agent.name}: ${task.title}`);
: isManager } else {
? "Review your department's goals and your team's workload. Create tasks for your direct reports if they have capacity. Review any work submitted for your approval. Ensure your team is productive and unblocked." // Only create new heartbeat task if none exists to reuse
: `Automated heartbeat run for ${agent.name}. Review assigned tasks, check goals, and take action as needed.`; const taskTitle = isCeo
? `[Heartbeat] ${agent.name} — Strategic planning & task delegation`
: isManager
? `[Heartbeat] ${agent.name} — Review team & delegate work`
: `[Heartbeat] ${agent.name} — Check for work`;
const taskDescription = isCeo
? "Review active company goals. Break each goal into projects and actionable tasks. Assign tasks to department leads and team members. Create tasks using the API. This is your primary responsibility as CEO — ensure every goal has a clear execution plan with assigned owners."
: isManager
? "Review your department's goals and your team's workload. Create tasks for your direct reports if they have capacity. Review any work submitted for your approval. Ensure your team is productive and unblocked."
: `Automated heartbeat run for ${agent.name}. Review assigned tasks, check goals, and take action as needed.`;
const taskId = db.saveTask({ // CRITICAL FIX: Create as "todo" (not "in_progress") so checkoutTask CAS works.
companyId, // Previously created as "in_progress" which caused checkoutTask to always fail
title: taskTitle, // (it requires status IN ('todo', 'backlog')), silently dropping the run.
description: taskDescription, const taskId = db.saveTask({
assigneeAgentId: agentId, companyId,
workspaceId: agent.workspaceId ?? undefined, title: taskTitle,
status: "in_progress", description: taskDescription,
priority: isCeo ? "high" : "medium", assigneeAgentId: agentId,
}); workspaceId: agent.workspaceId ?? undefined,
task = db.getTask(taskId); status: "todo",
logger.info(`[heartbeat] Created bootstrap task for ${agent.name}: ${taskTitle}`); priority: isCeo ? "high" : "medium",
});
task = db.getTask(taskId);
logger.info(`[heartbeat] Created heartbeat task for ${agent.name}: ${taskTitle}`);
}
} }
if (task.activeRunId || hasActiveRun({ taskId: task.id, companyId })) { if (task.activeRunId || hasActiveRun({ taskId: task.id, companyId })) {
@@ -617,6 +628,19 @@ export function createOrchestrationService({
function startHeartbeatScheduler() { function startHeartbeatScheduler() {
if (heartbeatTimer) clearInterval(heartbeatTimer); if (heartbeatTimer) clearInterval(heartbeatTimer);
// One-time cleanup of existing duplicate heartbeat tasks from before the coalescing fix
try {
const activeCompanies = db.listActiveCompanyIds();
for (const company of activeCompanies) {
const cleaned = db.cleanupDuplicateHeartbeatTasks(company.id);
if (cleaned > 0) {
logger.info(`[heartbeat] Cleaned up ${cleaned} duplicate heartbeat task(s) for company ${company.name}`);
}
}
} catch (err) {
logger.error(`[heartbeat] Heartbeat task cleanup failed: ${err instanceof Error ? err.message : String(err)}`);
}
heartbeatTimer = setInterval(() => { heartbeatTimer = setInterval(() => {
try { try {
if (isQuitting() || !hasWorkerReady()) return; if (isQuitting() || !hasWorkerReady()) return;