fix(FN-2111): select no-task heartbeat prompt by run type

- Promote HEARTBEAT_NO_TASK_SYSTEM_PROMPT as the primary no-task heartbeat constant while keeping a backward-compatible alias.
- Refine no-task heartbeat guidance for lightweight ambient coordination and explicit tool usage.
- Select the no-task prompt for heartbeat runs without task context before appending instruction/rating overlays.
- Update heartbeat regression tests to assert the renamed no-task prompt constant and prompt/tool expectations.
This commit is contained in:
Fusion
2026-04-19 04:13:22 -07:00
committed by gsxdsm
parent 95169722df
commit 4c8acea995
2 changed files with 27 additions and 28 deletions

View File

@@ -6,7 +6,7 @@ import {
type AgentSession,
type HeartbeatExecutionOptions,
HEARTBEAT_SYSTEM_PROMPT,
HEARTBEAT_SYSTEM_PROMPT_NO_TASK,
HEARTBEAT_NO_TASK_SYSTEM_PROMPT,
} from "./agent-heartbeat.js";
import { AgentLogger } from "./agent-logger.js";
import * as agentTools from "./agent-tools.js";
@@ -1466,7 +1466,7 @@ describe("HeartbeatMonitor", () => {
expect(toolNames).not.toContain("task_document_read");
});
it("no-task run receives HEARTBEAT_SYSTEM_PROMPT_NO_TASK as system prompt", async () => {
it("no-task run receives HEARTBEAT_NO_TASK_SYSTEM_PROMPT as system prompt", async () => {
const store = createStoreWithAgentForExec({ taskId: undefined, soul: "I am a coordinator" });
const mockSession = createMockAgentSession();
mockedCreateKbAgent.mockResolvedValue({ session: mockSession as any });
@@ -1479,7 +1479,7 @@ describe("HeartbeatMonitor", () => {
const callArgs = mockedCreateKbAgent.mock.calls[0]![0]!;
const systemPrompt = callArgs.systemPrompt;
expect(systemPrompt).toContain(HEARTBEAT_SYSTEM_PROMPT_NO_TASK);
expect(systemPrompt).toContain(HEARTBEAT_NO_TASK_SYSTEM_PROMPT);
expect(systemPrompt).not.toContain("task_log");
expect(systemPrompt).not.toContain("task_document_write");
expect(systemPrompt).not.toContain("task_document_read");
@@ -1505,7 +1505,7 @@ describe("HeartbeatMonitor", () => {
expect(mockedCreateKbAgent).toHaveBeenCalledOnce();
const callArgs = mockedCreateKbAgent.mock.calls[0]![0]!;
const systemPrompt = callArgs.systemPrompt;
expect(systemPrompt).toContain(HEARTBEAT_SYSTEM_PROMPT_NO_TASK);
expect(systemPrompt).toContain(HEARTBEAT_NO_TASK_SYSTEM_PROMPT);
expect(systemPrompt).not.toContain("task_log");
expect(systemPrompt).not.toContain("task_document_write");
expect(systemPrompt).not.toContain("task_document_read");
@@ -2354,19 +2354,19 @@ describe("HeartbeatMonitor", () => {
});
describe("execution", () => {
it("HEARTBEAT_SYSTEM_PROMPT_NO_TASK does not mention task-scoped tools", () => {
expect(HEARTBEAT_SYSTEM_PROMPT_NO_TASK).not.toContain("task_log");
expect(HEARTBEAT_SYSTEM_PROMPT_NO_TASK).not.toContain("task_document_write");
expect(HEARTBEAT_SYSTEM_PROMPT_NO_TASK).not.toContain("task_document_read");
expect(HEARTBEAT_SYSTEM_PROMPT_NO_TASK).not.toContain("task_document");
expect(HEARTBEAT_SYSTEM_PROMPT_NO_TASK).toContain("task_create");
expect(HEARTBEAT_SYSTEM_PROMPT_NO_TASK).toContain("list_agents");
expect(HEARTBEAT_SYSTEM_PROMPT_NO_TASK).toContain("delegate_task");
expect(HEARTBEAT_SYSTEM_PROMPT_NO_TASK).toContain("read_messages");
expect(HEARTBEAT_SYSTEM_PROMPT_NO_TASK).toContain("send_message");
expect(HEARTBEAT_SYSTEM_PROMPT_NO_TASK).toContain("memory_search");
expect(HEARTBEAT_SYSTEM_PROMPT_NO_TASK).toContain("memory_append");
expect(HEARTBEAT_SYSTEM_PROMPT_NO_TASK).toContain("heartbeat_done");
it("HEARTBEAT_NO_TASK_SYSTEM_PROMPT does not mention task-scoped tools", () => {
expect(HEARTBEAT_NO_TASK_SYSTEM_PROMPT).not.toContain("task_log");
expect(HEARTBEAT_NO_TASK_SYSTEM_PROMPT).not.toContain("task_document_write");
expect(HEARTBEAT_NO_TASK_SYSTEM_PROMPT).not.toContain("task_document_read");
expect(HEARTBEAT_NO_TASK_SYSTEM_PROMPT).not.toContain("task_document");
expect(HEARTBEAT_NO_TASK_SYSTEM_PROMPT).toContain("task_create");
expect(HEARTBEAT_NO_TASK_SYSTEM_PROMPT).toContain("list_agents");
expect(HEARTBEAT_NO_TASK_SYSTEM_PROMPT).toContain("delegate_task");
expect(HEARTBEAT_NO_TASK_SYSTEM_PROMPT).toContain("read_messages");
expect(HEARTBEAT_NO_TASK_SYSTEM_PROMPT).toContain("send_message");
expect(HEARTBEAT_NO_TASK_SYSTEM_PROMPT).toContain("memory_search");
expect(HEARTBEAT_NO_TASK_SYSTEM_PROMPT).toContain("memory_append");
expect(HEARTBEAT_NO_TASK_SYSTEM_PROMPT).toContain("heartbeat_done");
});
it("HEARTBEAT_SYSTEM_PROMPT mentions task_log and task_document_write", () => {

View File

@@ -166,18 +166,17 @@ When sending messages:
* System prompt for no-task heartbeat agent sessions.
* Instructs the agent to perform ambient work only with tools that do not require task context.
*/
export const HEARTBEAT_SYSTEM_PROMPT_NO_TASK = `You are a heartbeat agent running in a short execution window.
export const HEARTBEAT_NO_TASK_SYSTEM_PROMPT = `You are a heartbeat agent running in a short execution window.
Your job:
1. Review your context — check messages, memory, and project state.
2. Do ONE useful action: analyze, create follow-up tasks, or update memory.
2. Do ONE useful action: analyze, create follow-up tasks, delegate work, or update memory.
3. Use task_create to spawn follow-up work.
4. Use list_agents and delegate_task to assign work to other agents.
4. Use list_agents and delegate_task to coordinate with other agents.
5. Call heartbeat_done when finished with an optional summary of what was accomplished.
Keep work lightweight — this is a single-pass check, not a full implementation run.
You have readonly file access plus task_create, list_agents, delegate_task, messaging, memory, and heartbeat_done tools.
Use read_messages and send_message for inbox processing, and memory_search, memory_get, and memory_append for memory workflows.
Keep work lightweight — this is a single-pass ambient check, not a full implementation run.
You have readonly file access plus task_create, list_agents, delegate_task, send_message, read_messages, memory_search, memory_append, and heartbeat_done.
## Memory Boundaries
@@ -201,8 +200,8 @@ When sending messages:
- Include relevant context (task IDs, file paths) in metadata when applicable.
- Use agent-to-agent for inter-agent communication.`;
// Backward-compatible alias; prefer HEARTBEAT_SYSTEM_PROMPT_NO_TASK.
export const HEARTBEAT_NO_TASK_SYSTEM_PROMPT = HEARTBEAT_SYSTEM_PROMPT_NO_TASK;
// Backward-compatible alias; prefer HEARTBEAT_NO_TASK_SYSTEM_PROMPT.
export const HEARTBEAT_SYSTEM_PROMPT_NO_TASK = HEARTBEAT_NO_TASK_SYSTEM_PROMPT;
/** Parameter schema for the heartbeat_done tool */
const heartbeatDoneParams = Type.Object({
@@ -1111,10 +1110,10 @@ export class HeartbeatMonitor {
// Build skill selection context for heartbeat session (uses waking agent's skills, no role fallback)
const skillContext = buildSessionSkillContextSync(agent, "heartbeat", rootDir);
const baseHeartbeatSystemPrompt = isNoTaskRun
? HEARTBEAT_SYSTEM_PROMPT_NO_TASK
let systemPrompt = isNoTaskRun
? HEARTBEAT_NO_TASK_SYSTEM_PROMPT
: HEARTBEAT_SYSTEM_PROMPT;
let systemPrompt = baseHeartbeatSystemPrompt;
const baseHeartbeatSystemPrompt = systemPrompt;
try {
const agentInstructions = await resolveAgentInstructionsWithRatings(agent, rootDir, this.store);
const memoryInstructions = memorySettings?.memoryEnabled === false