feat(memory-core): add dreaming verbose logging

This commit is contained in:
Peter Steinberger
2026-04-05 15:10:53 +01:00
parent bcd0a492a4
commit 7ff7a27f61
10 changed files with 158 additions and 12 deletions

View File

@@ -1,4 +1,4 @@
1dc927cd4be5a0ef6e17958a53ceb6df155107ca8100cdb4d417003483f17990 config-baseline.json
b1a181b667568b5860a80945837d544fdec4f946fba34e871936ce0cd3eb689b config-baseline.core.json
ea126fa950fe65c4f7be68c92ff06f3a2256dfe1e770c8643c93b132528bb217 config-baseline.json
587eb0dde83443aa49d743010d224cdc2d7bb6c9d21c3c4effae44f5a06913c5 config-baseline.core.json
3c999707b167138de34f6255e3488b99e404c5132d3fc5879a1fa12d815c31f5 config-baseline.channel.json
fcf32a00815f392ceda9195b8c2af82ae7e88da333feaacee9296f7d5921e73f config-baseline.plugin.json
31a7d5fd79cb3591a6469c13e5ab42010f8c54e9c1f74da0aad4a1629969085d config-baseline.plugin.json

View File

@@ -133,3 +133,4 @@ Notes:
- If effectively active memory remote API key fields are configured as SecretRefs, the command resolves those values from the active gateway snapshot. If gateway is unavailable, the command fails fast.
- Gateway version skew note: this command path requires a gateway that supports `secrets.resolve`; older gateways return an unknown-method error.
- Dreaming cadence defaults to each mode's preset schedule. Override cadence with `plugins.entries.memory-core.config.dreaming.frequency` as a cron expression (for example `0 3 * * *`) and fine-tune with `timezone`, `limit`, `minScore`, `minRecallCount`, and `minUniqueQueries`.
- Set `plugins.entries.memory-core.config.dreaming.verboseLogging` to `true` to emit per-run candidate and apply details into the normal gateway logs while tuning the feature.

View File

@@ -82,6 +82,7 @@ You can still tune behavior with explicit overrides such as:
- `dreaming.minScore`
- `dreaming.minRecallCount`
- `dreaming.minUniqueQueries`
- `dreaming.verboseLogging`
## Configure

View File

@@ -2649,6 +2649,7 @@ See [Local Models](/gateway/local-models). TL;DR: run a large local model via LM
- `minScore`: minimum weighted score threshold for promotion.
- `minRecallCount`: minimum recall count threshold.
- `minUniqueQueries`: minimum distinct query count threshold.
- `verboseLogging`: emit detailed per-run dreaming logs into the normal gateway log stream.
- Enabled Claude bundle plugins can also contribute embedded Pi defaults from `settings.json`; OpenClaw applies those as sanitized agent settings, not as raw OpenClaw config patches.
- `plugins.slots.memory`: pick the active memory plugin id, or `"none"` to disable memory plugins.
- `plugins.slots.contextEngine`: pick the active context engine plugin id; defaults to `"legacy"` unless you install and select another engine.

View File

@@ -379,15 +379,16 @@ Dreaming is configured under `plugins.entries.memory-core.config.dreaming`,
not under `agents.defaults.memorySearch`. For conceptual details and chat
commands, see [Dreaming](/concepts/memory-dreaming).
| Key | Type | Default | Description |
| ------------------ | -------- | -------------- | ----------------------------------------- |
| `mode` | `string` | `"off"` | Preset: `off`, `core`, `rem`, or `deep` |
| `cron` | `string` | preset default | Cron expression override for the schedule |
| `timezone` | `string` | user timezone | Timezone for schedule evaluation |
| `limit` | `number` | preset default | Max candidates to promote per cycle |
| `minScore` | `number` | preset default | Minimum weighted score for promotion |
| `minRecallCount` | `number` | preset default | Minimum recall count threshold |
| `minUniqueQueries` | `number` | preset default | Minimum distinct query count threshold |
| Key | Type | Default | Description |
| ------------------ | --------- | -------------- | ----------------------------------------- |
| `mode` | `string` | `"off"` | Preset: `off`, `core`, `rem`, or `deep` |
| `cron` | `string` | preset default | Cron expression override for the schedule |
| `timezone` | `string` | user timezone | Timezone for schedule evaluation |
| `limit` | `number` | preset default | Max candidates to promote per cycle |
| `minScore` | `number` | preset default | Minimum weighted score for promotion |
| `minRecallCount` | `number` | preset default | Minimum recall count threshold |
| `minUniqueQueries` | `number` | preset default | Minimum distinct query count threshold |
| `verboseLogging` | `boolean` | `false` | Emit detailed per-run dreaming logs |
### Preset defaults

View File

@@ -41,6 +41,11 @@
"label": "Promotion Min Queries",
"placeholder": "2",
"help": "Minimum unique query count required for automatic promotion."
},
"dreaming.verboseLogging": {
"label": "Dreaming Verbose Logging",
"placeholder": "false",
"help": "Emit detailed dreaming-run logs for candidate ranking and promotion decisions."
}
},
"configSchema": {
@@ -80,6 +85,9 @@
"minUniqueQueries": {
"type": "number",
"minimum": 0
},
"verboseLogging": {
"type": "boolean"
}
},
"required": ["mode"]

View File

@@ -132,6 +132,7 @@ describe("memory-core /dreaming command", () => {
expect(result.text).toContain("Dreaming status:");
expect(result.text).toContain("- mode: deep");
expect(result.text).toContain("- cadence: 0 */12 * * * (America/Los_Angeles)");
expect(result.text).toContain("- verboseLogging: off");
expect(runtime.config.writeConfigFile).not.toHaveBeenCalled();
});

View File

@@ -107,6 +107,7 @@ function formatStatus(cfg: OpenClawConfig): string {
`- cadence: ${cadence}${timezone}`,
`- limit: ${resolved.limit}`,
`- thresholds: minScore=${resolved.minScore}, minRecallCount=${resolved.minRecallCount}, minUniqueQueries=${resolved.minUniqueQueries}`,
`- verboseLogging: ${resolved.verboseLogging ? "on" : "off"}`,
].join("\n");
}

View File

@@ -117,6 +117,7 @@ describe("short-term dreaming config", () => {
minScore: constants.DEFAULT_DREAMING_MIN_SCORE,
minRecallCount: constants.DEFAULT_DREAMING_MIN_RECALL_COUNT,
minUniqueQueries: constants.DEFAULT_DREAMING_MIN_UNIQUE_QUERIES,
verboseLogging: false,
});
});
@@ -131,6 +132,7 @@ describe("short-term dreaming config", () => {
minScore: 0.4,
minRecallCount: 2,
minUniqueQueries: 3,
verboseLogging: true,
},
},
});
@@ -142,6 +144,7 @@ describe("short-term dreaming config", () => {
minScore: 0.4,
minRecallCount: 2,
minUniqueQueries: 3,
verboseLogging: true,
});
});
@@ -165,6 +168,7 @@ describe("short-term dreaming config", () => {
minScore: 0.6,
minRecallCount: 2,
minUniqueQueries: 3,
verboseLogging: false,
});
});
@@ -187,6 +191,7 @@ describe("short-term dreaming config", () => {
minScore: constants.DREAMING_PRESET_DEFAULTS.deep.minScore,
minRecallCount: constants.DREAMING_PRESET_DEFAULTS.deep.minRecallCount,
minUniqueQueries: constants.DREAMING_PRESET_DEFAULTS.deep.minUniqueQueries,
verboseLogging: false,
});
});
@@ -202,6 +207,28 @@ describe("short-term dreaming config", () => {
expect(resolved.limit).toBe(0);
});
it("accepts verboseLogging as a boolean or boolean string", () => {
const enabled = resolveShortTermPromotionDreamingConfig({
pluginConfig: {
dreaming: {
mode: "core",
verboseLogging: true,
},
},
});
const disabled = resolveShortTermPromotionDreamingConfig({
pluginConfig: {
dreaming: {
mode: "core",
verboseLogging: "false",
},
},
});
expect(enabled.verboseLogging).toBe(true);
expect(disabled.verboseLogging).toBe(false);
});
it("falls back to defaults when thresholds are negative", () => {
const resolved = resolveShortTermPromotionDreamingConfig({
pluginConfig: {
@@ -263,6 +290,7 @@ describe("short-term dreaming cron reconciliation", () => {
minScore: 0.5,
minRecallCount: 4,
minUniqueQueries: 5,
verboseLogging: false,
},
logger,
});
@@ -294,6 +322,7 @@ describe("short-term dreaming cron reconciliation", () => {
minScore: constants.DEFAULT_DREAMING_MIN_SCORE,
minRecallCount: constants.DEFAULT_DREAMING_MIN_RECALL_COUNT,
minUniqueQueries: constants.DEFAULT_DREAMING_MIN_UNIQUE_QUERIES,
verboseLogging: false,
} as const;
const desired = __testing.buildManagedDreamingCronJob(desiredConfig);
const stalePrimary: CronJobLike = {
@@ -384,6 +413,7 @@ describe("short-term dreaming cron reconciliation", () => {
minScore: constants.DEFAULT_DREAMING_MIN_SCORE,
minRecallCount: constants.DEFAULT_DREAMING_MIN_RECALL_COUNT,
minUniqueQueries: constants.DEFAULT_DREAMING_MIN_UNIQUE_QUERIES,
verboseLogging: false,
},
logger,
});
@@ -417,6 +447,7 @@ describe("short-term dreaming cron reconciliation", () => {
minScore: constants.DEFAULT_DREAMING_MIN_SCORE,
minRecallCount: constants.DEFAULT_DREAMING_MIN_RECALL_COUNT,
minUniqueQueries: constants.DEFAULT_DREAMING_MIN_UNIQUE_QUERIES,
verboseLogging: false,
},
logger,
});
@@ -449,6 +480,7 @@ describe("short-term dreaming cron reconciliation", () => {
minScore: constants.DEFAULT_DREAMING_MIN_SCORE,
minRecallCount: constants.DEFAULT_DREAMING_MIN_RECALL_COUNT,
minUniqueQueries: constants.DEFAULT_DREAMING_MIN_UNIQUE_QUERIES,
verboseLogging: false,
},
logger,
});
@@ -499,6 +531,7 @@ describe("short-term dreaming trigger", () => {
minScore: 0,
minRecallCount: 0,
minUniqueQueries: 0,
verboseLogging: false,
},
logger,
});
@@ -539,6 +572,7 @@ describe("short-term dreaming trigger", () => {
minScore: constants.DEFAULT_DREAMING_MIN_SCORE,
minRecallCount: constants.DEFAULT_DREAMING_MIN_RECALL_COUNT,
minUniqueQueries: constants.DEFAULT_DREAMING_MIN_UNIQUE_QUERIES,
verboseLogging: false,
},
logger,
});
@@ -568,6 +602,7 @@ describe("short-term dreaming trigger", () => {
minScore: 0,
minRecallCount: 0,
minUniqueQueries: 0,
verboseLogging: false,
},
logger,
});
@@ -590,6 +625,7 @@ describe("short-term dreaming trigger", () => {
minScore: 0,
minRecallCount: 0,
minUniqueQueries: 0,
verboseLogging: false,
},
logger,
});
@@ -654,6 +690,7 @@ describe("short-term dreaming trigger", () => {
minScore: 0,
minRecallCount: 0,
minUniqueQueries: 0,
verboseLogging: false,
},
logger,
});
@@ -680,4 +717,52 @@ describe("short-term dreaming trigger", () => {
expect.arrayContaining(["glacier", "router", "failover"]),
);
});
it("emits detailed run logs when verboseLogging is enabled", async () => {
const logger = createLogger();
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "memory-dreaming-verbose-"));
tempDirs.push(workspaceDir);
await recordShortTermRecalls({
workspaceDir,
query: "backup policy",
results: [
{
path: "memory/2026-04-02.md",
startLine: 1,
endLine: 1,
score: 0.9,
snippet: "Move backups to S3 Glacier.",
source: "memory",
},
],
});
const result = await runShortTermDreamingPromotionIfTriggered({
cleanedBody: constants.DREAMING_SYSTEM_EVENT_TEXT,
trigger: "heartbeat",
workspaceDir,
config: {
enabled: true,
cron: constants.DEFAULT_DREAMING_CRON_EXPR,
limit: 10,
minScore: 0,
minRecallCount: 0,
minUniqueQueries: 0,
verboseLogging: true,
},
logger,
});
expect(result?.handled).toBe(true);
expect(logger.info).toHaveBeenCalledWith(
expect.stringContaining("memory-core: dreaming verbose enabled"),
);
expect(logger.info).toHaveBeenCalledWith(
expect.stringContaining("memory-core: dreaming candidate details"),
);
expect(logger.info).toHaveBeenCalledWith(
expect.stringContaining("memory-core: dreaming applied details"),
);
});
});

View File

@@ -113,6 +113,7 @@ export type ShortTermPromotionDreamingConfig = {
minScore: number;
minRecallCount: number;
minUniqueQueries: number;
verboseLogging: boolean;
};
type ReconcileResult =
@@ -179,6 +180,22 @@ function normalizeScore(value: unknown, fallback: number): number {
return num;
}
function normalizeBoolean(value: unknown, fallback: boolean): boolean {
if (typeof value === "boolean") {
return value;
}
if (typeof value === "string") {
const normalized = value.trim().toLowerCase();
if (normalized === "true") {
return true;
}
if (normalized === "false") {
return false;
}
}
return fallback;
}
function formatErrorMessage(err: unknown): string {
if (err instanceof Error) {
return err.message;
@@ -370,6 +387,7 @@ export function resolveShortTermPromotionDreamingConfig(params: {
minScore,
minRecallCount,
minUniqueQueries,
verboseLogging: normalizeBoolean(dreaming?.verboseLogging, false),
};
}
@@ -471,6 +489,11 @@ export async function runShortTermDreamingPromotionIfTriggered(params: {
}
try {
if (params.config.verboseLogging) {
params.logger.info(
`memory-core: dreaming verbose enabled (cron=${params.config.cron}, limit=${params.config.limit}, minScore=${params.config.minScore.toFixed(3)}, minRecallCount=${params.config.minRecallCount}, minUniqueQueries=${params.config.minUniqueQueries}).`,
);
}
const repair = await repairShortTermPromotionArtifacts({ workspaceDir });
if (repair.changed) {
params.logger.info(
@@ -484,6 +507,18 @@ export async function runShortTermDreamingPromotionIfTriggered(params: {
minRecallCount: params.config.minRecallCount,
minUniqueQueries: params.config.minUniqueQueries,
});
if (params.config.verboseLogging) {
const candidateSummary =
candidates.length > 0
? candidates
.map(
(candidate) =>
`${candidate.path}:${candidate.startLine}-${candidate.endLine} score=${candidate.score.toFixed(3)} recalls=${candidate.recallCount} queries=${candidate.uniqueQueries} components={freq=${candidate.components.frequency.toFixed(3)},rel=${candidate.components.relevance.toFixed(3)},div=${candidate.components.diversity.toFixed(3)},rec=${candidate.components.recency.toFixed(3)},cons=${candidate.components.consolidation.toFixed(3)},concept=${candidate.components.conceptual.toFixed(3)}}`,
)
.join(" | ")
: "none";
params.logger.info(`memory-core: dreaming candidate details ${candidateSummary}`);
}
const applied = await applyShortTermPromotions({
workspaceDir,
candidates,
@@ -492,6 +527,18 @@ export async function runShortTermDreamingPromotionIfTriggered(params: {
minRecallCount: params.config.minRecallCount,
minUniqueQueries: params.config.minUniqueQueries,
});
if (params.config.verboseLogging) {
const appliedSummary =
applied.appliedCandidates.length > 0
? applied.appliedCandidates
.map(
(candidate) =>
`${candidate.path}:${candidate.startLine}-${candidate.endLine} score=${candidate.score.toFixed(3)} recalls=${candidate.recallCount}`,
)
.join(" | ")
: "none";
params.logger.info(`memory-core: dreaming applied details ${appliedSummary}`);
}
params.logger.info(
`memory-core: dreaming promotion complete (candidates=${candidates.length}, applied=${applied.applied}).`,
);