Skip to content

fix(memory): corpus dreaming eligibility contract + heartbeat provenance test#110571

Open
headbouyJB wants to merge 3 commits into
openclaw:mainfrom
headbouyJB:fix/dream-corpus-heartbeat-noise
Open

fix(memory): corpus dreaming eligibility contract + heartbeat provenance test#110571
headbouyJB wants to merge 3 commits into
openclaw:mainfrom
headbouyJB:fix/dream-corpus-heartbeat-noise

Conversation

@headbouyJB

@headbouyJB headbouyJB commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

What Problem This Solves

Issue #103720 identified two corpus-contamination paths in the Dreaming pipeline.

A. Archive artifacts (deleted/reset transcripts) re-ingested by Dreaming

.jsonl.deleted.* and .jsonl.reset.* rotation files are retained in the shared corpus for memory_search continuity, but Dreaming must learn only from the live session corpus. The runtime guard (da50a450d2) correctly skips these, but it operated via an ad-hoc artifactKind === "archive-artifact" check inside the consuming phase — SessionTranscriptCorpusEntry carried no explicit eligibleForDreaming signal. Any future corpus consumer had to re-implement the exclusion rule independently, and the clawsweeper review required a first-class field, a dedicated test file, and the consuming phase to gate on the field rather than repeating the ad-hoc check.

B. Natural-language heartbeat responses leaking into the dream corpus

buildSessionEntry() gained provenance-based insideHeartbeatTurn suppression in 571f1dcef9, correctly excluding assistant responses paired with { kind: "internal_system", sourceTool: "heartbeat" } user messages. No integration test exercised that path through a real dreaming sweep — specifically the case where the assistant responds with natural-language text rather than the exact HEARTBEAT_OK token.

Clawsweeper acceptance criteria (from issue #103720):

# Requirement Status
AC-1 SessionTranscriptCorpusEntry must carry eligibleForDreaming: boolean; archive artifacts false, active sessions true ✓ satisfied
AC-2 New test file session-transcript-corpus.test.ts under packages/memory-host-sdk/src/host/ covering active, deleted, reset, orphaned-deleted, and cron-lineage variants ✓ satisfied
AC-3 collectSessionIngestionBatches() in dreaming-phases.ts must gate on !entry.eligibleForDreaming rather than repeating artifactKind === "archive-artifact" ✓ satisfied
AC-4 Integration test in dreaming-phases asserting that a provenance: { kind: "internal_system", sourceTool: "heartbeat" } user message suppresses the paired natural-language assistant response through a full dreaming sweep ✓ satisfied

All four clawsweeper acceptance criteria are met by this PR.


What This PR Does

  1. Adds eligibleForDreaming: boolean to SessionTranscriptCorpusEntry — set at construction time (true for active sessions, false for all archive artifacts) so any corpus consumer can honour the exclusion without re-examining artifactKind.

  2. Adds session-transcript-corpus.test.ts (new file, 5 unit tests) covering: SQLite-backed active session (true), .deleted archive (false), .reset archive (false), orphaned-deleted archive with no sessions.json entry (false), and cron-lineage archive preserving generatedByCronRun: true alongside eligibleForDreaming: false.

  3. Updates dreaming-phases.ts to gate on !entry.eligibleForDreaming instead of entry.artifactKind === "archive-artifact", making the dreaming skip a consequence of the corpus-level contract rather than a second ad-hoc check.

  4. Adds dreaming-phases integration test that seeds a session with a provenance-authenticated heartbeat user message followed by a natural-language assistant response ("Looks like you're all set for the morning"), runs a full dreaming sweep, and asserts the heartbeat turn and its paired response are absent from corpus output while a normal user/assistant exchange in the same session is retained.

Files changed:

File Change
packages/memory-host-sdk/src/host/session-transcript-corpus.ts eligibleForDreaming field added to type; true in toSessionStoreCorpusEntry(), false in toArtifactCorpusEntry()
packages/memory-host-sdk/src/host/session-transcript-corpus.test.ts NEW — 5 unit tests for the field across all artifact variants
packages/memory-host-sdk/src/host/session-files.test.ts Updated 3 strict-equality assertions to include eligibleForDreaming: false
extensions/memory-core/src/dreaming-phases.ts Skip condition changed from artifactKind === "archive-artifact" to !entry.eligibleForDreaming
extensions/memory-core/src/dreaming-phases.test.ts seedDreamingSessionTranscript() accepts optional provenance; new e2e heartbeat-provenance sweep test added

Evidence

Rebased HEAD: beedbb7076c69eb04ae1a8ccb2d492086ded182b
Current upstream/main: 78b4a4afedf33e39a1b64eee07ffa7543abe79ef

Mutation check — BEFORE (src reverted to upstream state)

Command:

cd ~/dev/openclaw-wt-103720
node scripts/run-vitest.mjs packages/memory-host-sdk/src/host/session-transcript-corpus.test.ts

Output:

 Test Files  1 failed (1)
      Tests  5 failed (5)
   Duration  1.66s

AssertionError: expected { agentId: 'main', …(5) } to match object { …(4) }

- Expected
+ Received

  {
    "artifactKind": "archive-artifact",
-   "eligibleForDreaming": false,
    "generatedByCronRun": true,
    "sessionId": "cron-run",
  }

All 5 tests fail with eligibleForDreaming absent — the field did not exist before this PR.

Mutation check — AFTER (fix commit beedbb7076)

Command:

cd ~/dev/openclaw-wt-103720
node scripts/run-vitest.mjs packages/memory-host-sdk/src/host/session-transcript-corpus.test.ts

Output:

 Test Files  1 passed (1)
      Tests  5 passed (5)
   Duration  1.27s

Full test suite on fix commit

Commands:

node scripts/run-vitest.mjs packages/memory-host-sdk/src/host/session-transcript-corpus.test.ts
node scripts/run-vitest.mjs extensions/memory-core/src/dreaming-phases.test.ts
node scripts/run-vitest.mjs packages/memory-host-sdk/src/host/session-files.test.ts
Suite Tests Result
session-transcript-corpus.test.ts (NEW) 5/5 PASS
dreaming-phases.test.ts (+1 new e2e) 47/47 PASS
session-files.test.ts 41/41 PASS

Typecheck (node scripts/run-tsgo.mjs): the only errors are pre-existing on upstream/main (packages/net-policy/src/ip.ts lines 67/69 — "benchmarking" / "orchid2" type mismatches); not caused by this change.

git diff --check: clean.


Notes

Legacy-provenance window. insideHeartbeatTurn suppression in buildSessionEntry() relies on provenance metadata being present in persisted JSONL/SQLite records. Session files written before 571f1dcef9 (2026-07-17) were not rewritten with provenance, so natural-language heartbeat responses from those sessions may remain eligible for Dreaming ingestion until they rotate out naturally. This PR does not backfill legacy sessions: backfilling would require rewriting all existing JSONL/SQLite records to inject provenance that was never captured, risking data loss on sessions that have genuine content mixed with heartbeat turns and no reliable way to distinguish them retrospectively. The gap is transient — sessions rotate on their normal schedule — and does not affect new sessions from 571f1dcef9 onward.

No text-fallback reintroduced. 571f1dcef9 deliberately removed user-spoofable text matching (see session-files.test.ts: "does not couple user-spoofed heartbeat text to the next assistant response"). This PR does not reintroduce text-based detection.

AI-assisted development. This change was developed with Claude Code assistance (claude-sonnet-4-6). All logic reviewed and validated manually; mutation test run and outputs verified by hand.

Fixes #103720


Real behavior proof (entry-level, both builds — added after triage)

Probe on PR head beedbb7076 vs merge-base 78b4a4afed: seeded an SQLite-backed active session, a .jsonl.deleted. archive artifact, and a heartbeat-provenance turn; called listSessionTranscriptCorpusEntriesForAgent directly on each build.

Marker Entry class Baseline PR build Dreaming fate
CORPUS-LIVE-ALPHA active-session field absent eligibleForDreaming: true included (both)
CORPUS-DELETED-BETA archive-artifact excluded via artifactKind check eligibleForDreaming: false, excluded via field gate excluded (both)
HEARTBEAT-NOISE-GAMMA provenance-tagged turn pruned by existing buildSessionEntry filter same pruned (both; this PR adds the regression test)

Honest delta: runtime exclusion behavior is unchanged — the PR contributes the explicit contract, the property-driven gate, and the coverage (per the four acceptance criteria above).

Serialized-state flag resolved: SessionTranscriptCorpusEntry is a runtime-computed view, never persisted; the persisted dreaming state (writeSessionIngestionState, dreaming-phases.ts:587) is a separate {version, files, seenMessages} type, and JSON.stringify.*eligibleForDreaming greps to zero in production code. No migration surface.

Full probe detail in the comment below.

…integration tests for heartbeat provenance filtering

Add `eligibleForDreaming: boolean` to `SessionTranscriptCorpusEntry` so
archive artifacts (.jsonl.deleted.* / .jsonl.reset.*) carry an explicit
`false` signal rather than relying on corpus consumers to check
`artifactKind === "archive-artifact"` ad-hoc. Dreaming phases now gate
on `!entry.eligibleForDreaming` instead, making the eligibility boundary
a first-class corpus concern.

Also add the missing `session-transcript-corpus.test.ts` (clawsweeper
acceptance criterion) verifying that active sessions carry `true` and all
archive artifact variants (deleted, reset, orphaned, cron-lineage) carry
`false`.

Extend `seedDreamingSessionTranscript` to propagate an optional
`provenance` field, and add an end-to-end dreaming sweep test
demonstrating that a heartbeat turn with runtime provenance
`{ kind: "internal_system", sourceTool: "heartbeat" }` suppresses the
paired natural-language assistant response (not "HEARTBEAT_OK") from the
session corpus output — the path added by openclaw#109403 / commit 571f1dc.

Acceptance criteria satisfied:
- pnpm test packages/memory-host-sdk/src/host/session-files.test.ts           (41/41)
- pnpm test packages/memory-host-sdk/src/host/session-transcript-corpus.test.ts (5/5 new)
- pnpm test extensions/memory-core/src/dreaming-phases.test.ts                 (47/47, +1 new)

Fixes openclaw#103720

Co-Authored-By: Claude Fable 5 <[email protected]>
@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P2 Normal backlog priority with limited blast radius. labels Jul 18, 2026
@clawsweeper

clawsweeper Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed July 18, 2026, 5:44 AM ET / 09:44 UTC.

Summary
The PR adds eligibleForDreaming to session transcript corpus entries, uses it to exclude archive artifacts during Dreaming ingestion, and adds archive-eligibility plus heartbeat-provenance sweep tests.

PR surface: Source +12, Tests +289. Total +301 across 5 files.

Reproducibility: yes. in source, with medium confidence: archive corpus entries and provenance-tagged heartbeat turns have a concrete seeded path through the corpus reader and Dreaming sweep, and the contributor supplied before/after runtime output; this review did not independently execute that path.

Review metrics: none identified.

Stored data model
Persistent data-model change detected: serialized state: packages/memory-host-sdk/src/host/session-files.test.ts, serialized state: packages/memory-host-sdk/src/host/session-transcript-corpus.test.ts, serialized state: packages/memory-host-sdk/src/host/session-transcript-corpus.ts, unknown-data-model-change: extensions/memory-core/src/dreaming-phases.test.ts, unknown-data-model-change: packages/memory-host-sdk/src/host/session-transcript-corpus.test.ts, vector/embedding metadata: extensions/memory-core/src/dreaming-phases.test.ts. Confirm migration or upgrade compatibility proof before merge.

Merge readiness
Overall: 🐚 platinum hermit
Proof: 🐚 platinum hermit
Patch quality: 🐚 platinum hermit
Result: ready for maintainer review.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • none.

Next step before merge

  • [P2] No concrete automated repair is needed: the remaining action is ordinary maintainer review of a currently open implementation PR linked to the canonical bug.

Security
Cleared: The patch changes in-repository TypeScript corpus classification and tests only; no dependency, workflow, credential, permission, package-resolution, or code-execution surface is introduced.

Review details

Best possible solution:

Land a single bounded change that keeps Dreaming eligibility on the corpus-entry contract, preserves archive availability for memory_search, and retains the provenance-only heartbeat regression coverage without adding a text-based fallback.

Do we have a high-confidence way to reproduce the issue?

Yes in source, with medium confidence: archive corpus entries and provenance-tagged heartbeat turns have a concrete seeded path through the corpus reader and Dreaming sweep, and the contributor supplied before/after runtime output; this review did not independently execute that path.

Is this the best way to solve the issue?

Yes: placing the live-versus-archive decision on the corpus entry avoids repeated consumer-side artifactKind checks, while preserving the existing provenance-based heartbeat filter rather than reintroducing user-spoofable text matching.

AGENTS.md: unclear because the file could not be read completely.

Codex review notes: model internal, reasoning high; reviewed against fceae4d8aa04.

Label changes

Label changes:

  • add proof: sufficient: Contributor real behavior proof is sufficient. The contributor supplied a redacted before/after live-output probe against an SQLite-backed corpus setup that directly shows the new runtime eligibility values and the preserved archive/heartbeat outcomes.
  • add rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🐚 platinum hermit and patch quality is 🐚 platinum hermit.
  • add status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (live_output): The contributor supplied a redacted before/after live-output probe against an SQLite-backed corpus setup that directly shows the new runtime eligibility values and the preserved archive/heartbeat outcomes.
  • remove rating: 🦪 silver shellfish: Current PR rating is rating: 🐚 platinum hermit, so this older rating label is no longer current.
  • remove status: 📣 needs proof: Current PR status label is status: 👀 ready for maintainer look.

Label justifications:

  • P2: The PR addresses a bounded session-corpus correctness problem with limited blast radius and no demonstrated availability or security regression.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🐚 platinum hermit and patch quality is 🐚 platinum hermit.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (live_output): The contributor supplied a redacted before/after live-output probe against an SQLite-backed corpus setup that directly shows the new runtime eligibility values and the preserved archive/heartbeat outcomes.
  • proof: sufficient: Contributor real behavior proof is sufficient. The contributor supplied a redacted before/after live-output probe against an SQLite-backed corpus setup that directly shows the new runtime eligibility values and the preserved archive/heartbeat outcomes.
Evidence reviewed

PR surface:

Source +12, Tests +289. Total +301 across 5 files.

View PR surface stats
Area Files Added Removed Net
Source 2 15 3 +12
Tests 3 289 0 +289
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 5 304 3 +301

What I checked:

  • Corpus contract implementation: The proposed entry type carries a required eligibility flag; active session-store entries set it to true and archive-artifact entries set it to false, keeping the exclusion rule with the corpus classification. (packages/memory-host-sdk/src/host/session-transcript-corpus.ts:38, beedbb7076c6)
  • Dreaming consumer follows the contract: The Dreaming ingestion loop skips entries whose eligibleForDreaming value is false, while retaining the independent checkpoint-path exclusion. (extensions/memory-core/src/dreaming-phases.ts:824, beedbb7076c6)
  • Regression coverage: The branch adds direct corpus-entry coverage for live, deleted, reset, orphaned, and cron-lineage archive cases, and a full Dreaming-sweep test for provenance-marked heartbeat turns with natural-language assistant text. (extensions/memory-core/src/dreaming-phases.test.ts:1739, beedbb7076c6)
  • After-fix behavior proof: The contributor’s July 18, 2026 proof compares the merge base with the PR head using an SQLite-backed live session, a retained deleted artifact, and a provenance-tagged heartbeat turn; the PR head reports the new true/false eligibility values while preserving the intended exclusion behavior. (beedbb7076c6)
  • Canonical linked work: The PR uses closing syntax for the still-open report describing the same archive-ingestion and heartbeat-noise paths, so the issue should remain the canonical bug record until this PR is merged.

Likely related people:

  • headbouyJB: The available review context identifies this contributor as the author of the only proposed implementation and its runtime proof; current-main feature-history attribution could not be completed from the read-only inspection environment. (role: proposed patch author and current review contact; confidence: low; commits: beedbb7076c6; files: packages/memory-host-sdk/src/host/session-transcript-corpus.ts, extensions/memory-core/src/dreaming-phases.ts)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

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
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.
Review history (1 earlier review cycle)
  • reviewed 2026-07-18T08:45:23.374Z sha beedbb7 :: needs real behavior proof before merge. :: none

@headbouyJB

Copy link
Copy Markdown
Contributor Author

Real behavior proof — eligibleForDreaming corpus gating confirmed

Environment: PR worktree beedbb7076 (fix/dream-corpus-heartbeat-noise) vs baseline 78b4a4afed (merge-base), Node v26.0.0, macOS arm64. Probe script seeds an SQLite-backed active session + a .jsonl.deleted. archive artifact, calls listSessionTranscriptCorpusEntriesForAgent directly on both builds.

Entry-level evidence (three markers):

Marker Entry type BEFORE (78b4a4a) AFTER (beedbb7) Dreaming fate
CORPUS-LIVE-ALPHA active-session eligibleForDreaming FIELD ABSENT eligibleForDreaming: true INCLUDED (both builds)
CORPUS-DELETED-BETA archive-artifact FIELD ABSENT; excluded via artifactKind === "archive-artifact" check eligibleForDreaming: false; excluded via !entry.eligibleForDreaming EXCLUDED (both builds, different gate)
HEARTBEAT-NOISE-GAMMA provenance-tagged turn in active session pruned at turn level by buildSessionEntry provenance filter (existing code) same PRUNED from corpus text (both builds; new regression test added)

Honest delta: The PR does not change runtime exclusion behavior for archive artifacts — both builds correctly exclude them. The PR's value is (1) an explicit eligibleForDreaming: boolean data contract on SessionTranscriptCorpusEntry, (2) a single property-driven gate in collectSessionIngestionBatches replacing an implicit artifactKind string check, and (3) test coverage for both archive exclusion and heartbeat-provenance turn filtering.

Test results (PR build):

  • session-transcript-corpus.test.ts — 5/5 pass (marks SQLite active session true, all archive types false)
  • dreaming-phases.test.ts — 47/47 pass (includes new: "drops provenance-marked heartbeat turns with natural-language assistant responses from corpus")
  • session-files.test.ts — 41/41 pass

Data-model / serialized-state question:
SessionTranscriptCorpusEntry (and its new eligibleForDreaming field) is a runtime-computed view — built on every call to listSessionTranscriptCorpusEntriesForAgent by scanning the filesystem and session store. The persisted dreaming state (session-ingestion.json, written by writeSessionIngestionState at dreaming-phases.ts:587) is a separate type: { version: 3, files: {…}, seenMessages: {…} }. Global grep for JSON.stringify.*eligibleForDreaming in production code returns zero results. No migration concern.

Repro (repo root, either build):

node scripts/run-vitest.mjs packages/memory-host-sdk/src/host/session-transcript-corpus.test.ts extensions/memory-core/src/dreaming-phases.test.ts

(The entry-level probe seeds an SQLite-backed active session plus a .jsonl.deleted. artifact and calls listSessionTranscriptCorpusEntriesForAgent directly on each build — script available on request if useful for review.)

AI-assist disclosure: probe scripts, state seeding, and this proof document were produced with Claude Code (claude-sonnet-4-6).

@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Jul 18, 2026
@clawsweeper

clawsweeper Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

ClawSweeper status: review started.

I am starting a fresh review of this pull request: fix(memory): corpus dreaming eligibility contract + heartbeat provenance test This is item 1/1 in the current shard. Shard 0/1.

This placeholder means the worker is alive and reading the current context. I will edit this same comment with the actual review when the claws are done clicking.

Crustacean status: shell secured, claws on keyboard, evidence pebbles being sorted.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

extensions: memory-core Extension: memory-core P2 Normal backlog priority with limited blast radius. proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. size: M status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Deleted transcripts ingested by dreaming + incomplete heartbeat filter leaks heartbeat noise into dream corpus

1 participant