Skip to content
Merged
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
7 changes: 5 additions & 2 deletions packages/core/src/execution-inspect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ export interface AgentRunInspectToolSummary {

export interface AgentRunInspectCompactionCheckpoint {
eventId: string;
validation: 'shape_valid' | 'invalid';
/** `superseded`: well-formed, but minted under an older source policy. */
validation: 'shape_valid' | 'invalid' | 'superseded';
checkpointId?: string;
policyVersion?: string;
sourceCoverage?: ExecutionLogCoverage;
Expand Down Expand Up @@ -297,7 +298,9 @@ function isCompactionCheckpoint(value: unknown): boolean {
['checkpointId', 'policyVersion', 'sourceCoverage'],
) &&
typeof value.eventId === 'string' &&
(value.validation === 'shape_valid' || value.validation === 'invalid') &&
(value.validation === 'shape_valid' ||
value.validation === 'invalid' ||
value.validation === 'superseded') &&
isOptionalString(value.checkpointId) &&
isOptionalString(value.policyVersion) &&
(value.sourceCoverage === undefined || isCoverage(value.sourceCoverage))
Expand Down
2 changes: 1 addition & 1 deletion packages/runtime/src/__tests__/context-budget.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { createHash } from 'node:crypto';
import { test } from 'node:test';
import type { RuntimeEvent } from '@maka/core/runtime-event';
import { applyRuntimeEventContextBudget } from '../context-budget.js';
import { estimateRuntimeEventsTokens } from '../context-budget-helpers.js';
import { estimateRuntimeEventsTokens } from '../model-history.js';
import { buildHistoryCompactCheckpoint } from '../history-compact-checkpoint.js';

test('estimates only model-visible provider context', () => {
Expand Down
113 changes: 113 additions & 0 deletions packages/runtime/src/__tests__/conversation-copy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2402,6 +2402,119 @@ test('conversation copy rebuilds an inline checkpoint without legacy child event
}
});

test('conversation copy drops a checkpoint from a superseded source policy instead of failing', async () => {
// A ledger keeps every checkpoint it ever recorded, so a session that
// compacted under an older source policy still carries that record forever.
// Copy must treat it as absent — the copy carries the canonical raw
// RuntimeEvents and can compact again — or those sessions become permanently
// unbranchable (apache/maka#4283).
const root = await mkdtemp(join(tmpdir(), 'maka-conversation-legacy-policy-copy-'));
try {
const runStore = createSqliteAgentRunStore(root);
const runtimeEventStore = createWorkspaceRuntimeStore(root);
const run = agentRunHeader({
runId: 'run-source',
invocationId: 'invocation-1',
turnId: 'turn-1',
cwd: root,
completedAt: 3,
});
await runStore.createRun(run);
const sourceEvents = [
runtimeEvent({
id: 'event-user',
invocationId: 'invocation-1',
runId: 'run-source',
turnId: 'turn-1',
role: 'user',
author: 'user',
content: { kind: 'text', text: 'first' },
}),
runtimeEvent({
id: 'event-terminal',
invocationId: 'invocation-1',
runId: 'run-source',
turnId: 'turn-1',
ts: 2,
role: 'system',
author: 'system',
status: 'completed',
}),
];
for (const event of sourceEvents) {
await runtimeEventStore.appendRuntimeEvent(event.sessionId, event.runId, event);
}
const current = buildHistoryCompactCheckpoint({
sessionId: 'session-source',
coveredRuntimeEvents: sourceEvents.filter(isHistoryCompactContentEvent),
summary: 'Everything so far is complete.',
summaryFormat: 'legacy_freeform',
highWaterSeq: 5,
});
const legacyPolicyCheckpoint = {
...current,
source: {
...current.source,
policyVersion: 'maka.compactable_runtime_event_projection.v1',
},
};
await runStore.appendEvent('session-source', 'run-source', {
type: 'history_compact_checkpoint_recorded',
id: 'checkpoint-legacy-policy',
runId: 'run-source',
sessionId: 'session-source',
turnId: 'turn-1',
ts: 2.5,
data: {
checkpointId: legacyPolicyCheckpoint.checkpointId,
highWaterName: legacyPolicyCheckpoint.highWaterName,
highWaterSeq: legacyPolicyCheckpoint.highWaterSeq,
boundaryKind: 'historyCompact',
checkpoint: legacyPolicyCheckpoint,
},
});
const source = await new RuntimeReadModel({
runStore,
runtimeEventStore,
}).getSessionView('session-source');
let sequence = 0;

await cloneConversationRuntimeLedger({
plan: await prepareTestCopyPlan(source, source.messages, runStore, runtimeEventStore),
copiedMessages: source.messages,
referenceMap: {
mode: 'exact',
linkedChildren: { mode: 'reject' },
sourceSessionId: 'session-source',
targetSessionId: 'session-target',
artifactIds: new Map(),
relativePaths: new Map(),
},
runStore,
runtimeEventStore,
newId: () => `target-${++sequence}`,
});

const targetRuns = await runStore.listSessionRuns('session-target');
assert.ok(targetRuns.length > 0);
const targetOperationalEvents = (
await Promise.all(targetRuns.map((run) => runStore.readEvents('session-target', run.runId)))
).flat();
assert.equal(
targetOperationalEvents.some((event) => event.type === 'history_compact_checkpoint_recorded'),
false,
);
const targetEvents = (
await Promise.all(
targetRuns.map((run) => runtimeEventStore.readRuntimeEvents('session-target', run.runId)),
)
).flat();
assert.equal(targetEvents.length, sourceEvents.length);
} finally {
await rm(root, { recursive: true, force: true });
}
});

test('conversation copy rebuilds a resumed child checkpoint over its child run chain', async () => {
const root = await mkdtemp(join(tmpdir(), 'maka-conversation-child-checkpoint-copy-'));
try {
Expand Down
195 changes: 195 additions & 0 deletions packages/runtime/src/__tests__/effective-history-compaction.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

/**
* Budgeting, summarization, and checkpoint source digests must all read the
* EFFECTIVE model history — the durable Tool Result projection committed at T2
* — and never the raw execution fact it replaced (apache/maka#4283, PR 2).
*/

import assert from 'node:assert/strict';
import { describe, test } from 'node:test';
import type { RuntimeEvent } from '@maka/core/runtime-event';
import type { DurableToolResultProjection } from '@maka/core/durable-tool-result-projection';
import { estimateRuntimeEventsTokens } from '../model-history.js';
import {
buildHistoryCompactCheckpoint,
matchHistoryCompactCheckpointPrefix,
validateHistoryCompactCheckpointShape,
} from '../history-compact-checkpoint.js';
import {
buildLlmHistorySummarizer,
type AiSdkGenerateTextLike,
} from '../history-compact-summarizer.js';

const RAW_SECRET = 'RAW-EXECUTION-EVIDENCE-'.repeat(200);
const PROJECTED = 'bounded model-visible result';

const STRUCTURED_SUMMARY = [
'## Goal',
'X',
'',
'## Progress',
'- done',
'',
'## Next Steps',
'1. continue',
'',
'## Critical Context',
'- (none)',
].join('\n');

describe('effective model history feeds budgeting and compaction', () => {
test('budgeting sizes the durable projection, not the raw execution fact', () => {
const projected = toolResultEvent('evt-3', RAW_SECRET, textProjection(PROJECTED));
const raw = toolResultEvent('evt-3', RAW_SECRET);

// Same raw result, but only the un-projected legacy event is sized by it.
assert.equal(estimateRuntimeEventsTokens([projected], 1), 'Bash'.length + PROJECTED.length);
assert.ok(estimateRuntimeEventsTokens([raw], 1) > RAW_SECRET.length);
});

test('summarization cannot read raw output the projection replaced', async () => {
let seen: Parameters<AiSdkGenerateTextLike>[0] | undefined;
const summarize = buildLlmHistorySummarizer({
resolveModel: () => 'fake-model',
generateText: async (options) => {
seen = options;
return { text: STRUCTURED_SUMMARY };
},
});

await summarize({
sessionId: 'session-1',
turnId: 'turn-1',
source: {
foldedRuntimeEvents: [
userEvent('evt-1', 'run it'),
toolCallEvent('evt-2'),
toolResultEvent('evt-3', RAW_SECRET, textProjection(PROJECTED)),
],
},
});

const serialized = JSON.stringify(seen?.messages ?? []);
assert.ok(serialized.includes(PROJECTED));
assert.equal(serialized.includes('RAW-EXECUTION-EVIDENCE-'), false);
});

test('the source digest ignores raw evidence but tracks the effective projection', () => {
const covered = [
userEvent('evt-1', 'run it'),
toolCallEvent('evt-2'),
toolResultEvent('evt-3', RAW_SECRET, textProjection(PROJECTED)),
];
const checkpoint = buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: covered,
summary: STRUCTURED_SUMMARY,
charsPerToken: 1,
});

// Raw evidence rewritten under an unchanged projection: still the same
// folded model history, so the checkpoint keeps replaying.
const rawRewritten = [
...covered.slice(0, 2),
toolResultEvent('evt-3', 'a different raw fact', textProjection(PROJECTED)),
];
assert.equal(matchHistoryCompactCheckpointPrefix(checkpoint, rawRewritten).reason, undefined);

// The projection itself replaced: the folded content is no longer what
// this checkpoint covered, so it must not replay over it.
const projectionReplaced = [
...covered.slice(0, 2),
toolResultEvent('evt-3', RAW_SECRET, textProjection('[archived]')),
];
assert.equal(
matchHistoryCompactCheckpointPrefix(checkpoint, projectionReplaced).reason,
'source_hash_mismatch',
);
});

test('a checkpoint minted under the raw-source policy no longer validates', () => {
const checkpoint = buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: [userEvent('evt-1', 'run it')],
summary: STRUCTURED_SUMMARY,
charsPerToken: 1,
});
const legacy = {
...checkpoint,
source: {
...checkpoint.source,
policyVersion: 'maka.compactable_runtime_event_projection.v1',
},
} as unknown as typeof checkpoint;

assert.equal(validateHistoryCompactCheckpointShape(checkpoint, 'session-1'), true);
assert.equal(validateHistoryCompactCheckpointShape(legacy, 'session-1'), false);
});
});

function textProjection(text: string): DurableToolResultProjection {
return { version: 1, kind: 'text', text };
}

function userEvent(id: string, text: string): RuntimeEvent {
return {
id,
invocationId: 'invocation-1',
sessionId: 'session-1',
runId: 'run-1',
turnId: 'turn-1',
ts: 1,
partial: false,
role: 'user',
author: 'user',
status: 'completed',
modelVisibility: 'visible',
content: { kind: 'text', text },
};
}

function toolCallEvent(id: string): RuntimeEvent {
return {
...userEvent(id, ''),
role: 'model',
author: 'agent',
content: { kind: 'function_call', id: 'tool-call', name: 'Bash', args: {} },
};
}

function toolResultEvent(
id: string,
result: string,
modelProjection?: DurableToolResultProjection,
): RuntimeEvent {
return {
...userEvent(id, ''),
role: 'tool',
author: 'tool',
content: {
kind: 'function_response',
id: 'tool-call',
name: 'Bash',
result,
...(modelProjection ? { modelProjection } : {}),
},
};
}
Loading