diff --git a/CHANGELOG.md b/CHANGELOG.md index ad2a213bc6a..e879a80b554 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) diff --git a/src/agents/pi-embedded-runner/run/trigger-policy.test.ts b/src/agents/pi-embedded-runner/run/trigger-policy.test.ts index fbbac2d7169..9a977a7b512 100644 --- a/src/agents/pi-embedded-runner/run/trigger-policy.test.ts +++ b/src/agents/pi-embedded-runner/run/trigger-policy.test.ts @@ -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); }); }); diff --git a/src/agents/pi-embedded-runner/run/trigger-policy.ts b/src/agents/pi-embedded-runner/run/trigger-policy.ts index 4b7a18bb898..db8a2626a34 100644 --- a/src/agents/pi-embedded-runner/run/trigger-policy.ts +++ b/src/agents/pi-embedded-runner/run/trigger-policy.ts @@ -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, };