feat(FN-4244): add no-task heartbeat procedure override

Merged FN-4244: adds an override path for the no-task heartbeat procedure in the engine, allowing agents to execute a custom procedure when they have no active task. The fix commit corrects the initial implementation, with test coverage for procedure source selection and updated documentation in `do

Fusion-Task-Id: FN-4244

Fusion-Task-Lineage: 6bf1bfe4-de2c-4467-93dc-d90231a53eb2
This commit is contained in:
Fusion
2026-05-12 21:34:41 -07:00
committed by gsxdsm
parent 2d8b4d6bbb
commit 3f5a2fcbf3
7 changed files with 88 additions and 7 deletions

View File

@@ -855,6 +855,57 @@ describe("executeHeartbeat", () => {
expect(executionPrompt).toContain(HEARTBEAT_NO_TASK_PROCEDURE);
});
it("no-task run overrides a seeded task-scoped heartbeatProcedurePath in the assembled prompt", async () => {
const tmpRoot = mkdtempSync(join(tmpdir(), "fn-hb-no-task-procedure-"));
try {
writeFileSync(join(tmpRoot, "HEARTBEAT.md"), HEARTBEAT_PROCEDURE, "utf-8");
const store = createStoreWithAgentForExec({
taskId: undefined,
soul: "I am a coordinator",
heartbeatProcedurePath: "HEARTBEAT.md",
});
const mockSession = createMockAgentSession();
mockedCreateFnAgent.mockResolvedValue({ session: mockSession as any });
const monitor = new HeartbeatMonitor({ store, taskStore: mockTaskStore, rootDir: tmpRoot });
const result = await monitor.executeHeartbeat({ agentId: "agent-001", source: "timer" });
expect(result.status).toBe("completed");
const executionPrompt = mockSession.prompt.mock.calls.at(-1)?.[0];
expect(executionPrompt).toBeDefined();
expect(executionPrompt).not.toContain("fn_task_log");
expect(executionPrompt).not.toContain("fn_task_document_write");
expect(executionPrompt).not.toContain("do not re-read PROMPT.md to advance it");
expect(executionPrompt).toContain("Implementation-scope discovery");
const savedRun = await store.getRunDetail("agent-001", result.id);
expect(savedRun?.heartbeatProcedureSource).toBe("default-no-task-override");
} finally {
rmSync(tmpRoot, { recursive: true, force: true });
}
});
it("no-task run without a custom heartbeatProcedurePath still uses the ambient procedure", async () => {
const store = createStoreWithAgentForExec({ taskId: undefined, soul: "I am a coordinator" });
const mockSession = createMockAgentSession();
mockedCreateFnAgent.mockResolvedValue({ session: mockSession as any });
const monitor = new HeartbeatMonitor({ store, taskStore: mockTaskStore, rootDir: "/tmp" });
const result = await monitor.executeHeartbeat({ agentId: "agent-001", source: "timer" });
expect(result.status).toBe("completed");
const executionPrompt = mockSession.prompt.mock.calls.at(-1)?.[0];
expect(executionPrompt).toBeDefined();
expect(executionPrompt).not.toContain("fn_task_log");
expect(executionPrompt).not.toContain("fn_task_document_write");
expect(executionPrompt).not.toContain("do not re-read PROMPT.md to advance it");
expect(executionPrompt).toContain("Implementation-scope discovery");
const savedRun = await store.getRunDetail("agent-001", result.id);
expect(savedRun?.heartbeatProcedureSource).toBe("default");
});
it("task-scoped run receives HEARTBEAT_SYSTEM_PROMPT as system prompt", async () => {
const store = createStoreWithAgentForExec({ taskId: "FN-001" });
const mockSession = createMockAgentSession();
@@ -1305,6 +1356,9 @@ describe("executeHeartbeat", () => {
expect(executionPrompt).not.toContain(HEARTBEAT_PROCEDURE);
// Wake Delta still rendered.
expect(executionPrompt).toContain("## Wake Delta");
const savedRun = await store.getRunDetail("agent-001", result.id);
expect(savedRun?.heartbeatProcedureSource).toBe("custom");
} finally {
rmSync(tmpRoot, { recursive: true, force: true });
}

View File

@@ -2163,8 +2163,19 @@ export class HeartbeatMonitor {
// existing instructionsPath/instructionsText reload contract) so an
// operator can iterate on procedure text without restarting agents.
const customProcedure = await resolveAgentHeartbeatProcedure(agent, rootDir);
const heartbeatProcedureText = customProcedure
?? (isNoTaskRun ? HEARTBEAT_NO_TASK_PROCEDURE : HEARTBEAT_PROCEDURE);
const customProcedureConfigured = Boolean(customProcedure);
const shouldOverrideCustomProcedureForNoTaskRun = isNoTaskRun && customProcedureConfigured;
if (shouldOverrideCustomProcedureForNoTaskRun) {
heartbeatLog.log(
`Agent ${agentId} no-task heartbeat bypassed configured heartbeatProcedurePath and used HEARTBEAT_NO_TASK_PROCEDURE to keep prompt guidance aligned with ambient tools`,
);
}
const heartbeatProcedureText = shouldOverrideCustomProcedureForNoTaskRun
? HEARTBEAT_NO_TASK_PROCEDURE
: (customProcedure ?? (isNoTaskRun ? HEARTBEAT_NO_TASK_PROCEDURE : HEARTBEAT_PROCEDURE));
const heartbeatProcedureSource = shouldOverrideCustomProcedureForNoTaskRun
? "default-no-task-override"
: (customProcedure ? "custom" : "default");
const reportsHealthSection = await this.buildReportsHealthSection(agent.id, this.store);
if (isNoTaskRun) {
@@ -2360,7 +2371,7 @@ export class HeartbeatMonitor {
...run,
systemPrompt: truncatePrompt(systemPromptFinal, 100_000),
executionPrompt: truncatePrompt(executionPrompt, 100_000),
heartbeatProcedureSource: customProcedure ? "custom" : "default",
heartbeatProcedureSource,
};
await this.store.saveRun(runWithPrompts);
// Update local run reference so completeRun merges correctly