feat: add sleepMemory implementation
This commit is contained in:
26
prompts/memoir.md
Normal file
26
prompts/memoir.md
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
You are the persona described in the **Personality** section below, writing a private journal entry at the end of the day. The entry is for your own recollection tomorrow morning. It is not addressed to anyone else.
|
||||||
|
|
||||||
|
### INPUT FORMAT
|
||||||
|
|
||||||
|
You will receive a single message containing the following labeled sections:
|
||||||
|
|
||||||
|
- **Personality:** The character's full psychological operating system, first-person. Voice, vocabulary, temperament, values, anxieties, what they pay attention to.
|
||||||
|
- **Date:** A `YYYY-MM-DD` for the day being recorded.
|
||||||
|
- **Conversation log:** The full message history for that day, formatted as `{persona name}@{timestamp}: message` per line. The persona is referred to by their name; the other party is referred to as `사용자` (or by name if it appears in the personality/history).
|
||||||
|
|
||||||
|
### TASK
|
||||||
|
|
||||||
|
Write a short memoir passage — one to three short paragraphs — about the persona's day, captured entirely from the conversation log above.
|
||||||
|
|
||||||
|
- The persona is referred to in **third person** by their name. The entry is a recollection, not a present-tense chat reply.
|
||||||
|
- Do **not** invent facts, events, locations, or feelings that are not present in the conversation log. If a topic did not come up, it does not appear.
|
||||||
|
- Capture the emotional weather, what mattered, what was unresolved — not a play-by-play transcript. Do not paraphrase message-for-message.
|
||||||
|
- Filter every sentence through the persona's documented voice and temperament. The entry should sound like that person would sound writing privately about their own day.
|
||||||
|
- Plain prose. No bullet points, no numbered lists, no markdown headers, no JSON.
|
||||||
|
|
||||||
|
### ABSOLUTE RULES
|
||||||
|
|
||||||
|
1. Only facts present in the conversation log may appear. Silence on a topic is silence in the entry.
|
||||||
|
2. The user is referred to as `사용자` or by name if their name is given. Never as `the user`.
|
||||||
|
3. The persona is never referred to in first person. No `나`, `저`, `내`, `제`, `I`, `me`, `my`.
|
||||||
|
4. Output prose only.
|
||||||
@@ -1072,3 +1072,98 @@ describe("Brain.sendMessage — tool-calling flow", () => {
|
|||||||
expect(hits[0]!.content).toContain("피자");
|
expect(hits[0]!.content).toContain("피자");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("Brain.sleepMemory", () => {
|
||||||
|
test("SJM1: persists a daily-journal document and returns the LLM text", async () => {
|
||||||
|
const brain = await makeBrain();
|
||||||
|
const db = brain.db as unknown as MockSupermemory;
|
||||||
|
const datetime = new Date(2026, 5, 10, 23, 0, 0);
|
||||||
|
const dateKey = formatDateKey(datetime);
|
||||||
|
const history = [
|
||||||
|
{
|
||||||
|
sender: "user" as const,
|
||||||
|
time: new Date(2026, 5, 10, 9, 0, 0),
|
||||||
|
content: "오늘 뭐 했어?",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sender: "persona" as const,
|
||||||
|
time: new Date(2026, 5, 10, 9, 0, 30),
|
||||||
|
content: "그냥 잤어",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = await brain.sleepMemory(datetime, history);
|
||||||
|
|
||||||
|
expect(result).toBe("test-description");
|
||||||
|
const memoirCall = llmCalls.find(
|
||||||
|
(c) =>
|
||||||
|
c.options.jsonSchemaName === undefined &&
|
||||||
|
typeof c.options.message === "string" &&
|
||||||
|
c.options.message.includes("Conversation log:"),
|
||||||
|
);
|
||||||
|
expect(memoirCall).toBeDefined();
|
||||||
|
expect(memoirCall!.options.message).toContain(`Date: ${dateKey}`);
|
||||||
|
expect(memoirCall!.options.message).toContain("Test personality");
|
||||||
|
expect(memoirCall!.options.message).toContain("오늘 뭐 했어?");
|
||||||
|
expect(memoirCall!.options.message).toContain("그냥 잤어");
|
||||||
|
expect(memoirCall!.options.message).toContain("사용자@");
|
||||||
|
|
||||||
|
const stored = db.findByCustomId(`daily-journal:${dateKey}`);
|
||||||
|
expect(stored).toBeDefined();
|
||||||
|
expect(stored!.containerTag).toBe(brain.space.name);
|
||||||
|
expect(stored!.content).toBe("test-description");
|
||||||
|
expect(stored!.metadata).toEqual({
|
||||||
|
kind: "daily-journal",
|
||||||
|
source: "sleepMemory",
|
||||||
|
date: dateKey,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("SJM2: empty history returns null without calling the LLM", async () => {
|
||||||
|
const brain = await makeBrain();
|
||||||
|
const datetime = new Date(2026, 5, 10, 23, 0, 0);
|
||||||
|
|
||||||
|
llmCalls.length = 0;
|
||||||
|
const result = await brain.sleepMemory(datetime, []);
|
||||||
|
|
||||||
|
expect(result).toBeNull();
|
||||||
|
expect(llmCalls).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("SJM3: stored daily-journal is readable via brain.get with kind=daily-journal", async () => {
|
||||||
|
const brain = await makeBrain();
|
||||||
|
const datetime = new Date(2026, 5, 10, 23, 0, 0);
|
||||||
|
const dateKey = formatDateKey(datetime);
|
||||||
|
|
||||||
|
await brain.sleepMemory(datetime, [
|
||||||
|
{
|
||||||
|
sender: "user" as const,
|
||||||
|
time: new Date(2026, 5, 10, 9, 0, 0),
|
||||||
|
content: "hi",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const stored = await brain.get(`daily-journal:${dateKey}`);
|
||||||
|
expect(stored).not.toBeNull();
|
||||||
|
expect(stored!.content).toBe("test-description");
|
||||||
|
expect(stored!.metadata?.kind).toBe("daily-journal");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("SJM4: datetime defaults to today when undefined is passed", async () => {
|
||||||
|
const brain = await makeBrain();
|
||||||
|
const db = brain.db as unknown as MockSupermemory;
|
||||||
|
const expectedKey = formatDateKey(new Date());
|
||||||
|
|
||||||
|
const result = await brain.sleepMemory(undefined as unknown as Date, [
|
||||||
|
{
|
||||||
|
sender: "user" as const,
|
||||||
|
time: new Date(),
|
||||||
|
content: "ping",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(result).toBe("test-description");
|
||||||
|
const stored = db.findByCustomId(`daily-journal:${expectedKey}`);
|
||||||
|
expect(stored).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -135,6 +135,49 @@ export class Brain {
|
|||||||
return await runCreateMonthlyScheduleSteps(this, datetime, message, noopRunner);
|
return await runCreateMonthlyScheduleSteps(this, datetime, message, noopRunner);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async sleepMemory(
|
||||||
|
datetime: Date = new Date(),
|
||||||
|
history: ReadonlyArray<MessageHistoryEntry>,
|
||||||
|
): Promise<string | null> {
|
||||||
|
if (history.length === 0) return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const dateKey = formatDateKey(datetime);
|
||||||
|
const instruction = await loadPrompt("MEMOIR");
|
||||||
|
const historyBlock = translateMessageHistory(
|
||||||
|
this.brainbase.displayName,
|
||||||
|
history,
|
||||||
|
);
|
||||||
|
const promptMessage = [
|
||||||
|
`Date: ${dateKey}`,
|
||||||
|
`Personality: ${this.brainbase.baseSystemPrompt}`,
|
||||||
|
`Conversation log:`,
|
||||||
|
historyBlock,
|
||||||
|
].join("\n\n");
|
||||||
|
|
||||||
|
const memoir = await llm.call<string>(llm.models.identity, {
|
||||||
|
instruction,
|
||||||
|
message: promptMessage,
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.add({
|
||||||
|
customId: `daily-journal:${dateKey}`,
|
||||||
|
content: memoir,
|
||||||
|
metadata: {
|
||||||
|
kind: "daily-journal",
|
||||||
|
source: "sleepMemory",
|
||||||
|
date: dateKey,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return memoir;
|
||||||
|
} catch (error) {
|
||||||
|
const reason = error instanceof Error ? error.message : String(error);
|
||||||
|
logger.error(`sleepMemory failed: ${reason}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async getTodayScheduledAvailability(
|
async getTodayScheduledAvailability(
|
||||||
datetime: Date,
|
datetime: Date,
|
||||||
): Promise<AvailabilityWindows | null> {
|
): Promise<AvailabilityWindows | null> {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ const prompts = [
|
|||||||
"OBJECTIFIER",
|
"OBJECTIFIER",
|
||||||
"SEND_MESSAGE",
|
"SEND_MESSAGE",
|
||||||
"START_CONVERSATION",
|
"START_CONVERSATION",
|
||||||
|
"MEMOIR",
|
||||||
] as const;
|
] as const;
|
||||||
export type PromptKey = (typeof prompts)[number];
|
export type PromptKey = (typeof prompts)[number];
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user