The remember tool rewrote the bot's whole MEMORY.md with each call, so every
new fact erased the earlier ones unless the model re-sent the full document.
- MemoryCommitRequest gains mode "append" (the store joins the note onto the
current content inside the same serializable transaction and still writes a
revision row) and every commit is capped at 16 KB per document.
- remember appends a dated bullet ("- YYYY-MM-DD: ...", the user's local date)
by default and rewrites only with mode "replace"; a full document returns an
error that tells the bot to condense it. Retries of the same tool call stay
single appends through the existing effect record.
- Scripted runtime memory entries can append too; the scripted "remember"
script now appends instead of overwriting the bot's memory.
- When memory does not fit the run's 32 KB budget, the prompt keeps a
document's newest lines instead of its oldest.
- memory.update maps an oversized save to BAD_REQUEST.
- docs/PRODUCT.md records that UserFact is the cross-space user model and the
space MEMORY.md stays space notes (no migration).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
65 lines
2.1 KiB
TypeScript
65 lines
2.1 KiB
TypeScript
import type { AdapterContext } from "@rakazo/adapter-kit";
|
|
import { describe, expect, it, vi } from "vitest";
|
|
import { appendMarkdown, MarkdownMemoryStore } from "./index.js";
|
|
|
|
const context: AdapterContext = {
|
|
operationId: "read-memory",
|
|
traceId: "read-memory",
|
|
spaceId: "workspace-1",
|
|
userId: "user-1",
|
|
signal: new AbortController().signal,
|
|
};
|
|
|
|
describe("memory store contract shape", () => {
|
|
it("declares markdown portability", () => {
|
|
const store = new MarkdownMemoryStore({} as never);
|
|
expect(store.describe().capabilities.markdownPortable).toBe(true);
|
|
});
|
|
|
|
it("reads the most recently updated documents first", async () => {
|
|
const updatedAt = new Date("2026-08-16T10:00:00.000Z");
|
|
const findMany = vi
|
|
.fn()
|
|
.mockResolvedValue([
|
|
{ id: "memory-1", path: "facts.md", content: "A fact", revision: 3, updatedAt },
|
|
]);
|
|
const store = new MarkdownMemoryStore({ memoryDocument: { findMany } } as never);
|
|
|
|
await expect(store.read({ scope: "bot", botId: "bot-1" }, context)).resolves.toEqual({
|
|
documents: [
|
|
{
|
|
id: "memory-1",
|
|
path: "facts.md",
|
|
content: "A fact",
|
|
revision: 3,
|
|
updatedAt: updatedAt.toISOString(),
|
|
},
|
|
],
|
|
});
|
|
expect(findMany).toHaveBeenCalledWith({
|
|
where: {
|
|
spaceId: "workspace-1",
|
|
userId: "user-1",
|
|
scope: "bot",
|
|
botId: "bot-1",
|
|
},
|
|
orderBy: [{ updatedAt: "desc" }, { path: "asc" }],
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("appendMarkdown", () => {
|
|
it("adds the note below a heading or paragraph, and right below a list", () => {
|
|
expect(appendMarkdown("# Chief\n\n", "- 2026-10-03: Prefers tea")).toBe(
|
|
"# Chief\n\n- 2026-10-03: Prefers tea\n",
|
|
);
|
|
expect(appendMarkdown("- a\n- b", "- c")).toBe("- a\n- b\n- c\n");
|
|
expect(appendMarkdown("- a\n more about a\n", "- c")).toBe("- a\n more about a\n- c\n");
|
|
expect(appendMarkdown("", "- first")).toBe("- first\n");
|
|
});
|
|
|
|
it("leaves the document unchanged for an empty note", () => {
|
|
expect(appendMarkdown("# Chief\n", " ")).toBe("# Chief\n");
|
|
});
|
|
});
|