From 2dc4e78ed6c0ed8269dbccabe32bf9cab5ae2132 Mon Sep 17 00:00:00 2001 From: colt Date: Sat, 1 Aug 2026 16:38:17 +0300 Subject: [PATCH 1/2] feat(kilo-memory): route memory audit diagnostics through the DEBUG log stream --- .changeset/memory-audit-log.md | 6 +++ packages/kilo-memory/src/effect/capture.ts | 4 +- packages/kilo-memory/src/effect/log.ts | 9 ++++ packages/kilo-memory/src/storage/audit.ts | 34 ++------------- packages/kilo-memory/test/audit.test.ts | 43 +++++++++++++++++++ .../opencode/src/kilocode/memory/runtime.ts | 1 + 6 files changed, 65 insertions(+), 32 deletions(-) create mode 100644 .changeset/memory-audit-log.md create mode 100644 packages/kilo-memory/test/audit.test.ts diff --git a/.changeset/memory-audit-log.md b/.changeset/memory-audit-log.md new file mode 100644 index 00000000000..542f7e26e66 --- /dev/null +++ b/.changeset/memory-audit-log.md @@ -0,0 +1,6 @@ +--- +"@kilocode/kilo-memory": patch +"@kilocode/cli": patch +--- + +Log memory parse failures (including the full model output) and capture-decision records at DEBUG level. Visible by default in source/dev runs; in release builds set `KILO_LOG_LEVEL=DEBUG` or pass `--logLevel DEBUG`. diff --git a/packages/kilo-memory/src/effect/capture.ts b/packages/kilo-memory/src/effect/capture.ts index fda14a1a19a..1ec9c8a0a16 100644 --- a/packages/kilo-memory/src/effect/capture.ts +++ b/packages/kilo-memory/src/effect/capture.ts @@ -302,7 +302,7 @@ export namespace MemoryCapture { yield* fail("digest parse_error") yield* memory.append({ root, - text: `digest parse_error=${MemoryShared.brief(reason, 160)} fallback=1`, + text: `digest parse_error=${MemoryShared.brief(reason, 160)} full=${result.result.text} fallback=1`, }) return undefined }), @@ -427,7 +427,7 @@ export namespace MemoryCapture { Effect.gen(function* () { const reason = MemoryRedact.text(errorReason(err)) yield* fail("consolidate parse_error") - yield* memory.append({ root, text: `consolidate parse_error=${MemoryShared.brief(reason, 160)}` }) + yield* memory.append({ root, text: `consolidate parse_error=${MemoryShared.brief(reason, 160)} full=${result.result.text}` }) return undefined }), ), diff --git a/packages/kilo-memory/src/effect/log.ts b/packages/kilo-memory/src/effect/log.ts index 1db5d171329..d5471df3332 100644 --- a/packages/kilo-memory/src/effect/log.ts +++ b/packages/kilo-memory/src/effect/log.ts @@ -4,12 +4,21 @@ export namespace MemoryLog { export type Fn = (message: string, meta?: Record) => void let warnFn: Fn = () => {} + let debugFn: Fn = () => {} export function setWarn(fn: Fn) { warnFn = fn } + export function setDebug(fn: Fn) { + debugFn = fn + } + export function warn(message: string, meta?: Record) { warnFn(message, meta) } + + export function debug(message: string, meta?: Record) { + debugFn(message, meta) + } } diff --git a/packages/kilo-memory/src/storage/audit.ts b/packages/kilo-memory/src/storage/audit.ts index 70c7c42bf3a..390c9fa0cf7 100644 --- a/packages/kilo-memory/src/storage/audit.ts +++ b/packages/kilo-memory/src/storage/audit.ts @@ -1,15 +1,6 @@ -import z from "zod" -import { MemoryFs } from "./fs" +import { MemoryLog } from "../effect/log" export namespace MemoryAudit { - const Log = z - .object({ - kind: z.literal("log"), - summary: z.string(), - time: z.string().optional(), - }) - .passthrough() - export type Decision = | { kind: "log" @@ -45,8 +36,7 @@ export namespace MemoryAudit { function audit(root: string, input: Decision) { void root - void input - return Promise.resolve() + MemoryLog.debug("memory audit", input) } export async function append(root: string, text: string) { @@ -62,24 +52,8 @@ export namespace MemoryAudit { return "" } - function record(input: string) { - try { - const data = JSON.parse(input) - const parsed = Log.safeParse(data) - return parsed.success ? parsed.data : undefined - } catch (error) { - if (MemoryFs.parse(error)) return undefined - throw error - } - } - export async function readChanges(root: string) { - const lines = (await readDecisions(root)).split("\n").flatMap((line) => { - const data = record(line) - if (!data) return [] - const time = data.time ?? "" - return [`${time} ${data.summary}`.trim()] - }) - return lines.join("\n") + void root + return "" } } diff --git a/packages/kilo-memory/test/audit.test.ts b/packages/kilo-memory/test/audit.test.ts new file mode 100644 index 00000000000..44b6f87ccb5 --- /dev/null +++ b/packages/kilo-memory/test/audit.test.ts @@ -0,0 +1,43 @@ +import { MemoryAudit } from "../src/storage/audit" +import { MemoryLog } from "../src/effect/log" + +describe("memory audit → debug log", () => { + let captured: { message: string; meta?: Record }[] = [] + + beforeEach(() => { + captured = [] + MemoryLog.setDebug((message, meta) => captured.push({ message, meta })) + }) + + afterEach(() => { + MemoryLog.setDebug(() => {}) + }) + + test("debug() is a no-op until a logger is injected", () => { + MemoryLog.setDebug(() => {}) + expect(() => MemoryLog.debug("anything")).not.toThrow() + }) + + test("append() routes a log record through the debug channel", async () => { + await MemoryAudit.append("/tmp/root", "hello world") + expect(captured).toEqual([ + { message: "memory audit", meta: { kind: "log", result: "logged", summary: "hello world" } }, + ]) + }) + + test("decide() routes a structured decision through the debug channel", async () => { + await MemoryAudit.decide("/tmp/root", { kind: "digest", result: "saved", operationCount: 3 }) + expect(captured).toHaveLength(1) + expect(captured[0].message).toBe("memory audit") + expect(captured[0].meta).toMatchObject({ kind: "digest", result: "saved", operationCount: 3 }) + }) + + test("warn and debug use independent channels", () => { + const warns: string[] = [] + MemoryLog.setWarn((message) => warns.push(message)) + MemoryLog.warn("w1") + MemoryLog.debug("d1") + expect(warns).toEqual(["w1"]) + expect(captured.map((item) => item.message)).toEqual(["d1"]) + }) +}) diff --git a/packages/opencode/src/kilocode/memory/runtime.ts b/packages/opencode/src/kilocode/memory/runtime.ts index 74dae54524d..d159e395035 100644 --- a/packages/opencode/src/kilocode/memory/runtime.ts +++ b/packages/opencode/src/kilocode/memory/runtime.ts @@ -19,5 +19,6 @@ export function installMemoryRuntime() { MemoryPaths.configure(() => ({ data: Global.Path.data })) MemoryInstance.setBinder((fn) => bind(fn)) MemoryLog.setWarn((message, meta) => log.warn(message, meta)) + MemoryLog.setDebug((message, meta) => log.debug(message, meta)) MemoryEvents.install() } From 0b548f9e0ee1d588aee8fe6ca0ec2f800faefc77 Mon Sep 17 00:00:00 2001 From: Vitaly X Date: Sat, 1 Aug 2026 23:45:15 +0300 Subject: [PATCH 2/2] Update packages/kilo-memory/src/effect/capture.ts Co-authored-by: kilo-code-bot[bot] <240665456+kilo-code-bot[bot]@users.noreply.github.com> --- packages/kilo-memory/src/effect/capture.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kilo-memory/src/effect/capture.ts b/packages/kilo-memory/src/effect/capture.ts index 1ec9c8a0a16..81bc79fdda2 100644 --- a/packages/kilo-memory/src/effect/capture.ts +++ b/packages/kilo-memory/src/effect/capture.ts @@ -302,7 +302,7 @@ export namespace MemoryCapture { yield* fail("digest parse_error") yield* memory.append({ root, - text: `digest parse_error=${MemoryShared.brief(reason, 160)} full=${result.result.text} fallback=1`, + text: `digest parse_error=${MemoryShared.brief(reason, 160)} full=${MemoryShared.brief(MemoryRedact.text(result.result.text), 2000)} fallback=1`, }) return undefined }),