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
5 changes: 5 additions & 0 deletions .changeset/skip-legacy-wire-records.md
Original file line number Diff line number Diff line change
@@ -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.
15 changes: 13 additions & 2 deletions packages/agent-core-v2/src/wire/wireService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Comment on lines +9 to +13

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Limit the module header to its external role

Remove the new replay/allowlist details from this header: they narrate branch behavior and enumerate implementation decisions, so the comment will become stale whenever the legacy set or rewrite logic changes. The scoped guide requires top-of-file comments to describe only the module’s external responsibility and explicitly forbids narrating implementation steps.

AGENTS.md reference: packages/agent-core-v2/AGENTS.md:L11-L15

Useful? React with 👍 / 👎.

*/

/* eslint-disable @typescript-eslint/no-explicit-any */
Expand Down Expand Up @@ -49,6 +53,11 @@ import {

const MAX_DRAIN = 100;

const LEGACY_RECORD_TYPES: ReadonlySet<string> = new Set([
'micro_compaction.apply',
'context.update_token_count',
]);

export class CycleError extends WireError {
constructor(readonly depth: number, readonly opTypes: readonly string[]) {
super(
Expand Down Expand Up @@ -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));
Expand Down
57 changes: 57 additions & 0 deletions packages/agent-core-v2/test/wire/wire-compat.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Split the healing rewrite into a separate test

This single it checks both silent replay and preservation during a protocol migration, so a failure does not identify which contract regressed and the skip behavior is unnecessarily coupled to rewrite mechanics. Keep the silent-skip/replay assertion in one scenario and move the metadata upgrade plus record-preservation assertions into a separately named test.

Useful? React with 👍 / 👎.

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<WireRecord>(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);
});
});