fix(memory): write dream fallback without subagent runtime (#90121)

This commit is contained in:
AMARA
2026-06-11 01:29:22 +02:00
committed by GitHub
parent 2b89623c50
commit f049477dd4
4 changed files with 72 additions and 10 deletions

View File

@@ -1020,20 +1020,21 @@ describe("generateAndAppendDreamNarrative", () => {
const storePath = path.join(sessionsDir, "sessions.json");
const orphanPath = path.join(sessionsDir, "orphan.jsonl");
const livePath = path.join(sessionsDir, "still-live.jsonl");
const updatedAt = Date.now();
await sessionStoreRuntimeModule.saveSessionStore(
storePath,
{
"agent:main:dreaming-narrative-light-1": {
sessionId: "missing",
updatedAt: Date.now(),
updatedAt,
},
"agent:main:kept-session": {
sessionId: "still-live",
updatedAt: Date.now(),
updatedAt,
},
"agent:main:telegram:group:dreaming-narrative-room": {
sessionId: "still-missing-non-dreaming",
updatedAt: Date.now(),
updatedAt,
},
},
{ skipMaintenance: true },
@@ -1090,20 +1091,21 @@ describe("generateAndAppendDreamNarrative", () => {
// A second dreaming row whose transcript is fresh (a live/just-started run)
// must be preserved.
const liveTranscript = path.join(sessionsDir, "live-dreaming.jsonl");
const updatedAt = Date.now();
await sessionStoreRuntimeModule.saveSessionStore(
storePath,
{
"agent:main:dreaming-narrative-deep-orphan": {
sessionId: "orphan-dreaming",
updatedAt: Date.now(),
updatedAt,
},
"agent:main:dreaming-narrative-deep-live": {
sessionId: "live-dreaming",
updatedAt: Date.now(),
updatedAt,
},
"agent:main:kept-session": {
sessionId: "still-live",
updatedAt: Date.now(),
updatedAt,
},
},
{ skipMaintenance: true },

View File

@@ -152,7 +152,7 @@ function buildRequestScopedFallbackNarrative(_data: NarrativePhaseData): string
return "A memory trace surfaced, but details were unavailable in this run.";
}
async function appendFallbackNarrativeEntry(params: {
export async function appendFallbackNarrativeEntry(params: {
workspaceDir: string;
data: NarrativePhaseData;
nowMs: number;

View File

@@ -2341,6 +2341,57 @@ describe("short-term dreaming trigger", () => {
expect(memoryText).toContain("Move backups to S3 Glacier.");
});
it("writes fallback dream diary prose when managed cron has no subagent runtime", async () => {
const logger = createLogger();
const workspaceDir = await createTempWorkspace("memory-dreaming-cron-no-subagent-");
await writeDailyMemoryNote(workspaceDir, "2026-04-02", ["Move backups to S3 Glacier."]);
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: "cron",
workspaceDir,
config: {
enabled: true,
cron: constants.DEFAULT_DREAMING_CRON_EXPR,
limit: 10,
minScore: 0,
minRecallCount: 0,
minUniqueQueries: 0,
recencyHalfLifeDays: constants.DEFAULT_DREAMING_RECENCY_HALF_LIFE_DAYS,
verboseLogging: false,
},
logger,
});
expect(result?.handled).toBe(true);
const memoryText = await fs.readFile(path.join(workspaceDir, "MEMORY.md"), "utf-8");
expect(memoryText).toContain("Move backups to S3 Glacier.");
const dreamsText = await fs.readFile(path.join(workspaceDir, "DREAMS.md"), "utf-8");
expect(dreamsText).toContain("<!-- openclaw:dreaming:diary:start -->");
expect(dreamsText).toContain(
"A memory trace surfaced, but details were unavailable in this run.",
);
expect(dreamsText).not.toContain("Move backups to S3 Glacier.");
expect(logger.info).toHaveBeenCalledWith(
"memory-core: narrative generation used fallback for deep phase because subagent runtime is unavailable.",
);
});
it("keeps one-off recalls out of long-term memory under default thresholds", async () => {
const logger = createLogger();
const workspaceDir = await createTempWorkspace("memory-dreaming-strict-");

View File

@@ -556,7 +556,7 @@ export async function runShortTermDreamingPromotionIfTriggered(params: {
const detachNarratives = params.trigger === "cron";
const [
{ writeDeepDreamingReport },
{ generateAndAppendDreamNarrative, runDetachedDreamNarrative },
{ appendFallbackNarrativeEntry, generateAndAppendDreamNarrative, runDetachedDreamNarrative },
{ runDreamingSweepPhases },
{
applyShortTermPromotions,
@@ -652,13 +652,22 @@ export async function runShortTermDreamingPromotionIfTriggered(params: {
storage: params.config.storage ?? { mode: "separate", separateReports: false },
});
// Generate dream diary narrative from promoted memories.
if (params.subagent && (candidates.length > 0 || applied.applied > 0)) {
if (candidates.length > 0 || applied.applied > 0) {
const data: NarrativePhaseData = {
phase: "deep",
snippets: candidates.map((c) => c.snippet).filter(Boolean),
promotions: applied.appliedCandidates.map((c) => c.snippet).filter(Boolean),
};
if (detachNarratives) {
if (!params.subagent) {
await appendFallbackNarrativeEntry({
workspaceDir,
data,
nowMs: sweepNowMs,
timezone: params.config.timezone,
logger: params.logger,
reason: "subagent runtime is unavailable",
});
} else if (detachNarratives) {
runDetachedDreamNarrative({
subagent: params.subagent,
workspaceDir,