From 7b628a4ce2530014b2d34007464f926da3ab222c Mon Sep 17 00:00:00 2001 From: p-sw Date: Wed, 24 Jun 2026 23:12:27 +0900 Subject: [PATCH] fix: add early return for existing schedule --- src/brain/index.test.ts | 112 ++++++++++++++++++++++++++++++++++++++++ src/brain/index.ts | 28 +++++++++- 2 files changed, 138 insertions(+), 2 deletions(-) diff --git a/src/brain/index.test.ts b/src/brain/index.test.ts index 6aeec3f..0784c9f 100644 --- a/src/brain/index.test.ts +++ b/src/brain/index.test.ts @@ -558,6 +558,118 @@ describe("Brain.createMonthlySchedule", () => { }); }); +describe("Brain.createDailySchedule early return", () => { + test("returns existing schedule without calling LLM when daily-schedule for tomorrow already exists", async () => { + const brain = await makeBrain(); + const today = new Date(2026, 5, 9); + const tomorrow = (await import("./schedule")).nextDay(today); + const tomorrowKey = formatDateKey(tomorrow); + const preseeded = { + items: [ + { + start: "06:00", + end: "07:00", + activity: "preserved-morning", + notes: "n/a", + }, + { + start: "22:00", + end: "23:00", + activity: "preserved-evening", + notes: "n/a", + }, + ], + }; + await brain.add({ + customId: `daily-schedule:${tomorrowKey}`, + content: JSON.stringify(preseeded), + metadata: { kind: "schedule", source: "test-seed", date: tomorrowKey }, + }); + + llmCalls.length = 0; + const result = await brain.createDailySchedule(today, "ignored user message"); + + expect(result).toEqual(preseeded); + const dailyLlmCall = llmCalls.find( + (c) => c.options.jsonSchemaName === "daily-schedule", + ); + expect(dailyLlmCall).toBeUndefined(); + }); + + test("falls through to generation when stored content is malformed", async () => { + const brain = await makeBrain(); + const db = brain.db as unknown as MockSupermemory; + const today = new Date(2026, 5, 9); + const tomorrow = (await import("./schedule")).nextDay(today); + const tomorrowKey = formatDateKey(tomorrow); + await brain.add({ + customId: `daily-schedule:${tomorrowKey}`, + content: "{not valid json", + metadata: { kind: "schedule", source: "test-seed", date: tomorrowKey }, + }); + + const result = await brain.createDailySchedule(today, ""); + + expect(result).not.toBeNull(); + expect(result!.items).toHaveLength(48); + const dailyLlmCall = llmCalls.find( + (c) => c.options.jsonSchemaName === "daily-schedule", + ); + expect(dailyLlmCall).toBeDefined(); + expect(db.findByCustomId(`daily-schedule:${tomorrowKey}`)).toBeDefined(); + }); +}); + +describe("Brain.createMonthlySchedule early return", () => { + test("returns existing schedule without calling LLM when monthly-schedule for next month already exists", async () => { + const brain = await makeBrain(); + const today = new Date(2026, 0, 15); + const next = nextMonth(today); + const monthKey = `${next.year}-${String(next.month + 1).padStart(2, "0")}`; + const preseeded = { + items: [ + { day: 1, summary: "preserved-day-1" }, + { day: 2, summary: "preserved-day-2" }, + { day: 3, summary: "preserved-day-3" }, + ], + }; + await brain.add({ + customId: `monthly-schedule:${monthKey}`, + content: JSON.stringify(preseeded), + metadata: { kind: "schedule", source: "test-seed", month: monthKey }, + }); + + llmCalls.length = 0; + const result = await brain.createMonthlySchedule(today, "ignored user message"); + + expect(result).toEqual(preseeded); + const monthlyLlmCall = llmCalls.find( + (c) => c.options.jsonSchemaName === "monthly-schedule", + ); + expect(monthlyLlmCall).toBeUndefined(); + }); + + test("falls through to generation when stored content is malformed", async () => { + const brain = await makeBrain(); + const today = new Date(2026, 0, 15); + const next = nextMonth(today); + const monthKey = `${next.year}-${String(next.month + 1).padStart(2, "0")}`; + await brain.add({ + customId: `monthly-schedule:${monthKey}`, + content: "{not valid json", + metadata: { kind: "schedule", source: "test-seed", month: monthKey }, + }); + + const result = await brain.createMonthlySchedule(today, ""); + + expect(result).not.toBeNull(); + const monthlyLlmCall = llmCalls.find( + (c) => c.options.jsonSchemaName === "monthly-schedule", + ); + expect(monthlyLlmCall).toBeDefined(); + }); +}); + describe("Brain.getTodayScheduledAvailability", () => { test("S3: returns availability windows when today's daily schedule exists", async () => { const brain = await makeBrain(); diff --git a/src/brain/index.ts b/src/brain/index.ts index 810aec5..5efd5a4 100644 --- a/src/brain/index.ts +++ b/src/brain/index.ts @@ -495,9 +495,21 @@ export async function runCreateDailyScheduleSteps( runner: StepRunner = noopRunner, ): Promise { try { - runner.start("gathering context"); const target = nextDay(datetime); const dateKey = formatDateKey(target); + const existing = await brain.get(`daily-schedule:${dateKey}`); + if (existing) { + try { + const parsed = JSON.parse(existing.content) as DailySchedule; + runner.start("checking for existing schedule"); + runner.done(`existing schedule found (customId=daily-schedule:${dateKey}), skipping generation`); + return parsed; + } catch { + // fall through to regeneration if stored content is malformed + } + } + + runner.start("gathering context"); const twoDaysAgo = new Date(target); twoDaysAgo.setDate(twoDaysAgo.getDate() - 2); const twoDaysAgoKey = formatDateKey(twoDaysAgo); @@ -577,9 +589,21 @@ export async function runCreateMonthlyScheduleSteps( runner: StepRunner = noopRunner, ): Promise { try { - runner.start("gathering context"); const next = nextMonth(datetime); const monthKey = `${next.year}-${pad2(next.month + 1)}`; + const existing = await brain.get(`monthly-schedule:${monthKey}`); + if (existing) { + try { + const parsed = JSON.parse(existing.content) as MonthlySchedule; + runner.start("checking for existing schedule"); + runner.done(`existing schedule found (customId=monthly-schedule:${monthKey}), skipping generation`); + return parsed; + } catch { + // fall through to regeneration if stored content is malformed + } + } + + runner.start("gathering context"); const twoMonthsAgo = new Date(next.year, next.month - 2, 1); const twoMonthsAgoKey = `${twoMonthsAgo.getFullYear()}-${pad2(twoMonthsAgo.getMonth() + 1)}`; const [history, twoMonthsAgoStored] = await Promise.all([