mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 08:10:44 +00:00
fix(agents): stop injecting heartbeat system prompt on non-heartbeat runs (#69079)
This commit is contained in:
32
src/agents/pi-embedded-runner/run/trigger-policy.test.ts
Normal file
32
src/agents/pi-embedded-runner/run/trigger-policy.test.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { shouldInjectHeartbeatPromptForTrigger } from "./trigger-policy.js";
|
||||
|
||||
describe("shouldInjectHeartbeatPromptForTrigger", () => {
|
||||
it("injects the heartbeat prompt on heartbeat-triggered runs", () => {
|
||||
expect(shouldInjectHeartbeatPromptForTrigger("heartbeat")).toBe(true);
|
||||
});
|
||||
|
||||
// Regression: the heartbeat system prompt instructs the model to reply
|
||||
// exactly "HEARTBEAT_OK" when nothing is pending. If that prompt leaks into
|
||||
// a user-triggered turn, the model can pattern-match the literal HEARTBEAT_OK
|
||||
// token (which the delivery runtime then suppresses, so the user sees
|
||||
// silence) or hallucinate a "[object Object]" serialization error as it
|
||||
// tries to reconcile the heartbeat instruction with a real user message.
|
||||
// See issue #69079 and its parent #50797.
|
||||
it.each([
|
||||
["user"] as const,
|
||||
["manual"] as const,
|
||||
["cron"] as const,
|
||||
["memory"] as const,
|
||||
["overflow"] as const,
|
||||
])("does not inject the heartbeat prompt on %s-triggered runs", (trigger) => {
|
||||
expect(shouldInjectHeartbeatPromptForTrigger(trigger)).toBe(false);
|
||||
});
|
||||
|
||||
it("does not inject the heartbeat prompt when no trigger is supplied", () => {
|
||||
// Defense-in-depth: if a new call site lands without a trigger, it should
|
||||
// fall through to the safe default rather than spuriously injecting
|
||||
// heartbeat instructions.
|
||||
expect(shouldInjectHeartbeatPromptForTrigger(undefined)).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -4,13 +4,21 @@ type EmbeddedRunTriggerPolicy = {
|
||||
injectHeartbeatPrompt: boolean;
|
||||
};
|
||||
|
||||
// The heartbeat system prompt tells the model to reply exactly "HEARTBEAT_OK"
|
||||
// when nothing needs attention. It is only meaningful on heartbeat-triggered
|
||||
// runs; injecting it on user/manual/memory/overflow runs confuses smaller
|
||||
// models into pattern-matching the HEARTBEAT_OK output on real user messages
|
||||
// (delivery then suppresses the "reply", so the user sees silence) or into
|
||||
// fabricating "[object Object]" serialization errors as they try to reconcile
|
||||
// the heartbeat instruction with a non-heartbeat turn. See issue #69079 and
|
||||
// its parent #50797. Default off; heartbeat trigger explicitly opts in.
|
||||
const DEFAULT_EMBEDDED_RUN_TRIGGER_POLICY: EmbeddedRunTriggerPolicy = {
|
||||
injectHeartbeatPrompt: true,
|
||||
injectHeartbeatPrompt: false,
|
||||
};
|
||||
|
||||
const EMBEDDED_RUN_TRIGGER_POLICY: Partial<Record<EmbeddedRunTrigger, EmbeddedRunTriggerPolicy>> = {
|
||||
cron: {
|
||||
injectHeartbeatPrompt: false,
|
||||
heartbeat: {
|
||||
injectHeartbeatPrompt: true,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user