- stream fallback Memory Core vector scoring with SQLite iterate() and a bounded top-K result set
- add regression coverage and live-main lint/boundary helper repairs
- supersedes #73069
Thanks @parkertoddbrooks.
Allow the memory index suite to exceed the global 120s test timeout when it runs inside a packed extension shard. The scoped Vitest config is reset after the file.
Closes#72837. The 15s narrative-subagent timeout was empirically too
tight for warm-gateway runs across light, REM, and deep phases —
gpt-5.4-mini latency through OpenAI alone routinely brushes 12s+, so the
first sweep after a restart deterministically times out across all three
phases. 60s gives realistic LLM-call headroom while still capping the
worst case at one minute, preserving the original comment's "don't leave
parent cron running for minutes" constraint.
Test: updates the matching toMatchObject assertion in
dreaming-narrative.test.ts from 15_000 to 60_000.
* Manage Codex app-server binary
* Use plugin deps for Codex app-server binary
* Stabilize media model registry test
* Exclude checkpoint transcripts from memory ingestion
* fix(memory-core): add runtime cron service fallback for dreaming reconciliation
When the cron service is unavailable during gateway_start (e.g., due to
a startup timing race or deferred initialization), the startupCronSource
is captured as null and never refreshed. All subsequent runtime
reconciliation attempts fail with 'cron service unavailable', even when
the cron service is fully operational.
This adds a fallback path in the runtime reconciliation that attempts to
obtain the cron service from the plugin API runtime when the startup
capture was null. This handles the case where the cron service becomes
available after the initial startup event.
Fixes#67362
* fix(memory-core): hold gateway context for runtime cron resolution
The previous attempt tried to access api.runtime.cron which doesn't exist
on the PluginRuntime type. The cron service is only accessible through
PluginHookGatewayContext.getCron().
This fix stores the gateway context from the gateway_start event and uses
it to retry cron resolution at runtime when the initial capture was null.
This handles the race condition where the cron service isn't available
during gateway_start (250ms deferred init) but is ready later.
Also refreshes the startupCron capture when the runtime retry succeeds,
so subsequent reconciliation calls resolve immediately.
Addresses review feedback on #71694
Expose raw `vectorScore` and `textScore` alongside the combined hybrid memory search `score`.
- Preserve vector/text component scores from `mergeHybridResults` output.
- Add optional component-score fields to both memory host SDK type surfaces.
- Extend hybrid merge tests for vector-only, text-only, and overlapping result cases.
- Document that component scores remain raw retrieval diagnostics while temporal decay/MMR only adjust or reorder the combined ranking `score`.
Closes#68166.
Maintainer verification:
- `pnpm test extensions/memory-core/src/memory/hybrid.test.ts`
- `pnpm check:changed`
- Fresh GitHub checks passed.
Co-authored-by: Alex Fries <alex@engramlabs.io>
* [EV-001] memory-core: filter memory_search session hits by visibility
- Move session visibility + listSpawnedSessionKeys to plugin-sdk; sync test
hook with sessions-resolution __testing.setDepsForTest
- Extract loadCombinedSessionStoreForGateway to config/sessions; re-export
from gateway session-utils
- Add session-transcript-hit stem resolver for builtin + QMD paths
- Post-filter memory_search results before citations/recall; fail closed when
requester session key missing; optional corpus=sessions
- Tests: stem extraction, visibility filter smoke, existing suites green
* chore: sync plugin-sdk exports for session-transcript-hit and session-visibility
Run pnpm plugin-sdk:sync-exports so package.json exports match
scripts/lib/plugin-sdk-entrypoints.json. Fixes contract tests and
lint:plugins:plugin-sdk-subpaths-exported for memory-core imports.
* fix(EV-001): cross-agent session memory hits + hoist combined store load
- resolveTranscriptStemToSessionKeys: stop filtering by requester agentId so
keys from other agents reach createSessionVisibilityGuard (a2a + visibility=all).
- Re-export loadCombinedSessionStoreForGateway from session-transcript-hit;
filterMemorySearchHitsBySessionVisibility loads the combined store once per pass.
- Drop unused agentId from filter params; extend tests (Greptile/Codex review).
* fix(memory_search): honor corpus=sessions before maxResults cap
Pass sources into MemoryIndexManager.search so FTS/vector queries add
source IN (...) before ranking and top-N slice (Codex: non-session hits
could fill the window).
QMD path: oversample fetch limit for single-source recall, filter by
source, then diversify/clamp to the requested maxResults.
Wire corpus=sessions from tools; extend MemorySearchManager opts and
wrappers.
* fix(memory_search): apply corpus=memory source filter like sessions
Pass sources: ["memory"] into manager.search so maxResults applies only
within the memory index; post-filter for defense in depth. Document
corpus=memory in the tool description.
* fix: scope qmd session memory search
* fix: enforce memory search session visibility (#70761) (thanks @nefainl)
---------
Co-authored-by: NefAI <info@nefai.nl>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* Revert "fix(memory/dreaming): surface blocked status when heartbeat is disabled for main (#69875)"
This reverts commit 529577e045.
Making way for the dreaming-vs-heartbeat decoupling from Josh's
josh/dreaming-isolated-cron-fix branch, which moves the managed dreaming
cron to isolated agent turns (sessionTarget: "isolated") so dreaming no
longer requires heartbeat to fire. Once the cron no longer rides the
heartbeat path, the blocked-reason observability has nothing left to
report — removing it cleanly here before the cherry-picks land.
* openclaw-3ba.1: move managed dreaming cron to isolated agent turns
* openclaw-46d: claim cron runs before embedded attempts
* openclaw-575: disable managed dreaming cron delivery
* openclaw-575: accept wrapped dreaming cron tokens
* openclaw-ccd: filter cron and wrapper transcript noise from dreaming corpus
* openclaw-cd9: filter archived, cron, and heartbeat transcript noise from dreaming corpus
* openclaw-cd9: suppress role-label reflection tags in rem dreaming
* openclaw-b49: stop narrative timeouts from blocking dreaming cron
* openclaw-b49: keep managed dreaming cron out of diary subagents
* openclaw-ff9: restore cron dream diary generation without serial waits
* openclaw-ff9: run dreaming narratives with lightweight isolated subagent lanes
* openclaw-ff9: detach cron dream diary generation from run completion
* openclaw-ff9: defer cron diary task startup until after cron completion
* doctor/cron: migrate stale managed dreaming jobs to isolated agent turns
After the dreaming cron moved off the heartbeat path to sessionTarget:
"isolated" + payload.kind: "agentTurn" (see the preceding memory-core
changes), users with existing ~/.openclaw/cron/jobs.json entries in the
old sessionTarget: "main" + payload.kind: "systemEvent" shape still
carry stale jobs until the gateway restart reconcile rewrites them.
Add a dreaming-specific cron migration to the existing
maybeRepairLegacyCronStore doctor path so "openclaw doctor" (and
"openclaw doctor --fix") rewrites those jobs without needing a gateway
restart. Match lives in a new doctor-cron-dreaming-payload-migration
helper alongside the existing legacy-delivery and store-migration files.
The matching uses the memory-core managed-job name and description tag
plus the short-term-promotion payload token. Constants are mirrored
from extensions/memory-core/src/dreaming.ts and commented so a future
rename in memory-core is a visible drift point here too.
* memory/dreaming: tighten cron-token match to known wrapper, not substring
The previous match relaxed the line check from 'trimmed line equals token'
to 'line contains token anywhere as a substring' to accept the
`[cron:<id>] <token>` wrapper that isolated-cron turns add. Substring
matching also let any user message embedding the token mid-sentence
trigger the dream-promotion hook, and was flagged by both Greptile and
Aisle on PR #70737.
Replace it with strip-the-known-prefix-then-exact-match: keep the
`[cron:<id>]` wrapper case working, reject every other variant. Add
focused unit coverage that the bare token, the wrapped token, and bare
multiline cases match while embedded / code-fenced / arbitrarily-wrapped
variants do not.
* memory/dreaming: drop assistant followup only on assistant-side signals
Per PR #70737 review (aisle-research-bot, Medium): the previous logic
suppressed the next assistant message whenever the prior user message
matched a 'generated prompt' pattern (`[cron:...]`,
`System (untrusted): ...`, heartbeat prompts, exec-completion events).
Real users can type those same patterns, which let a user exfiltrate
real assistant replies from the dreaming corpus by prefixing their own
prompt — the assistant's reply would be silently dropped.
Remove the cross-message coupling. Assistant-side machinery (silent
replies, system wrappers) is already dropped by sanitizeSessionText,
which is the right layer for that filter. Add an explicit assistant-side
HEARTBEAT_TOKEN check to keep the legitimate `HEARTBEAT_OK` ack drop
working without depending on the prior user message. Add a regression
test exercising the spoofing scenario.
* doctor/cron: assert mirrored dreaming constants stay in sync
Per PR #70737 review (greptile-apps): the doctor migration mirrors three
constants (MANAGED_DREAMING_CRON_NAME, MANAGED_DREAMING_CRON_TAG,
DREAMING_SYSTEM_EVENT_TEXT) from extensions/memory-core/src/dreaming.ts.
A future rename in either file would silently break the migration.
Add a vitest unit that reads both files and asserts the literals match.
Manually verified the assertion fires with a clear error when one side
diverges. Adds no runtime cost; sits in the regular test pipeline.
* fix(memory): stabilize dreaming CI checks
* memory/dreaming: skip eager narrative session cleanup when detached
Per PR #70737 review (chatgpt-codex-connector, P2): runDreamingSweepPhases
called deleteNarrativeSessionBestEffort synchronously right after each
phase. Once narrative generation moved to detached mode (queued via
queueMicrotask), the eager cleanup races the writer: the session is
deleted before the queued subagent run reads it, silently dropping cron
diary entries.
Skip the eager cleanup branch when params.detachNarratives is true.
generateAndAppendDreamNarrative still runs its own deleteSession in the
finally{} block, so the cleanup intent is preserved without the race.
Heartbeat-driven (non-detached) runs keep the original eager-cleanup
behavior.
* fix(plugin-sdk): restore heartbeat-summary re-export
Per PR #70737 review (chatgpt-codex-connector, P1): the revert of
PR #69875 dropped the `heartbeat-summary` re-export from
`openclaw/plugin-sdk/infra-runtime`. That subpath shipped publicly two
days earlier, so removing it is technically a breaking change to a
public SDK surface — third-party plugins importing
`isHeartbeatEnabledForAgent` / `resolveHeartbeatIntervalMs` from this
path would fail with no replacement contract introduced.
Restore the re-export. Costs nothing to keep; the helpers are already
public via `../infra/heartbeat-summary.ts`. SDK additions are by
default backwards-compatible (CLAUDE.md), so removing within days of
introduction violates that intent.
* changelog: note dreaming decoupling from heartbeat
Refs PR #70737.
---------
Co-authored-by: Josh Lehman <josh@martian.engineering>