fix(FN-2077): use no-task prompt for ambient heartbeat runs
- Add HEARTBEAT_NO_TASK_SYSTEM_PROMPT for no-task heartbeat sessions with only non-task-scoped tooling guidance - Switch heartbeat session setup to choose the no-task base prompt when executing without task context - Keep agent instruction and memory enrichment on top of the selected base prompt for both task and no-task runs - Extend heartbeat tests to verify no-task prompts exclude task-scoped tool references and are passed into createKbAgent
This commit is contained in:
@@ -1,5 +1,13 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
import { HeartbeatMonitor, HeartbeatTriggerScheduler, isBlockedStateDuplicate, type AgentSession, type HeartbeatExecutionOptions, HEARTBEAT_SYSTEM_PROMPT } from "./agent-heartbeat.js";
|
||||
import {
|
||||
HeartbeatMonitor,
|
||||
HeartbeatTriggerScheduler,
|
||||
isBlockedStateDuplicate,
|
||||
type AgentSession,
|
||||
type HeartbeatExecutionOptions,
|
||||
HEARTBEAT_SYSTEM_PROMPT,
|
||||
HEARTBEAT_NO_TASK_SYSTEM_PROMPT,
|
||||
} from "./agent-heartbeat.js";
|
||||
import { AgentLogger } from "./agent-logger.js";
|
||||
import type { AgentStore, AgentHeartbeatRun, TaskStore, TaskDetail, Agent, MessageStore, Message, AgentBudgetStatus } from "@fusion/core";
|
||||
|
||||
@@ -1363,6 +1371,14 @@ describe("HeartbeatMonitor", () => {
|
||||
await monitor.executeHeartbeat({ agentId: "agent-001", source: "timer" });
|
||||
|
||||
expect(mockedCreateKbAgent).toHaveBeenCalledOnce();
|
||||
const callArgs = mockedCreateKbAgent.mock.calls[0]![0]!;
|
||||
const systemPrompt = callArgs.systemPrompt;
|
||||
expect(systemPrompt).not.toContain("task_log");
|
||||
expect(systemPrompt).not.toContain("task_document_write");
|
||||
expect(systemPrompt).not.toContain("task_document_read");
|
||||
expect(systemPrompt).toContain("task_create");
|
||||
expect(systemPrompt).toContain("heartbeat_done");
|
||||
|
||||
// The execution prompt is passed to session.prompt by promptWithFallback mock
|
||||
const promptCalls = mockSession.prompt.mock.calls;
|
||||
expect(promptCalls.length).toBeGreaterThan(0);
|
||||
@@ -2184,6 +2200,13 @@ describe("HeartbeatMonitor", () => {
|
||||
});
|
||||
|
||||
describe("execution", () => {
|
||||
it("no-task heartbeat system prompt does not reference 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");
|
||||
});
|
||||
|
||||
it("creates session with enriched system prompt and expected tools", async () => {
|
||||
const store = createStoreWithAgentForExec({
|
||||
soul: "Act like a practical teammate who prioritizes clarity.",
|
||||
|
||||
@@ -162,6 +162,44 @@ When sending messages:
|
||||
- Include relevant context (task IDs, file paths) in metadata when applicable.
|
||||
- Use agent-to-agent for inter-agent communication.`;
|
||||
|
||||
/**
|
||||
* 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_NO_TASK_SYSTEM_PROMPT = `You are a heartbeat agent running in a short execution window.
|
||||
|
||||
Your job:
|
||||
1. Do ONE useful action: analyze, review, create follow-up tasks, or communicate findings.
|
||||
2. Use task_create to spawn follow-up work as needed.
|
||||
3. Use send_message and read_messages to process inter-agent communication.
|
||||
4. Use memory_search and memory_append to leverage and persist useful memory context.
|
||||
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, memory_search, memory_append, send_message, read_messages, and heartbeat_done tools.
|
||||
|
||||
## Memory Boundaries
|
||||
|
||||
You may receive an Agent Memory section and a Project Memory section.
|
||||
- Agent Memory is specific to you, including imported and user-created agents such as CEO-style coordinator agents. It has its own long-term memory, daily notes, dreams, and qmd-backed retrieval under .fusion/agent-memory/{agentId}/.
|
||||
- Project Memory is the workspace memory system under .fusion/memory/ with long-term memory, daily notes, dreams, and qmd-backed retrieval.
|
||||
- Keep these separate: do not copy personal agent operating notes into Project Memory unless they are genuinely useful to every future agent in this workspace.
|
||||
|
||||
## Processing Messages
|
||||
|
||||
When you are woken by an incoming message (source includes "wake-on-message"), you should:
|
||||
1. Use read_messages to check your inbox for unread messages.
|
||||
2. Review each message and determine the appropriate action:
|
||||
- If the message requires a response, use send_message to reply.
|
||||
- If the message is informational, acknowledge it with a brief response via send_message.
|
||||
- If the message requests work, create a follow-up task with task_create or handle it directly.
|
||||
3. After processing messages, continue with your normal heartbeat duties.
|
||||
|
||||
When sending messages:
|
||||
- Be concise and clear about what you need or what you've done.
|
||||
- Include relevant context (task IDs, file paths) in metadata when applicable.
|
||||
- Use agent-to-agent for inter-agent communication.`;
|
||||
|
||||
/** Parameter schema for the heartbeat_done tool */
|
||||
const heartbeatDoneParams = Type.Object({
|
||||
summary: Type.Optional(Type.String({ description: "Summary of what was accomplished this heartbeat" })),
|
||||
@@ -1069,14 +1107,17 @@ export class HeartbeatMonitor {
|
||||
// Build skill selection context for heartbeat session (uses waking agent's skills, no role fallback)
|
||||
const skillContext = buildSessionSkillContextSync(agent, "heartbeat", rootDir);
|
||||
|
||||
let systemPrompt = HEARTBEAT_SYSTEM_PROMPT;
|
||||
const baseHeartbeatSystemPrompt = isNoTaskRun
|
||||
? HEARTBEAT_NO_TASK_SYSTEM_PROMPT
|
||||
: HEARTBEAT_SYSTEM_PROMPT;
|
||||
let systemPrompt = baseHeartbeatSystemPrompt;
|
||||
try {
|
||||
const agentInstructions = await resolveAgentInstructionsWithRatings(agent, rootDir, this.store);
|
||||
const memoryInstructions = memorySettings?.memoryEnabled === false
|
||||
? ""
|
||||
: buildExecutionMemoryInstructions(rootDir, memorySettings);
|
||||
systemPrompt = buildSystemPromptWithInstructions(
|
||||
HEARTBEAT_SYSTEM_PROMPT,
|
||||
baseHeartbeatSystemPrompt,
|
||||
[agentInstructions, memoryInstructions].filter((part) => part.trim()).join("\n\n"),
|
||||
);
|
||||
} catch (instructionError) {
|
||||
|
||||
Reference in New Issue
Block a user