* refactor(runtime): own ambient state lifecycle cleanup
* chore(plugin-sdk): refresh additive API baseline
* fix(runtime): restore agent wait listener lifecycle
Keep agent-job data process-shared while preserving module-local event subscription ownership. Ratchet the public wildcard budget down after replacing text-runtime's wildcard singleton export with named exports.
* feat(memory): add provenance and recall metadata to the memory index
* feat(memory): provenance-gated promotion and capture hygiene
* feat(dreaming): LLM consolidation with deterministic gates, on by default
* feat(active-memory): deterministic recall lane with escalation default
* feat(memory): user model file and standing intents
* docs(memory): document the memory architecture
* fix(memory): live-QA fixes — metadata writers, provenance classes, intent scope, claim accumulation
* refactor(prompt): plain inbound context labels with a provenance marker
Replaces trust-worded inbound context labels ("(untrusted metadata)",
"(untrusted, for context)") with plain labels plus a fixed provenance
marker suffix appended to every OpenClaw-injected context header.
Detection keys on the marker, not label text, so strippers stay correct
across UI, TUI, replay, /trace segmentation, memory recall, and the Swift
chat preprocessor. Drops sanitizeInboundSystemTags in favor of the marker
boundary plus trusted system-prompt narration.
Renames the untrusted-named plugin SDK context identifiers to
channel-provenance names, keeping deprecated aliases registered for
removal after 2026-09-08.
Adds `openclaw doctor --fix` migrations that rewrite legacy inbound
labels in stored SQLite transcripts and purge legacy envelope-
contaminated LanceDB recall rows.
* fix(ci): resolve gate failures for plain inbound context labels
- doctor sqlite readers: open read-only connections via openNodeSqliteDatabase
so the Kysely connection-boundary guardrail holds; unexport the now-internal
transcript snapshot type (Knip unused-export gate).
- compat registry: split the record table into registry-records.ts and
plugin-sdk-subpath-records.ts. The new compat record pushed registry.ts past
the 700-line oxlint cap; suppressions are disallowed, so follow the existing
sibling record-module pattern. Public exports and PluginCompatCode literals
unchanged.
- acp-runtime test: assert current finalization behavior (newline normalization
only). The bracket de-fang and System: rewrite it expected were removed with
sanitizeInboundSystemTags; forged system lines are neutralized at the
system-event queue, the single chokepoint feeding the System:-per-line render.
- regenerate docs_map and the plugin SDK API baseline manifest.
* fix(prompt): harden inbound context label migration and drop in-band sanitizer
Review follow-ups on the plain-label + provenance-marker change:
- Remove src/security/system-tags.ts. Rewriting inbound text to neutralize
look-alike `System:`/`[System]` markers corrupted legitimate user text and is
not a real injection boundary; role separation plus external-content wrapping
is. Explicit product decision, recorded at the system-event queue.
- Narrow the LanceDB legacy-row purge so it cannot delete benign memories. It
now requires a complete known legacy sentinel line, a legacy label followed by
a fenced JSON body, or the complete legacy external-content header. The prior
predicates matched ordinary prose such as `Notes (untrusted metadata):`, and
deletion is irreversible.
- Make explicit-empty canonical ChannelStructuredContext win over the deprecated
alias via a present/absent result instead of collapsing `[]` to undefined.
- Keep `\r?` in the active-memory doctor rule. It is the only rule spanning the
header's line break, migrated assistant rows skip newline normalization, and
without it the marked-header replace wins and the body strips to empty. Added
a CRLF regression test.
- Fix stale comments that described removed behavior, and cover the Swift
prose-block strip path.
Claude-Session: https://claude.ai/code/session_01WNzsPddQmxy9Y7jKD4wAxH
* chore(compat): date the annotated deprecation families and expose removal-pending debt
Adds 10 dated compat-registry records for shipped deprecated surfaces that
previously had @deprecated annotations only (removeAfter 2026-10-01 per the
approved 60-day window), extends the beta5 session-store record with the
package-root aliases, surfaces removal-pending records with due dates,
blockers, and readers in the plugin boundary report, and converts undated
cleanup obligations (CI timing job, zizmor excessive-permissions, staged
Codex CI auth, memory-host legacy state dir, agent-DB schema-ladder floors)
into dated markers visible to the weekly dated-TODO sweep. Metadata,
diagnostics, and docs only - no runtime behavior change.
* fix(compat): keep extension src test paths out of core string literals
The core import guardrail forbids extensions/*/src/ path shapes in core
production string literals; cite the package-root google-meet test and the
registry test for official-plugin-export-aliases instead.
* fix(memory): keep canonical rows when same-file legacy memory tables diverge
* fix(memory): keep canonical-owned sources' chunk sets coherent during legacy import
* fix(memory): import legacy chunks when canonical source has no chunks
The same-file legacy migration excluded a legacy chunk from import whenever the
canonical index already had a source row for its (path, source). That stranded a
file whose canonical source was registered but had no chunks yet (indexing
interrupted before chunks were written, or embedding pending/failed): the legacy
chunks were its only searchable content, and the matching source hash stops sync
from re-indexing, so the file went silently unsearchable.
Re-key the chunk-coherence exclusion on canonical chunk ownership instead:
snapshot the (path, source) pairs that already have canonical chunks before the
import and skip legacy chunks only for those. A source with a canonical row but
no chunks now imports its legacy chunks. The snapshot is taken pre-insert because
the exclusion predicate reads the chunks table the import writes to.
Add regressions: legacy chunks import for a chunk-less canonical source while a
chunk-owning source still drops its stale legacy chunk; and restore abort
coverage for the meta and chunks copy assertions (previously only files was
exercised).
* fix(memory): harden same-file legacy conflict recovery
* fix(memory): rebuild ambiguous partial legacy sources
* fix(memory): reconcile migrated derived indexes
* fix(memory): close migrated index ownership gaps
* test(memory): align migration expectations
* test(tooling): match routed test order
* test(memory): exercise vector reload cleanup
* test(memory): prove real vector reload cleanup
* fix(memory): make migrated indexes converge
---------
Co-authored-by: Serhii Leniv <leniv.tech@gmail.com>
Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Preserve the shipped default identity while hashing custom header identity without retaining raw values.
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* 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 #70737).
Refs: #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: #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 <1707617+ekinnee@users.noreply.github.com>
---------
Co-authored-by: Erick Kinnee <erick@ekinnee.dev>
Co-authored-by: Erick Kinnee <ekinnee@gmail.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>