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:
@@ -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", () => {
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user