fix: stop heartbeat prompt leaking into user runs (#69278) (thanks @stainlu)

This commit is contained in:
Ayaan Zaidi
2026-04-25 16:08:29 +05:30
parent 06c058b21d
commit 27aae62d99
3 changed files with 1 additions and 18 deletions

View File

@@ -60,6 +60,7 @@ Docs: https://docs.openclaw.ai
- Gateway/restart continuation: durably hand restart continuations to a session-delivery queue before deleting the restart sentinel, recover queued continuation work after crashy restarts, and fall back to a session-only wake when no channel route survives reboot. (#70780) Thanks @fuller-stack-dev.
- Agents/tool-result pruning: harden the tool-result character estimator and context-pruning loops against malformed `{ type: "text" }` blocks created by void or undefined tool handler results, serializing non-string text payloads for size accounting so they cannot bypass trimming as zero-sized. Fixes #34979. (#51267) Thanks @cgdusek, @alvinttang, and @coffeexcoin.
- Daemon/service-env: add Nix Home Manager profile bin directories to generated gateway service PATHs on macOS and Linux, honoring `NIX_PROFILES` right-to-left precedence and falling back to `~/.nix-profile/bin` when unset. Fixes #44402. (#59935) Thanks @jerome-benoit.
- Agents/heartbeat: stop injecting the heartbeat system prompt into non-heartbeat runs, preventing ordinary user replies from being suppressed as `HEARTBEAT_OK` acknowledgments. Fixes #69079. (#69278) Thanks @stainlu.
## 2026.4.25 (Unreleased)

View File

@@ -6,13 +6,6 @@ describe("shouldInjectHeartbeatPromptForTrigger", () => {
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,
@@ -24,9 +17,6 @@ describe("shouldInjectHeartbeatPromptForTrigger", () => {
});
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);
});
});

View File

@@ -4,14 +4,6 @@ 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: false,
};