Skip to content

Commit 877eb1c

Browse files
authored
fix(heartbeat): align response tool prompts (#76458)
* fix(heartbeat): align response tool prompts * docs(changelog): credit heartbeat prompt fix
1 parent 103b6d5 commit 877eb1c

14 files changed

Lines changed: 88 additions & 28 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ Docs: https://docs.openclaw.ai
9595
- Gateway: preserve stack diagnostics when `chat.send` or agent attachment parsing/staging fails, improving image-send failure triage. Refs #63432. (#75135) Thanks @keen0206.
9696
- Agents/idle-timeout: add a cost-runaway breaker to the outer embedded-run retry loop that halts further attempts after 5 consecutive idle timeouts without completed model progress, so a wedged provider can no longer fan paid model calls out across the same run; completed text or tool-call progress resets the breaker, but partial tool-argument token dribbles do not. Fixes #76293. Thanks @ThePuma312.
9797
- Heartbeats/Codex: stop sending the legacy `HEARTBEAT_OK` prompt instruction when heartbeat turns have the structured `heartbeat_respond` tool, while keeping the text sentinel for legacy automatic heartbeat replies. Thanks @pashpashpash.
98+
- Heartbeats/Codex: keep structured heartbeat prompts aligned with actual `heartbeat_respond` tool availability and keep tool-disabled commitment check-ins on the legacy ack path. Thanks @pashpashpash and @vincentkoc.
9899
- Agent runtimes: fail explicit plugin runtime selections honestly when the requested harness is unavailable instead of silently falling back to the embedded PI runtime. Thanks @pashpashpash.
99100
- Maintainer workflow: push prepared PR heads through GitHub's verified commit API by default and require an explicit override before git-protocol pushes can publish unsigned commits. Thanks @BunsDev.
100101
- Feishu: resolve setup/status probes through the selected/default account so multi-account configs with account-scoped app credentials show as configured and probeable. Fixes #72930. Thanks @brokemac79.

extensions/qa-lab/src/scenario-catalog.test.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@ describe("qa scenario catalog", () => {
5353
const codexLeakConfig = readQaScenarioExecutionConfig("codex-harness-no-meta-leak") as
5454
| {
5555
harnessRuntime?: string;
56-
harnessFallback?: string;
5756
expectedReply?: string;
5857
forbiddenReplySubstrings?: string[];
5958
}
@@ -73,7 +72,6 @@ describe("qa scenario catalog", () => {
7372
);
7473
expect(codexLeak.title).toBe("Codex harness no meta leak");
7574
expect(codexLeakConfig?.harnessRuntime).toBe("codex");
76-
expect(codexLeakConfig?.harnessFallback).toBe("none");
7775
expect(JSON.stringify(codexLeak.execution.flow)).toContain("agentRuntime");
7876
expect(JSON.stringify(codexLeak.execution.flow)).not.toContain("embeddedHarness");
7977
expect(codexLeakConfig?.expectedReply).toBe("QA_LEAK_OK");

qa/scenarios/models/codex-harness-no-meta-leak.md

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ coverage:
1111
- runtime.no-meta-leak
1212
objective: Verify the Codex app-server harness keeps coordination/meta chatter out of the visible reply.
1313
successCriteria:
14-
- The scenario forces the Codex embedded harness and disables PI fallback.
14+
- The scenario forces the Codex embedded harness.
1515
- The final visible reply includes the requested confirmation token.
1616
- The visible reply does not include internal coordination or progress chatter.
1717
docsRefs:
@@ -29,7 +29,6 @@ execution:
2929
requiredProvider: codex
3030
requiredModel: gpt-5.5
3131
harnessRuntime: codex
32-
harnessFallback: none
3332
expectedReply: QA_LEAK_OK
3433
prompt: |-
3534
Think through your answer privately, but do not expose any internal planning, thread-context checks, or progress narration.
@@ -76,8 +75,6 @@ steps:
7675
agentRuntime:
7776
id:
7877
expr: config.harnessRuntime
79-
fallback:
80-
expr: config.harnessFallback
8178
- call: waitForGatewayHealthy
8279
args:
8380
- ref: env
@@ -94,11 +91,7 @@ steps:
9491
expr: "snapshot.config.agents?.defaults?.agentRuntime?.id === config.harnessRuntime"
9592
message:
9693
expr: "`expected agentRuntime.id=${config.harnessRuntime}, got ${JSON.stringify(snapshot.config.agents?.defaults?.agentRuntime)}`"
97-
- assert:
98-
expr: "snapshot.config.agents?.defaults?.agentRuntime?.fallback === config.harnessFallback"
99-
message:
100-
expr: "`expected agentRuntime.fallback=${config.harnessFallback}, got ${JSON.stringify(snapshot.config.agents?.defaults?.agentRuntime)}`"
101-
detailsExpr: "env.providerMode === 'live-frontier' ? `provider=${selected?.provider} model=${selected?.model} runtime=${snapshot.config.agents?.defaults?.agentRuntime?.id} fallback=${snapshot.config.agents?.defaults?.agentRuntime?.fallback}` : `mock mode: parsed ${scenario.id}`"
94+
detailsExpr: "env.providerMode === 'live-frontier' ? `provider=${selected?.provider} model=${selected?.model} runtime=${snapshot.config.agents?.defaults?.agentRuntime?.id}` : `mock mode: parsed ${scenario.id}`"
10295
- name: keeps codex coordination chatter out of the visible reply
10396
actions:
10497
- if:

qa/scenarios/workspace/medium-game-plan-codex-harness.md

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ coverage:
1212
objective: Verify the Codex app-server harness can plan and build a medium-complex self-contained browser game.
1313
successCriteria:
1414
- A live-frontier run fails fast unless the selected primary model is openai/gpt-5.5 with the Codex harness forced.
15-
- The scenario forces the Codex embedded harness and disables PI fallback.
15+
- The scenario forces the Codex embedded harness.
1616
- The prompt explicitly asks the agent to enter plan mode before editing.
1717
- The agent writes a self-contained HTML game with a canvas loop, controls, scoring, waves, pause, and restart.
1818
docsRefs:
@@ -30,7 +30,6 @@ execution:
3030
requiredProvider: codex
3131
requiredModel: gpt-5.5
3232
harnessRuntime: codex
33-
harnessFallback: none
3433
artifactFile: star-garden-defenders-codex.html
3534
gameTitle: Star Garden Defenders
3635
minBytes: 5000
@@ -81,8 +80,6 @@ steps:
8180
agentRuntime:
8281
id:
8382
expr: config.harnessRuntime
84-
fallback:
85-
expr: config.harnessFallback
8683
- call: waitForGatewayHealthy
8784
args:
8885
- ref: env
@@ -99,11 +96,7 @@ steps:
9996
expr: "snapshot.config.agents?.defaults?.agentRuntime?.id === config.harnessRuntime"
10097
message:
10198
expr: "`expected agentRuntime.id=${config.harnessRuntime}, got ${JSON.stringify(snapshot.config.agents?.defaults?.agentRuntime)}`"
102-
- assert:
103-
expr: "snapshot.config.agents?.defaults?.agentRuntime?.fallback === config.harnessFallback"
104-
message:
105-
expr: "`expected agentRuntime.fallback=${config.harnessFallback}, got ${JSON.stringify(snapshot.config.agents?.defaults?.agentRuntime)}`"
106-
detailsExpr: "env.providerMode === 'live-frontier' ? `provider=${selected?.provider} model=${selected?.model} runtime=${snapshot.config.agents?.defaults?.agentRuntime?.id} fallback=${snapshot.config.agents?.defaults?.agentRuntime?.fallback}` : `mock mode: parsed ${scenario.id}`"
99+
detailsExpr: "env.providerMode === 'live-frontier' ? `provider=${selected?.provider} model=${selected?.model} runtime=${snapshot.config.agents?.defaults?.agentRuntime?.id}` : `mock mode: parsed ${scenario.id}`"
107100
- name: builds the medium game artifact
108101
actions:
109102
- if:

qa/scenarios/workspace/medium-game-plan-pi-harness.md

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ execution:
3030
requiredProvider: openai
3131
requiredModel: gpt-5.5
3232
harnessRuntime: pi
33-
harnessFallback: pi
3433
artifactFile: star-garden-defenders-pi.html
3534
gameTitle: Star Garden Defenders
3635
minBytes: 5000
@@ -81,8 +80,6 @@ steps:
8180
agentRuntime:
8281
id:
8382
expr: config.harnessRuntime
84-
fallback:
85-
expr: config.harnessFallback
8683
- call: waitForGatewayHealthy
8784
args:
8885
- ref: env

scripts/e2e/lib/codex-npm-plugin-live/assertions.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ function configure() {
6666
defaults: {
6767
...cfg.agents?.defaults,
6868
model: { primary: modelRef, fallbacks: [] },
69-
agentRuntime: { id: "codex", fallback: "none" },
69+
agentRuntime: { id: "codex" },
7070
workspace: path.join(state, "workspace"),
7171
skipBootstrap: true,
7272
timeoutSeconds: 420,

src/agents/pi-embedded-runner/run.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1162,6 +1162,8 @@ export async function runEmbeddedPiAgent(
11621162
ownerOnlyToolAllowlist: params.ownerOnlyToolAllowlist,
11631163
disableMessageTool: params.disableMessageTool,
11641164
forceMessageTool: params.forceMessageTool,
1165+
enableHeartbeatTool: params.enableHeartbeatTool,
1166+
forceHeartbeatTool: params.forceHeartbeatTool,
11651167
requireExplicitMessageTarget: params.requireExplicitMessageTarget,
11661168
internalEvents: params.internalEvents,
11671169
bootstrapPromptWarningSignaturesSeen,

src/agents/pi-embedded-runner/run/attempt.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -925,6 +925,8 @@ export async function runEmbeddedAttempt(
925925
params.requireExplicitMessageTarget ?? isSubagentSessionKey(params.sessionKey),
926926
disableMessageTool: params.disableMessageTool,
927927
forceMessageTool: params.forceMessageTool,
928+
enableHeartbeatTool: params.enableHeartbeatTool,
929+
forceHeartbeatTool: params.forceHeartbeatTool,
928930
authProfileStore: params.authProfileStore,
929931
recordToolPrepStage: (name) => corePluginToolStages.mark(name),
930932
onYield: (message) => {

src/agents/pi-embedded-runner/run/params.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,10 @@ export type RunEmbeddedPiAgentParams = {
9494
promptMode?: PromptMode;
9595
/** Keep the message tool available even when a narrow profile would omit it. */
9696
forceMessageTool?: boolean;
97+
/** Include the heartbeat response tool for structured heartbeat outcomes. */
98+
enableHeartbeatTool?: boolean;
99+
/** Keep the heartbeat response tool available even when a narrow profile would omit it. */
100+
forceHeartbeatTool?: boolean;
97101
/** Allow runtime plugins for this run to late-bind the gateway subagent. */
98102
allowGatewaySubagentBinding?: boolean;
99103
sessionFile: string;

src/auto-reply/get-reply-options.types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ export type GetReplyOptions = {
5959
suppressToolErrorWarnings?: boolean;
6060
/** If true, run the model without OpenClaw tools for this turn. */
6161
disableTools?: boolean;
62+
/** If true, include the heartbeat response tool for structured heartbeat outcomes. */
63+
enableHeartbeatTool?: boolean;
64+
/** If true, keep the heartbeat response tool available even under narrow tool profiles. */
65+
forceHeartbeatTool?: boolean;
6266
/**
6367
* If true, dispatch skips default tool/progress text messages and expects the
6468
* channel to surface progress via its own streaming/edit UX.

0 commit comments

Comments
 (0)