From 04d2fbb9648170db4b4a13cf908756ba0a1bc8ec Mon Sep 17 00:00:00 2001 From: Rafael Rincon Date: Sat, 12 Sep 2026 21:26:36 -0400 Subject: [PATCH] feat: add durable memory checkpoints --- packages/core/src/okf/knowledge-base.ts | 14 ++++++++++++++ packages/server/src/mcp/server.ts | 16 ++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/packages/core/src/okf/knowledge-base.ts b/packages/core/src/okf/knowledge-base.ts index ce923c57..0b82f5b6 100644 --- a/packages/core/src/okf/knowledge-base.ts +++ b/packages/core/src/okf/knowledge-base.ts @@ -1,4 +1,5 @@ import path from "node:path"; +import { promises as fs } from "node:fs"; import { simpleGit, type SimpleGit } from "simple-git"; import { Bundle } from "./bundle.js"; import { pruneEmptyDirs, regenerateIndexChain } from "./indexer.js"; @@ -59,6 +60,19 @@ export class KnowledgeBase { return readLog(this.bundle); } + async writeCheckpoint(sessionId: string, messages: unknown[]): Promise { + const safe = sessionId.replace(/[^a-zA-Z0-9._-]/g, "_"); + if (!safe) throw new Error("Checkpoint session ID is required"); + const dir = path.join(this.bundle.root, ".checkpoints"); + const target = path.join(dir, `${safe}.json`); + const temp = `${target}.tmp-${process.pid}-${Date.now()}`; + const payload = JSON.stringify({ version: 1, session_id: sessionId, created_at: new Date().toISOString(), messages }); + await fs.mkdir(dir, { recursive: true }); + await fs.writeFile(temp, payload, { encoding: "utf-8", mode: 0o600 }); + await fs.rename(temp, target); + return `/.checkpoints/${safe}.json`; + } + validate(): Promise { return validateBundle(this.bundle); } diff --git a/packages/server/src/mcp/server.ts b/packages/server/src/mcp/server.ts index 9ea0f753..cded41aa 100644 --- a/packages/server/src/mcp/server.ts +++ b/packages/server/src/mcp/server.ts @@ -48,6 +48,22 @@ export async function buildMcpServer(kb: KnowledgeBase): Promise { } ); + server.registerTool( + "memory_checkpoint_save", + { + title: "Save pre-compression checkpoint", + description: "Durably save a session transcript before lossy compression.", + inputSchema: { + session_id: z.string().min(1), + messages: z.array(z.unknown()), + }, + }, + async ({ session_id, messages }) => { + const path = await kb.writeCheckpoint(session_id, messages); + return { content: [{ type: "text", text: JSON.stringify({ ok: true, path }) }] }; + } + ); + /** * Re-derive the seed after a mutation and push it into memory_query's * description; RegisteredTool.update() emits tools/list_changed so