fix(dreaming): drop heartbeat assistant responses from dream corpus#109403
Conversation
|
Codex review: needs real behavior proof before merge. Reviewed July 17, 2026, 12:53 AM ET / 04:53 UTC. Summary PR surface: Source +11, Tests +115. Total +126 across 4 files. Reproducibility: yes. at source level: current corpus sanitization removes the heartbeat user marker and exact acknowledgment but does not associate arbitrary assistant replies with the heartbeat turn; the branch adds focused regression cases for that path. This review did not independently run a current-main live reproduction. Review metrics: none identified. Stored data model Root-cause cluster Members:
Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything. 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 detailsBest possible solution: Keep the provenance-based forward filter, demonstrate one real heartbeat transcript whose natural-language assistant replies are absent from the generated corpus while the following normal turn remains present, then land it as the heartbeat-specific fix while leaving the deleted-transcript portion of the canonical issue separately tracked. Do we have a high-confidence way to reproduce the issue? Yes at source level: current corpus sanitization removes the heartbeat user marker and exact acknowledgment but does not associate arbitrary assistant replies with the heartbeat turn; the branch adds focused regression cases for that path. This review did not independently run a current-main live reproduction. Is this the best way to solve the issue? Yes, the source-authenticated provenance marker is safer and narrower than matching user-visible heartbeat text or broad acknowledgment heuristics, and the turn-boundary tests cover the important sibling cases. The remaining gap is proof rather than an identified implementation defect. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against e7c389b97a26. Label changesLabel changes:
Label justifications:
Evidence reviewedPR surface: Source +11, Tests +115. Total +126 across 4 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
Review history (1 earlier review cycle)
|
The dreaming ingestion pipeline filters heartbeat user messages (containing [OpenClaw heartbeat poll]) via sanitizeSessionText, but the paired assistant response is only dropped when it is the exact string HEARTBEAT_OK. Local models frequently respond with natural-language acknowledgments (e.g. "Heartbeat received. Main is active.") which pass through the sanitizer unchanged and enter the dream corpus as low-confidence (0.58) memory snippets. Fix: track heartbeat user message drops in buildSessionEntry and skip the immediately following assistant response. This cross-message coupling is safe because the heartbeat prompt pattern is injected by the runtime and cannot be spoofed by user input, unlike [cron:...] or System (untrusted): ... patterns (see PR openclaw#70737). Refs: openclaw#103720
…xt matching
Replace text-based heartbeat detection (isGeneratedHeartbeatPromptMessage)
with provenance-based authentication. The heartbeat turn now carries
provenance: { kind: "heartbeat" } when persisted to the transcript,
and buildSessionEntry checks for this provenance instead of matching
user-spoofable text content.
Changes:
- src/sessions/input-provenance.ts: Add "heartbeat" to
INPUT_PROVENANCE_KIND_VALUES
- src/auto-reply/reply/get-reply-run.ts: Attach heartbeat provenance
to user turn input when isHeartbeat is true
- packages/memory-host-sdk/src/host/session-files.ts: Check
message.provenance.kind === "heartbeat" instead of text matching
- packages/memory-host-sdk/src/host/session-files.test.ts: Update
heartbeat test to include provenance; add lookalike regression test
This addresses the ClawSweeper review finding [P1] about user-spoofable
text matching. The cross-message coupling is now authenticated by
runtime provenance, not user-typed content.
Refs: openclaw#103720
The findHeartbeatContaminatedCorpusLines function could not read SQLite-backed session transcripts. When a corpus ref had no .jsonl extension (SQLite logical path format), it appended .jsonl, read an empty file, and silently skipped — leaving pre-fix heartbeat contamination intact for SQLite users. Fix: - Add loadTranscriptLinesFromSqlite helper that reads events via the canonical loadTranscriptEventsSync API - Serialize all events to preserve original event positions for corpus #L<n> line-number references - Fall back to SQLite reader when filesystem read fails on a non-.jsonl corpus ref path Test: add SQLite-backed session regression test that seeds a session, writes corpus refs without .jsonl extension, and verifies both audit detection and targeted repair.
…nd early return P1: Remove clearScopedSessionIngestionState call after heartbeat line removal. Clearing the cursor causes the next ingestion to re-process the transcript from the beginning, duplicating normal corpus lines that were deliberately retained. P2: Remove early return after heartbeat cleanup so the self-ingestion narrative content check and archiveDiary request still run.
…t ID The session path in corpus refs is already in the format 'sessions/main/abc.jsonl' (includes agent subdirectory). The previous code prepended source.agentId again, creating a path like 'agents/main/sessions/main/abc.jsonl' — which silently returned empty in production workspaces. Fix: use path.basename to extract just the filename from the session path, then construct the transcript path correctly.
…LOC ratchet The dreaming-repair.ts file grew from 337 to 831 lines, exceeding the 500-line LOC ratchet limit. Split helper utilities into a new dreaming-repair-utils.ts file: - dreaming-repair.ts (365 lines): imports, types, public API functions (auditDreamingArtifacts, repairDreamingArtifacts) - dreaming-repair-utils.ts (498 lines): all helper functions, constants, and internal types
…lear When heartbeat contamination is found, archive the entire session-corpus directory and clear all SQLite checkpoints instead of doing targeted line-by-line rewrite. This ensures the cleaned corpus gets re-ingested under the new provenance-based forward filter.
Co-authored-by: Erick Kinnee <[email protected]>
a22b8bd to
5b095b1
Compare
|
Merged via squash.
|
…penclaw#109403) * fix(dreaming): drop heartbeat assistant responses from dream corpus The dreaming ingestion pipeline filters heartbeat user messages (containing [OpenClaw heartbeat poll]) via sanitizeSessionText, but the paired assistant response is only dropped when it is the exact string HEARTBEAT_OK. Local models frequently respond with natural-language acknowledgments (e.g. "Heartbeat received. Main is active.") which pass through the sanitizer unchanged and enter the dream corpus as low-confidence (0.58) memory snippets. Fix: track heartbeat user message drops in buildSessionEntry and skip the immediately following assistant response. This cross-message coupling is safe because the heartbeat prompt pattern is injected by the runtime and cannot be spoofed by user input, unlike [cron:...] or System (untrusted): ... patterns (see PR openclaw#70737). Refs: openclaw#103720 * test(dreaming): add heartbeat assistant response filter test * fix(dreaming): use provenance-based heartbeat detection instead of text matching Replace text-based heartbeat detection (isGeneratedHeartbeatPromptMessage) with provenance-based authentication. The heartbeat turn now carries provenance: { kind: "heartbeat" } when persisted to the transcript, and buildSessionEntry checks for this provenance instead of matching user-spoofable text content. Changes: - src/sessions/input-provenance.ts: Add "heartbeat" to INPUT_PROVENANCE_KIND_VALUES - src/auto-reply/reply/get-reply-run.ts: Attach heartbeat provenance to user turn input when isHeartbeat is true - packages/memory-host-sdk/src/host/session-files.ts: Check message.provenance.kind === "heartbeat" instead of text matching - packages/memory-host-sdk/src/host/session-files.test.ts: Update heartbeat test to include provenance; add lookalike regression test This addresses the ClawSweeper review finding [P1] about user-spoofable text matching. The cross-message coupling is now authenticated by runtime provenance, not user-typed content. Refs: openclaw#103720 * fix(dreaming): add targeted heartbeat-derived corpus repair * fix(dreaming): fix TS return type for clearScopedLegacySessionIngestionJson catch * fix(dreaming): match chunked SQLite seen-state keys by stored scope * fix(dreaming): repair SQLite-backed session transcript lookup in doctor The findHeartbeatContaminatedCorpusLines function could not read SQLite-backed session transcripts. When a corpus ref had no .jsonl extension (SQLite logical path format), it appended .jsonl, read an empty file, and silently skipped — leaving pre-fix heartbeat contamination intact for SQLite users. Fix: - Add loadTranscriptLinesFromSqlite helper that reads events via the canonical loadTranscriptEventsSync API - Serialize all events to preserve original event positions for corpus #L<n> line-number references - Fall back to SQLite reader when filesystem read fails on a non-.jsonl corpus ref path Test: add SQLite-backed session regression test that seeds a session, writes corpus refs without .jsonl extension, and verifies both audit detection and targeted repair. * fix(dreaming): address autoreview P1 and P2 for checkpoint clearing and early return P1: Remove clearScopedSessionIngestionState call after heartbeat line removal. Clearing the cursor causes the next ingestion to re-process the transcript from the beginning, duplicating normal corpus lines that were deliberately retained. P2: Remove early return after heartbeat cleanup so the self-ingestion narrative content check and archiveDiary request still run. * fix(dreaming): resolve corpus ref session path without duplicate agent ID The session path in corpus refs is already in the format 'sessions/main/abc.jsonl' (includes agent subdirectory). The previous code prepended source.agentId again, creating a path like 'agents/main/sessions/main/abc.jsonl' — which silently returned empty in production workspaces. Fix: use path.basename to extract just the filename from the session path, then construct the transcript path correctly. * refactor(dreaming): split repair utils into separate file to satisfy LOC ratchet The dreaming-repair.ts file grew from 337 to 831 lines, exceeding the 500-line LOC ratchet limit. Split helper utilities into a new dreaming-repair-utils.ts file: - dreaming-repair.ts (365 lines): imports, types, public API functions (auditDreamingArtifacts, repairDreamingArtifacts) - dreaming-repair-utils.ts (498 lines): all helper functions, constants, and internal types * fix(ts): add missing type imports for return types in dreaming-repair.ts * fix(deadcode): remove unused exports from dreaming-repair-utils.ts * fix: resolve merge conflict marker in get-reply-run.ts * fix: remove unnecessary export from INPUT_PROVENANCE_KIND_VALUES * fix(dreaming): converge targeted repair path with wholesale archive+clear When heartbeat contamination is found, archive the entire session-corpus directory and clear all SQLite checkpoints instead of doing targeted line-by-line rewrite. This ensures the cleaned corpus gets re-ingested under the new provenance-based forward filter. * fix(dreaming): authenticate heartbeat transcript turns Co-authored-by: Erick Kinnee <[email protected]> --------- Co-authored-by: Erick Kinnee <[email protected]> Co-authored-by: Erick Kinnee <[email protected]> Co-authored-by: Peter Steinberger <[email protected]>
Related: #103720
Supersedes #104085.
What Problem This Solves
Fixes an issue where natural-language assistant responses to heartbeat polls could enter the Dreaming session corpus. The existing sanitizer removed the heartbeat user marker and exact heartbeat acknowledgments, but retained arbitrary assistant text generated during the same heartbeat turn.
Why This Change Was Made
Heartbeat turns now persist the existing internal-system provenance with a heartbeat source-tool marker. Session corpus construction uses that trusted runtime fact to exclude every assistant response in the heartbeat turn until the next user turn.
This does not add a new provenance kind, and user-authored heartbeat-like text cannot suppress an assistant response. Historical transcripts do not contain an authenticated heartbeat marker, so this change intentionally does not guess from text or delete existing corpus lines.
User Impact
New heartbeat responses no longer add low-value Dreaming memories. Normal user turns, media-only turns, and inter-session turns remain indexable. Existing Dreaming diaries and ingestion checkpoints are untouched.
Evidence