Compare commits

...

2 Commits

Author SHA1 Message Date
Onur Solmaz
0f224121d5 docs: add memory index identity plan 2026-05-29 13:17:46 +08:00
Onur Solmaz
1f0ea6c43b fix(active-memory): raise recall timeout ceiling 2026-05-28 18:16:40 +08:00
6 changed files with 117 additions and 14 deletions

View File

@@ -683,7 +683,7 @@ The most important fields are:
| `config.thinking` | `"off" \| "minimal" \| "low" \| "medium" \| "high" \| "xhigh" \| "adaptive" \| "max"` | Advanced thinking override for the blocking memory sub-agent; default `off` for speed |
| `config.promptOverride` | `string` | Advanced full prompt replacement; not recommended for normal use |
| `config.promptAppend` | `string` | Advanced extra instructions appended to the default or overridden prompt |
| `config.timeoutMs` | `number` | Hard timeout for the blocking memory sub-agent, capped at 120000 ms |
| `config.timeoutMs` | `number` | Hard timeout for the blocking memory sub-agent, capped at 300000 ms. Higher values can delay the visible reply. |
| `config.setupGraceTimeoutMs` | `number` | Advanced extra setup budget before the recall timeout expires; defaults to 0 and is capped at 30000 ms. See [Cold-start grace](#cold-start-grace) for v2026.4.x upgrade guidance |
| `config.maxSummaryChars` | `number` | Maximum total characters allowed in the active-memory summary |
| `config.logging` | `boolean` | Emits active memory logs while tuning |
@@ -800,6 +800,10 @@ If active memory is too slow:
- reduce recent turn counts
- reduce per-turn char caps
For slow local recall models, you can raise `config.timeoutMs` up to `300000`
ms. This is an explicit trade-off: Active Memory may wait longer before the
visible reply starts.
## Common issues
Active Memory rides on the configured memory plugin's recall pipeline, so most

View File

@@ -60,7 +60,7 @@ describe("active-memory manifest config schema", () => {
value: {
enabled: true,
agents: ["main"],
timeoutMs: 120_000,
timeoutMs: 300_000,
},
});
@@ -102,7 +102,7 @@ describe("active-memory manifest config schema", () => {
value: {
enabled: true,
agents: ["main"],
timeoutMs: 120_001,
timeoutMs: 300_001,
},
});

View File

@@ -381,11 +381,11 @@ describe("active-memory plugin", () => {
it("registers before_prompt_build with the configured recall timeout", () => {
api.pluginConfig = {
agents: ["main"],
timeoutMs: 90_000,
timeoutMs: 180_000,
};
plugin.register(api as unknown as OpenClawPluginApi);
expect(hookOptions.before_prompt_build?.timeoutMs).toBe(90_000);
expect(hookOptions.before_prompt_build?.timeoutMs).toBe(180_000);
});
it("registers before_prompt_build with explicit setup grace when configured", () => {
@@ -3231,10 +3231,10 @@ describe("active-memory plugin", () => {
expectLinesToContain(warnLines, "before_prompt_build");
});
it("honors configured timeoutMs values above the former 60 000 ms ceiling", async () => {
it("honors configured timeoutMs values above the former 120 000 ms ceiling", async () => {
api.pluginConfig = {
agents: ["main"],
timeoutMs: 90_000,
timeoutMs: 180_000,
logging: true,
};
plugin.register(api as unknown as OpenClawPluginApi);
@@ -3250,13 +3250,13 @@ describe("active-memory plugin", () => {
);
const passedTimeoutMs = lastEmbeddedRunParams().timeoutMs;
expect(passedTimeoutMs).toBe(90_000);
expect(passedTimeoutMs).toBe(180_000);
});
it("clamps timeoutMs above the 120 000 ms ceiling to the ceiling", async () => {
it("clamps timeoutMs above the 300 000 ms ceiling to the ceiling", async () => {
api.pluginConfig = {
agents: ["main"],
timeoutMs: 200_000,
timeoutMs: 400_000,
logging: true,
};
plugin.register(api as unknown as OpenClawPluginApi);
@@ -3272,7 +3272,7 @@ describe("active-memory plugin", () => {
);
const passedTimeoutMs = lastEmbeddedRunParams().timeoutMs;
expect(passedTimeoutMs).toBe(120_000);
expect(passedTimeoutMs).toBe(300_000);
});
it("sanitizes active-memory log fields onto a single line", async () => {

View File

@@ -39,6 +39,7 @@ const DEFAULT_CACHE_TTL_MS = 15_000;
const DEFAULT_MAX_CACHE_ENTRIES = 1000;
const CACHE_SWEEP_INTERVAL_MS = 1000;
const DEFAULT_MIN_TIMEOUT_MS = 250;
const MAX_ACTIVE_MEMORY_TIMEOUT_MS = 300_000;
const DEFAULT_SETUP_GRACE_TIMEOUT_MS = 0;
const DEFAULT_QUERY_MODE = "recent" as const;
const DEFAULT_QMD_SEARCH_MODE = "search" as const;
@@ -894,7 +895,7 @@ function normalizePluginConfig(
parseOptionalPositiveInt(raw.timeoutMs, DEFAULT_TIMEOUT_MS),
DEFAULT_TIMEOUT_MS,
minimumTimeoutMs,
120_000,
MAX_ACTIVE_MEMORY_TIMEOUT_MS,
),
setupGraceTimeoutMs: clampInt(raw.setupGraceTimeoutMs, setupGraceTimeoutMs, 0, 30_000),
queryMode:

View File

@@ -39,7 +39,7 @@
"type": "string",
"enum": ["off", "minimal", "low", "medium", "high", "xhigh", "adaptive"]
},
"timeoutMs": { "type": "integer", "minimum": 250, "maximum": 120000 },
"timeoutMs": { "type": "integer", "minimum": 250, "maximum": 300000 },
"setupGraceTimeoutMs": { "type": "integer", "minimum": 0, "maximum": 30000 },
"queryMode": {
"type": "string",
@@ -123,7 +123,8 @@
"help": "Optional explicit denylist of chat/user IDs. Sessions whose resolved conversation id matches the list are skipped even when the chat type is allowed. Applied after allowedChatIds."
},
"timeoutMs": {
"label": "Timeout (ms)"
"label": "Timeout (ms)",
"help": "Hard timeout for the blocking memory sub-agent. Default: 15000. Maximum: 300000. Higher values can delay the visible reply."
},
"setupGraceTimeoutMs": {
"label": "Setup Grace Timeout (ms)",

View File

@@ -0,0 +1,97 @@
# Memory Index Identity Fix Plan
Issue: https://github.com/openclaw/openclaw/issues/83333
## Goal
Make a memory index healthy only when its stored identity matches the active memory config and embedding provider.
The current bad state is:
- config now wants `ollama` / `nomic-embed-text`
- SQLite metadata still describes an old OpenAI index
- `memory status --deep` can show `Dirty: no`
- search can silently return no useful vector matches
## Production Invariant
Every path that trusts the memory DB must answer the same question:
> Does this index belong to the active memory provider, model, provider key, sources, scope, chunking, tokenizer, and vector state?
If no, the index is stale. It must be dirty, invalid, or rebuilt. It must not be reported as clean.
## Current Gap
The explicit indexing path already checks provider/model/providerKey mismatch through `shouldRunFullMemoryReindex` in `extensions/memory-core/src/memory/manager-reindex-state.ts`.
The status-only path does not. `MemoryIndexManager` reads `meta.vectorDims`, then `resolveInitialMemoryDirty` treats any existing metadata as clean for status managers:
- `extensions/memory-core/src/memory/manager.ts`
- `extensions/memory-core/src/memory/manager-status-state.ts`
- `extensions/memory-core/src/cli.runtime.ts`
That lets deep status combine new provider info with old index metadata.
## Fix Shape
1. Extract a shared index identity check.
- Reuse the comparison logic behind `shouldRunFullMemoryReindex`.
- Return a structured result: `valid`, `missing`, or `mismatched`.
- Include a short mismatch reason for status output and tests.
2. Use that check when constructing status-only managers.
- Read stored metadata.
- Build expected identity from current settings and initialized provider when available.
- If identity does not match, set `dirty = true`.
3. Make `memory status --deep` validate after provider initialization.
- Deep status probes initialize the provider.
- Re-run the identity check after that point.
- Never print `Dirty: no` when provider/model/providerKey metadata mismatches.
4. Protect search bootstrap.
- Before trusting existing vector rows, validate index identity.
- If stale and sync is allowed, trigger the safe reindex path.
- If stale and sync cannot run, return a degraded/mismatch state instead of silently acting healthy.
5. Keep rebuilds atomic.
- Provider/model identity changes should use the existing safe temp-DB reindex and swap.
- Do not patch mixed-provider rows in place.
## Non-Goals
- No Ollama special case.
- No config migration.
- No plugin API change unless implementation proves memory-core lacks required identity data.
- No hardcoded vector dimension rules such as `1536 means OpenAI`.
## Tests
Add focused tests for:
- status-only manager marks mismatched provider/model metadata dirty
- `memory status --deep` does not report clean after OpenAI metadata with Ollama config
- manual `memory index` still reuses the same identity check
- search does not silently return a healthy empty vector result from a mismatched index
Suggested commands:
```sh
node scripts/run-vitest.mjs \
extensions/memory-core/src/memory/manager-status-state.test.ts \
extensions/memory-core/src/memory/manager-reindex-state.test.ts \
extensions/memory-core/src/memory/manager-search.test.ts \
extensions/memory-core/src/cli.test.ts
```
## Optional Live Proof
If feasible before landing, run a Docker or Crabbox smoke:
1. Build a small OpenAI-style memory index.
2. Switch `agents.defaults.memorySearch` to Ollama.
3. Run `openclaw memory status --deep`.
4. Confirm status is dirty or invalid, not clean with stale dimensions.
Unit proof is enough for the core invariant. Live proof is useful for confidence, not a reason to special-case Ollama.