`memory status` always appended "Fix: openclaw memory status --fix"
when any audit issue was present, even when none of the issues were
actually repaired by --fix. A missing qmd index file (qmd-index-missing)
is marked fixable: false, so running the suggested --fix left the
issue unresolved. Gate the --fix hint on at least one fixable issue,
and suggest `openclaw memory index --agent <id>` (which builds a fresh
index) when the qmd index file is missing.
* fix(memory-core): stop MEMORY.md compaction from deleting marker-free user sections
Budget compaction decided that a MEMORY.md section belonged to OpenClaw purely
from its heading text, so a user-authored section under a heading that looks
generated, such as "## Promoted From Short-Term Memory (2026-04-10)", was
eligible for deletion once the file plus the new promotion exceeded
memoryFileMaxChars. The rewrite succeeded silently and the atomic replace made
the loss durable.
A matching heading is now provisional: the block is classified as a promotion
only when it structurally contains at least one
"<!-- openclaw-memory-promotion:... -->" marker, which buildPromotionSection
emits for every promoted candidate. Marker-free sections are preserved.
Generated sections, including the multi-project shape whose markers sit under
"### Global" and "### Project:" subsections, are still dropped oldest-first, so
the size bound from #73691 is unchanged.
Extends #116057, which made only subsection handling marker-aware and left the
top-level heading classifier trusting presentation text.
* fix(memory-core): preserve mixed promotion blocks
Validate the complete marker-and-entry grammar before treating a promotion-shaped block as writer-owned. Preserve marker-only and mixed blocks so unheaded operator notes cannot be deleted during budget compaction.
Co-authored-by: yetval <yetvald@gmail.com>
* test(memory-core): allow cold citation startup
---------
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
* fix(memory-core): stop MEMORY.md compaction from deleting user headings
parseMemoryBlocks segmented MEMORY.md only on "## ", so a user heading of
any other level did not close the promotion section above it. The section
ran on to the next "## " heading or EOF, and dropping it to fit the budget
deleted the user text it had swallowed, with no backup and no diagnostic.
Close the current block on any ATX heading, keeping the generator's own
"### Global" and "### Project: <key>" subheadings inside the promotion
body so multi-project sections stay droppable whole.
* fix(memory-core): identify generated promotion subsections by their entry marker
The first exception treated every "### Global" and "### Project: <key>"
heading under a promotion block as dreaming-owned, so a user who wrote one
of those headings themselves still lost it to compaction, which is the same
data-loss class this change set out to fix.
A generated subsection always leads with an openclaw-memory-promotion entry
marker comment, so require that marker as the next non-blank line before
treating the heading as part of the promotion body. User headings that
collide with the generated names now terminate the block and survive.
* fix(memory-core): recognize tab-delimited and empty ATX headings
The heading detector required a literal ASCII space after the opening #
sequence. CommonMark also allows a tab or end of line, so a user note
starting with a tab-delimited heading stayed inside the promotion block
above it and was deleted when compaction dropped that block.
Accept a space, a tab, or end of line after the # sequence. The generated
subsection pattern stays space-only because that is what buildPromotionSection
emits, so a tab-delimited heading is user-authored and is preserved.
* fix(memory-core): preserve Setext headings during compaction
Setext headings were still absorbed into the generated promotion block above them, so dropping that block could silently delete durable user notes. Move the heading paragraph into a preserved block when its underline is encountered, while keeping marker-backed generated content compactable.
* fix(memory-core): preserve indented ATX headings
---------
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
Allow before_agent_reply plugins to declare host-enforced trigger eligibility so scheduled-only hooks do not block interrupted user-turn recovery. Keep omitted and malformed scopes fail-closed, scope both memory-core maintenance hooks, and cover three runner reload cycles through the public registration contract.
Refs: #111442
Source: #114836
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
Use the existing surrogate-safe helpers at the dreaming lead and heading boundaries. Preserve contributor investigations in #107882 and #107910.
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(memory-core): scope dream narrative sessions to their owning agent
Dreaming narratives built unscoped subagent session keys
(`dreaming-narrative-<phase>-<hash>`). Sessions live in per-agent SQLite
stores, so the plugin-runtime ownership pre-check could not resolve a store
and every narrative call failed with "Cannot resolve SQLite session scope
without an agent id" — the whole diary pipeline produced nothing while the
sweep still logged "dreaming promotion complete".
The agent id was available all along: `resolveMemoryDreamingWorkspaces`
returns it per workspace and the hook context carries the triggering agent.
Both were dropped. Session keys are now `agent:<id>:dreaming-narrative-...`,
while runIds stay unscoped so the orphan-transcript scrub marker still
matches. An unexpected narrative failure now writes the same dated fallback
diary entry the other failure branches write, and the promotion summary
warns instead of informing when every workspace failed.
* fix(memory-core): narrow the narrative export surface and type the sweep test double
* test(memory-core): drop the now-unused missing-path helper
* fix(memory-core): scope the narrative run id and keep empty sweeps a no-op
* fix(memory-core): route the ownerless narrative fallback through detached dispatch
* 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(memory-core): remove orphaned shadow trials
* refactor(qqbot): migrate direct voice upload formats
Release note: QQBot configs using voiceDirectUploadFormats now migrate through openclaw doctor --fix to audioFormatPolicy.uploadDirectFormats at root and account scope. Existing nested uploadDirectFormats values win conflicts, and runtime reads only the nested policy.
* refactor(feishu): migrate tools base alias
Release note: Feishu configs using tools.base now migrate through openclaw doctor --fix to tools.bitable at root and account scope. Existing bitable values win conflicts, and runtime accepts only the canonical key.
* refactor(extensions): remove dead runtime state
* chore(config): refresh bundled channel metadata
* refactor(codex): keep realtime fallback type private
* test(ci): align QA compatibility scenario count