fix(context-engine): forward isHeartbeat to afterTurn hook [AI-assisted]#89311
fix(context-engine): forward isHeartbeat to afterTurn hook [AI-assisted]#89311luyao618 wants to merge 1 commit into
Conversation
The ContextEngine afterTurn() SDK type declares isHeartbeat?: boolean to let plugins distinguish heartbeat turns from user turns, but every call site (harness finalize, embedded-agent loop hook, attempt.ts post finalize) omitted the field. Plugins such as OpenViking that branch on afterTurnParams.isHeartbeat could not reliably detect heartbeat turns and risked polluting context state. Thread isHeartbeat through finalizeHarnessContextEngineTurn and installContextEngineLoopHook. In attempt.ts pull the value from the already-registered AgentRunContext (registered by auto-reply with isHeartbeat) via getAgentRunContext(params.runId). Only emit the field when defined so the absence-vs-false distinction is preserved. Add it.each-parameterized regression tests covering true, false, and the omitted-by-caller cases. Closes openclaw#89302 [AI-assisted]
|
Codex review: needs real behavior proof before merge. Reviewed June 1, 2026, 11:47 PM ET / 03:47 UTC. Summary PR surface: Source +10, Tests +98. Total +108 across 6 files. Reproducibility: yes. source inspection gives a high-confidence reproduction path: current main declares Review metrics: none identified. Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Proof guidance:
Risk before merge
Maintainer options:
Next step before merge
Security Review findings
Review detailsBest possible solution: Remove the unused import, keep the narrow heartbeat propagation from existing run-kind state, update the PR proof section to the exact accepted labels, and rerun the focused context-engine tests plus Do we have a high-confidence way to reproduce the issue? Yes, source inspection gives a high-confidence reproduction path: current main declares Is this the best way to solve the issue? No, not as submitted: forwarding the existing heartbeat trigger is the right narrow fix, but the extra unused import and rejected proof format need to be fixed before this is the best mergeable solution. Full review comments:
Overall correctness: patch is incorrect AGENTS.md: found and applied where relevant. Codex review notes: model gpt-5.5, reasoning high; reviewed against 7d5d62511f62. Label changesLabel changes:
Label justifications:
Evidence reviewedPR surface: Source +10, Tests +98. Total +108 across 6 files. View PR surface stats
What I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
|
Summary
ContextEngine.afterTurn()SDK type declaresisHeartbeat?: boolean, but every runtime call site omitted the field, leaving plugins unable to distinguish heartbeat turns from real user turns.isHeartbeatthroughfinalizeHarnessContextEngineTurnandinstallContextEngineLoopHook, and forward it from the embedded attempt runner and the Codex app-server harness using each attempt's existingtrigger === "heartbeat"signal.afterTurncall paths got a new optional field; absence-vs-falseis preserved by spreading conditionally.ingest/ingestBatch/assemble/compactsignatures. No change to engine implementations. No change to heartbeat detection logic inauto-reply/heartbeat-filter.tsorinfra/agent-events.ts. No change to channel/provider plugins.Motivation
Bundled and third-party context-engine plugins (e.g. OpenViking) already check
afterTurnParams.isHeartbeatdefensively to avoid polluting persistent context with heartbeat turns. Because the runtime never sent that field, those plugins silently treated heartbeats as normal turns, growing memory state with no real user content. Fixing this honors the published SDK contract and unblocks heartbeat-aware engines on existing OpenClaw releases.Change Type (select all)
Scope (select all touched areas)
Linked Issue/PR
Real behavior proof (required for external PRs)
contextEngine.afterTurnwas never receivingisHeartbeatdespite the SDK type declaring it.0e16e720915de385d2a6b6471fecbee9cd121a93, macOS, Node v22.15.0. Repro drives the realfinalizeHarnessContextEngineTurnandinstallContextEngineLoopHooksource modules viatsx(no mocks of the modules under test).repro-89302.mjsthat importssrc/agents/harness/context-engine-lifecycle.tsandsrc/agents/embedded-agent-runner/tool-result-context-guard.tsdirectly, installs a fakeContextEnginewhoseafterTurnrecords its params, and invokes both finalize paths withisHeartbeat: true.node_modules/.bin/tsx repro-89302.mjsisHeartbeatto the engine, matching the SDK type contract.Root Cause (if applicable)
afterTurnwas introduced its call sites were copied without the heartbeat field, even though the SDK type andingest/ingestBatchalready documentedisHeartbeat. The runtime already knows the heartbeat status viaEmbeddedRunAttemptParams.trigger === "heartbeat"(also stored inAgentRunContext.isHeartbeat); the seam just never forwarded it.isHeartbeatround-trips throughfinalizeHarnessContextEngineTurn/installContextEngineLoopHookwould have caught it.afterTurnis newer thaningest/ingestBatch, so its parameter coverage drifted from those siblings.Regression Test Plan (if applicable)
src/agents/harness/context-engine-lifecycle.test.ts,src/agents/embedded-agent-runner/tool-result-context-guard.test.tsisHeartbeat=true|false, the engine'safterTurnreceives the same value; when the caller omits it, the field is absent on the payload (preserving the undefined contract).it.eachparameterizes true/false plus a separate omitted case at each seam.User-visible / Behavior Changes
None for runs without a context engine. For sessions with a context-engine plugin that branches on
afterTurnParams.isHeartbeat, heartbeat turns are now correctly identified as heartbeat, matching the SDK type. Plugins that ignore the field are unaffected.Diagram (if applicable)
Security Impact (required)
Repro + Verification
Environment
tsx)Steps
git fetch upstream && git checkout fix/context-engine-after-turn-heartbeatnode_modules/.bin/vitest run src/agents/harness/context-engine-lifecycle.test.ts src/agents/embedded-agent-runner/tool-result-context-guard.test.ts→ 112/112 passpnpm exec oxfmt --check <changed-files>→ cleannode scripts/run-tsgo.mjs -p test/tsconfig/tsconfig.core.test.json --pretty false --incremental→ no errors