diff --git a/.changeset/skip-legacy-wire-records.md b/.changeset/skip-legacy-wire-records.md new file mode 100644 index 0000000000..f26cb0e9a7 --- /dev/null +++ b/.changeset/skip-legacy-wire-records.md @@ -0,0 +1,5 @@ +--- +"@moonshot-ai/kimi-code": patch +--- + +Fix spurious "Unknown wire record type" errors when restoring sessions whose history contains records the engine deliberately no longer replays, such as those from the retired micro-compaction experiment. diff --git a/packages/agent-core-v2/src/wire/wireService.ts b/packages/agent-core-v2/src/wire/wireService.ts index 1873cc3a23..1118eb76da 100644 --- a/packages/agent-core-v2/src/wire/wireService.ts +++ b/packages/agent-core-v2/src/wire/wireService.ts @@ -6,7 +6,11 @@ * including creation-time sealing, metadata, migrations, atomic healing * rewrites, blob dehydration and rehydration plus an ordered post-restore hook. * It is bound at Agent scope because the aggregate identity is the Agent - * identity. + * identity. Replay silently skips `LEGACY_RECORD_TYPES` — journal vocabulary + * this engine knows of but deliberately does not replay: records of retired + * features (micro compaction) and v1-only bookkeeping it recomputes live + * (context token counts). Genuinely unknown types are still reported, and + * healing rewrites preserve legacy records for readers that understand them. */ /* eslint-disable @typescript-eslint/no-explicit-any */ @@ -49,6 +53,11 @@ import { const MAX_DRAIN = 100; +const LEGACY_RECORD_TYPES: ReadonlySet = new Set([ + 'micro_compaction.apply', + 'context.update_token_count', +]); + export class CycleError extends WireError { constructor(readonly depth: number, readonly opTypes: readonly string[]) { super( @@ -213,7 +222,9 @@ export class WireService extends Disposable implements IWireService { private replayRecord(record: WireRecord, index: number): void { const descriptor = OP_REGISTRY.get(record.type); if (descriptor === undefined) { - this.reportSkippedRecord(record.type, index); + if (!LEGACY_RECORD_TYPES.has(record.type)) { + this.reportSkippedRecord(record.type, index); + } return; } const payload = descriptor.schema.safeParse(wireRecordToPayload(record)); diff --git a/packages/agent-core-v2/test/wire/wire-compat.test.ts b/packages/agent-core-v2/test/wire/wire-compat.test.ts index a90ca1d02e..b158cb09f5 100644 --- a/packages/agent-core-v2/test/wire/wire-compat.test.ts +++ b/packages/agent-core-v2/test/wire/wire-compat.test.ts @@ -125,4 +125,61 @@ describe('wire.jsonl round-trip', () => { ); expect(replayTarget.wire.getModel(TagsModel)).toEqual(live.wire.getModel(TagsModel)); }); + + it('replays past legacy record types without reporting them', async () => { + const dir = await makeDir(); + const storage = new FileStorageService(dir); + const target = makeContainer(storage, 'legacy-replay'); + const records: WireRecord[] = [ + { type: 'micro_compaction.apply', cutoff: 2, time: 1 }, + { type: 'context.update_token_count', tokenCount: 5, time: 2 }, + { type: 'compat.counter.set', value: 3, time: 3 }, + ]; + + const unexpected: unknown[] = []; + setUnexpectedErrorHandler((error) => unexpected.push(error)); + try { + await restoreTestAgentWire( + target.wire, + target.log, + testWireScope(SCOPE, 'legacy-replay'), + records, + ); + } finally { + resetUnexpectedErrorHandler(); + } + + expect(unexpected).toEqual([]); + expect(target.wire.getModel(CounterModel)).toEqual({ value: 3 }); + }); + + it('skips legacy records in a metadata-first journal and preserves them through the healing rewrite', async () => { + const dir = await makeDir(); + const storage = new FileStorageService(dir); + const target = makeContainer(storage, 'legacy-metadata'); + const scope = testWireScope(SCOPE, 'legacy-metadata'); + const records: WireRecord[] = [ + { type: 'metadata', protocol_version: '1.4', created_at: 1 }, + { type: 'micro_compaction.apply', cutoff: 2, time: 1 }, + { type: 'compat.counter.set', value: 3, time: 2 }, + ]; + + const unexpected: unknown[] = []; + setUnexpectedErrorHandler((error) => unexpected.push(error)); + try { + await restoreTestAgentWire(target.wire, target.log, scope, records); + } finally { + resetUnexpectedErrorHandler(); + } + + expect(unexpected).toEqual([]); + expect(target.wire.getModel(CounterModel)).toEqual({ value: 3 }); + + const rewritten: WireRecord[] = []; + for await (const record of target.log.read(scope, AGENT_WIRE_RECORD_KEY)) { + rewritten.push(record); + } + expect(rewritten[0]).toMatchObject({ type: 'metadata', protocol_version: '1.5' }); + expect(rewritten.some((record) => record.type === 'micro_compaction.apply')).toBe(true); + }); });