Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/memory-audit-log.md
Original file line number Diff line number Diff line change
@@ -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`.
4 changes: 2 additions & 2 deletions packages/kilo-memory/src/effect/capture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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=${MemoryShared.brief(MemoryRedact.text(result.result.text), 2000)} fallback=1`,
})
return undefined
}),
Expand Down Expand Up @@ -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}` })
Comment thread
rusak47 marked this conversation as resolved.
return undefined
}),
),
Expand Down
9 changes: 9 additions & 0 deletions packages/kilo-memory/src/effect/log.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,21 @@ export namespace MemoryLog {
export type Fn = (message: string, meta?: Record<string, unknown>) => 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<string, unknown>) {
warnFn(message, meta)
}

export function debug(message: string, meta?: Record<string, unknown>) {
debugFn(message, meta)
}
}
34 changes: 4 additions & 30 deletions packages/kilo-memory/src/storage/audit.ts
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -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) {
Expand All @@ -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 ""
}
}
43 changes: 43 additions & 0 deletions packages/kilo-memory/test/audit.test.ts
Original file line number Diff line number Diff line change
@@ -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<string, unknown> }[] = []

beforeEach(() => {
captured = []
MemoryLog.setDebug((message, meta) => captured.push({ message, meta }))
})

afterEach(() => {
MemoryLog.setDebug(() => {})
})

test("debug() is a no-op until a logger is injected", () => {
Comment thread
rusak47 marked this conversation as resolved.
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))
Comment thread
rusak47 marked this conversation as resolved.
MemoryLog.warn("w1")
MemoryLog.debug("d1")
expect(warns).toEqual(["w1"])
expect(captured.map((item) => item.message)).toEqual(["d1"])
})
})
1 change: 1 addition & 0 deletions packages/opencode/src/kilocode/memory/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}