diff --git a/packages/core/src/backend-types.ts b/packages/core/src/backend-types.ts
index ef98240d7e..18f4383c2a 100644
--- a/packages/core/src/backend-types.ts
+++ b/packages/core/src/backend-types.ts
@@ -81,16 +81,13 @@ export interface BackendSendInput {
/** Inline quoted excerpts folded into the model-facing user content. */
quotes?: QuoteRef[];
/**
- * Prior conversation projected from the RuntimeEvent ledger into the
- * existing StoredMessage public shape. Adapters materialize this into the
- * SDK's expected conversation shape when native RuntimeEvent replay is not
- * available.
+ * Legacy caller projection retained for source compatibility. Runtime
+ * backends must not use it as provider history; RuntimeEvents are the only
+ * model-history authority.
*/
- context: StoredMessage[];
+ context?: StoredMessage[];
/**
- * Optional prior RuntimeEvent ledger for model-history projection. Backends
- * prefer this when supplied and usable; `context` is the RuntimeEvent-derived
- * compatibility projection.
+ * Optional prior RuntimeEvent ledger for model-history projection.
*/
runtimeContext?: RuntimeEvent[];
/**
diff --git a/packages/runtime-host/src/__tests__/memory-extraction-coordinator.test.ts b/packages/runtime-host/src/__tests__/memory-extraction-coordinator.test.ts
index 3a80bdfefd..ed38c7749c 100644
--- a/packages/runtime-host/src/__tests__/memory-extraction-coordinator.test.ts
+++ b/packages/runtime-host/src/__tests__/memory-extraction-coordinator.test.ts
@@ -45,6 +45,10 @@ import { type MemoryExtractionSourceSnapshot } from '@maka/runtime/memory-extrac
import { HostMemoryExtractionCoordinator } from '../server/memory-extraction-coordinator.js';
import { MemoryExtractionSessionLane } from '../server/memory-extraction-session-lane.js';
+function sectionedSummary(goal: string): string {
+ return `## Goal\n${goal}\n\n## Progress\n- done\n\n## Next Steps\n1. continue\n\n## Critical Context\n- (none)`;
+}
+
describe('HostMemoryExtractionCoordinator', () => {
test('extracts incidental memory through the post-terminal memory_extract path', async () => {
await withMemoryWriter(async (writer) => {
@@ -685,8 +689,7 @@ describe('HostMemoryExtractionCoordinator', () => {
checkpoint: buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: [old],
- summary: 'The older context was compacted.',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('The older context was compacted.'),
now: 1_500,
}),
});
@@ -731,15 +734,13 @@ describe('HostMemoryExtractionCoordinator', () => {
const firstCheckpoint = buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: [first, firstBoundary],
- summary: 'FIRST SUMMARY MUST BE REPLACED',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('FIRST SUMMARY MUST BE REPLACED'),
});
const secondCheckpoint = buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: [first, firstBoundary, second, secondBoundary],
previousCheckpointId: firstCheckpoint.checkpointId,
- summary: 'LATEST SECOND SUMMARY',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('LATEST SECOND SUMMARY'),
});
await writer.initializeExtractionCursor('session-1', 4);
const observed: Array<{ snapshot: MemoryExtractionSourceSnapshot; prompt: string }> = [];
@@ -811,8 +812,7 @@ describe('HostMemoryExtractionCoordinator', () => {
checkpoint: buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: [old, anchor],
- summary: 'The older context and current-turn prefix were compacted.',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('The older context and current-turn prefix were compacted.'),
phase: 'mid_turn',
headAnchor: { runtimeEventId: anchor.id, turnId: anchor.turnId },
now: 1_500,
@@ -849,8 +849,7 @@ describe('HostMemoryExtractionCoordinator', () => {
const checkpoint = buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: [old],
- summary: 'Purported compacted context.',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('Purported compacted context.'),
now: 1_500,
});
const observed: Array<{ snapshot: MemoryExtractionSourceSnapshot; prompt: string }> = [];
@@ -1239,8 +1238,7 @@ describe('HostMemoryExtractionCoordinator', () => {
const checkpoint = buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: [firstUser, secondUser],
- summary: 'The conversation contains two durable preferences.',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('The conversation contains two durable preferences.'),
memoryExtractionBoundary: {
runId: compactionBoundary.runId,
turnId: compactionBoundary.turnId,
@@ -1551,8 +1549,7 @@ describe('HostMemoryExtractionCoordinator', () => {
const firstCheckpoint = buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: [oldUser],
- summary: 'Old context.',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('Old context.'),
memoryExtractionBoundary: {
runId: oldBoundary.runId,
turnId: oldBoundary.turnId,
@@ -1616,8 +1613,7 @@ describe('HostMemoryExtractionCoordinator', () => {
const secondCheckpoint = buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: [oldUser, oldBoundary, newUser],
- summary: 'Old and new context.',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('Old and new context.'),
previousCheckpointId: firstCheckpoint.checkpointId,
memoryExtractionBoundary: {
runId: newBoundary.runId,
@@ -1671,8 +1667,7 @@ describe('HostMemoryExtractionCoordinator', () => {
const firstCheckpoint = buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: [oldUser],
- summary: 'Old context.',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('Old context.'),
memoryExtractionBoundary: {
runId: oldBoundary.runId,
turnId: oldBoundary.turnId,
@@ -1682,8 +1677,7 @@ describe('HostMemoryExtractionCoordinator', () => {
const secondCheckpoint = buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: [oldUser, oldBoundary, newUser],
- summary: 'Old and new context.',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('Old and new context.'),
previousCheckpointId: firstCheckpoint.checkpointId,
memoryExtractionBoundary: {
runId: newBoundary.runId,
@@ -1771,8 +1765,7 @@ describe('HostMemoryExtractionCoordinator', () => {
const deniedCheckpoint = buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: [pendingUser, pendingBoundary, deniedUser],
- summary: 'Denied period.',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('Denied period.'),
memoryExtractionBoundary: {
runId: deniedBoundary.runId,
turnId: deniedBoundary.turnId,
@@ -1789,8 +1782,7 @@ describe('HostMemoryExtractionCoordinator', () => {
deniedBoundary,
eligibleUser,
],
- summary: 'Eligible tail.',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('Eligible tail.'),
previousCheckpointId: deniedCheckpoint.checkpointId,
memoryExtractionBoundary: {
runId: eligibleBoundary.runId,
@@ -1904,8 +1896,7 @@ describe('HostMemoryExtractionCoordinator', () => {
const deniedCheckpoint = buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: [requestedUser, requestedCall, deniedUser],
- summary: 'DENIED_SUMMARY_SECRET',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('DENIED_SUMMARY_SECRET'),
memoryExtractionBoundary: {
runId: deniedBoundary.runId,
turnId: deniedBoundary.turnId,
@@ -1916,8 +1907,7 @@ describe('HostMemoryExtractionCoordinator', () => {
const laterCheckpoint = buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: [requestedUser, requestedCall, deniedUser, deniedBoundary, laterUser],
- summary: 'Cumulative summary after denial.',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('Cumulative summary after denial.'),
previousCheckpointId: deniedCheckpoint.checkpointId,
memoryExtractionBoundary: {
runId: laterBoundary.runId,
@@ -2046,8 +2036,7 @@ describe('HostMemoryExtractionCoordinator', () => {
const deniedCheckpoint = buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: [deniedUser],
- summary: 'Denied period.',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('Denied period.'),
memoryExtractionBoundary: {
runId: deniedBoundary.runId,
turnId: deniedBoundary.turnId,
@@ -2058,8 +2047,7 @@ describe('HostMemoryExtractionCoordinator', () => {
const eligibleCheckpoint = buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: [deniedUser, deniedBoundary, eligibleUser],
- summary: 'Eligible tail.',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('Eligible tail.'),
previousCheckpointId: deniedCheckpoint.checkpointId,
memoryExtractionBoundary: {
runId: eligibleBoundary.runId,
@@ -2117,8 +2105,7 @@ describe('HostMemoryExtractionCoordinator', () => {
const checkpoint = buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: [user],
- summary: 'Context.',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('Context.'),
memoryExtractionBoundary: {
runId: 'wrong-run',
turnId: boundary.turnId,
@@ -2161,8 +2148,7 @@ describe('HostMemoryExtractionCoordinator', () => {
const checkpoint = buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: [user],
- summary: 'Context.',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('Context.'),
memoryExtractionBoundary: {
runId: boundary.runId,
turnId: boundary.turnId,
@@ -2217,8 +2203,7 @@ describe('HostMemoryExtractionCoordinator', () => {
const checkpoint = buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: [user],
- summary: 'Context.',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('Context.'),
memoryExtractionBoundary: {
runId: boundary.runId,
turnId: boundary.turnId,
@@ -2259,8 +2244,7 @@ describe('HostMemoryExtractionCoordinator', () => {
const checkpoint = buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: [user],
- summary: 'Context.',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('Context.'),
memoryExtractionBoundary: {
runId: boundary.runId,
turnId: boundary.turnId,
diff --git a/packages/runtime-host/src/__tests__/session-revision-two-client-uds.test.ts b/packages/runtime-host/src/__tests__/session-revision-two-client-uds.test.ts
index 72e913fc87..efdc7f4895 100644
--- a/packages/runtime-host/src/__tests__/session-revision-two-client-uds.test.ts
+++ b/packages/runtime-host/src/__tests__/session-revision-two-client-uds.test.ts
@@ -78,6 +78,10 @@ const GRAPH_SIDE_CONVERSATION_REMOVAL_TARGET_ID = 'graph-side-conversation-remov
const ARCHIVED_SIDE_CONVERSATION_TARGET_ID = 'archived-side-conversation-target';
const ACTIVE_SOURCE_SIDE_CONVERSATION_TARGET_ID = 'active-source-side-conversation-target';
+function sectionedSummary(goal: string): string {
+ return `## Goal\n${goal}\n\n## Progress\n- done\n\n## Next Steps\n1. continue\n\n## Critical Context\n- (none)`;
+}
+
test('two Clients share exact retryable Session branch and revision authority', {
skip: process.platform === 'win32' ? 'Windows SQLite shutdown lifecycle' : false,
timeout: 120_000,
@@ -1642,8 +1646,7 @@ async function seedDurableOrderCheckpoint(
const checkpoint = buildHistoryCompactCheckpoint({
sessionId: sourceSessionId,
coveredRuntimeEvents,
- summary: 'The first turn completed.',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('The first turn completed.'),
highWaterSeq: 2,
});
await execution.agentRunStore.appendEvent(sourceSessionId, 'run-turn-1', {
diff --git a/packages/runtime/src/__tests__/ai-sdk-backend.test.ts b/packages/runtime/src/__tests__/ai-sdk-backend.test.ts
index ef34267203..055875101e 100644
--- a/packages/runtime/src/__tests__/ai-sdk-backend.test.ts
+++ b/packages/runtime/src/__tests__/ai-sdk-backend.test.ts
@@ -43,6 +43,7 @@ import {
mapSessionEventToRuntimeEvent,
} from '../session-event-runtime-mapper.js';
import { projectRuntimeEventsToStoredMessages } from '../runtime-event-read-model.js';
+import { sectionedSummary } from './history-compact-test-fixtures.js';
import type { RuntimeEventMapContext } from '../session-event-runtime-mapper.js';
import type { AssistantMessage, StoredMessage, ToolResultMessage } from '@maka/core/session';
import { z } from 'zod';
@@ -2133,7 +2134,7 @@ describe('AiSdkBackend model history', () => {
assert.equal(prompt.at(-1)?.role, 'tool');
});
- test('uses StoredMessage projection when RuntimeEvent replay is empty', async () => {
+ test('does not recover provider history from StoredMessages when RuntimeEvent replay is empty', async () => {
const model = completionModel();
const backend = createTestAiSdkBackend({
sessionId: 'session-1',
@@ -2182,65 +2183,11 @@ describe('AiSdkBackend model history', () => {
);
assert.deepEqual(compactPrompt(model), [
- { role: 'user', content: [{ type: 'text', text: 'projection user' }] },
- { role: 'assistant', content: [{ type: 'text', text: 'projection assistant' }] },
- { role: 'user', content: [{ type: 'text', text: 'current user' }] },
- ]);
- });
-
- test('stored-message fallback skips empty assistant texts', async () => {
- // A thinking/tool-only step projects an assistant row with empty text.
- // The degraded stored-message path must not replay it: an empty text
- // content block is a hard 400 on Anthropic-protocol providers, which
- // permanently blocks every later turn of the session.
- const model = completionModel();
- const backend = createTestAiSdkBackend({
- sessionId: 'session-1',
- header: header(),
- appendMessage: async () => {},
- connection: connection(),
- apiKey: 'sk-test',
- modelId: 'mock-model-id',
- modelFactory: () => model,
- tools: [],
- newId: idGenerator(),
- now: monotonicClock(),
- });
-
- await drain(
- backend.send({
- turnId: 'turn-current',
- text: 'current user',
- context: [
- { type: 'user', id: 'projection-u', turnId: 'turn-prev', ts: 1, text: 'projection user' },
- {
- type: 'assistant',
- id: 'projection-empty',
- turnId: 'turn-prev',
- ts: 2,
- text: '',
- modelId: 'm',
- },
- {
- type: 'assistant',
- id: 'projection-a',
- turnId: 'turn-prev',
- ts: 3,
- text: 'projection assistant',
- modelId: 'm',
- },
- ],
- }),
- );
-
- assert.deepEqual(compactPrompt(model), [
- { role: 'user', content: [{ type: 'text', text: 'projection user' }] },
- { role: 'assistant', content: [{ type: 'text', text: 'projection assistant' }] },
{ role: 'user', content: [{ type: 'text', text: 'current user' }] },
]);
});
- test('stored-message fallback describes an attachment that is not safely addressable', async () => {
+ test('RuntimeEvent replay describes an attachment that is not safely addressable', async () => {
const model = completionModel();
const backend = createTestAiSdkBackend({
sessionId: 'session-1',
@@ -2259,50 +2206,38 @@ describe('AiSdkBackend model history', () => {
backend.send({
turnId: 'turn-current',
text: 'current user',
- context: [
- {
- type: 'user',
- id: 'projection-u',
+ context: [],
+ runtimeContext: [
+ runtimeEvent({
+ id: 'rt-u',
turnId: 'turn-prev',
- ts: 1,
- text: 'see the attached chart',
- attachments: [
- {
- kind: 'image',
- name: 'chart.png',
- mimeType: 'image/png',
- bytes: 123,
- ref: {
- kind: 'session_file',
- sessionId: 'sess-1',
- relativePath: 'attachments/chart.png',
+ role: 'user',
+ author: 'user',
+ content: {
+ kind: 'text',
+ text: 'see the attached chart',
+ attachments: [
+ {
+ kind: 'image',
+ name: 'chart.png',
+ mimeType: 'image/png',
+ bytes: 123,
+ ref: {
+ kind: 'session_file',
+ sessionId: 'sess-1',
+ relativePath: 'attachments/chart.png',
+ },
},
- },
- ],
- },
- {
- type: 'assistant',
- id: 'projection-a',
- turnId: 'turn-prev',
- ts: 2,
- text: 'projection assistant',
- modelId: 'm',
- },
- ],
- runtimeContext: [
- {
- id: 'rt-terminal',
- invocationId: 'inv-1',
- runId: 'run-prev',
- sessionId: 'session-1',
+ ],
+ },
+ }),
+ runtimeTextEvent({
+ id: 'rt-a',
turnId: 'turn-prev',
- ts: 1,
- partial: false,
role: 'model',
author: 'agent',
- status: 'completed',
- actions: { endInvocation: true },
- },
+ text: 'projection assistant',
+ }),
],
}),
);
@@ -2316,11 +2251,11 @@ describe('AiSdkBackend model history', () => {
text.includes(
'\nThe attachment content is unavailable to Read.\nname: "chart.png"\nmime_type: "image/png"\n',
),
- `expected unavailable attachment context in stored-message fallback, got: ${text}`,
+ `expected unavailable attachment context in RuntimeEvent replay, got: ${text}`,
);
});
- test('current and stored directory references expose paths without eager listings', async () => {
+ test('current and replayed directory references expose paths without eager listings', async () => {
const model = completionModel();
const backend = createTestAiSdkBackend({
sessionId: 'session-1',
@@ -2342,38 +2277,26 @@ describe('AiSdkBackend model history', () => {
turnId: 'turn-current',
text: 'inspect current',
directoryReferences: [currentReference],
- context: [
- {
- type: 'user',
- id: 'projection-u',
- turnId: 'turn-prev',
- ts: 1,
- text: 'inspect prior',
- directoryReferences: [historicalReference],
- },
- {
- type: 'assistant',
- id: 'projection-a',
- turnId: 'turn-prev',
- ts: 2,
- text: 'projection assistant',
- modelId: 'm',
- },
- ],
+ context: [],
runtimeContext: [
- {
- id: 'rt-terminal',
- invocationId: 'inv-1',
- runId: 'run-prev',
- sessionId: 'session-1',
+ runtimeEvent({
+ id: 'rt-u',
+ turnId: 'turn-prev',
+ role: 'user',
+ author: 'user',
+ content: {
+ kind: 'text',
+ text: 'inspect prior',
+ directoryReferences: [historicalReference],
+ },
+ }),
+ runtimeTextEvent({
+ id: 'rt-a',
turnId: 'turn-prev',
- ts: 1,
- partial: false,
role: 'model',
author: 'agent',
- status: 'completed',
- actions: { endInvocation: true },
- },
+ text: 'projection assistant',
+ }),
],
}),
);
@@ -2395,7 +2318,7 @@ describe('AiSdkBackend model history', () => {
}
});
- test('stored-message fallback renders image attachments as image parts when a reader is wired', async () => {
+ test('RuntimeEvent replay renders image attachments as image parts when a reader is wired', async () => {
const pngBytes = new Uint8Array([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 4, 5, 6]);
const model = completionModel();
const backend = createTestAiSdkBackend({
@@ -2417,50 +2340,38 @@ describe('AiSdkBackend model history', () => {
backend.send({
turnId: 'turn-current',
text: 'current user',
- context: [
- {
- type: 'user',
- id: 'projection-u',
+ context: [],
+ runtimeContext: [
+ runtimeEvent({
+ id: 'rt-u',
turnId: 'turn-prev',
- ts: 1,
- text: 'see the attached chart',
- attachments: [
- {
- kind: 'image',
- name: 'chart.png',
- mimeType: 'image/png',
- bytes: 123,
- ref: {
- kind: 'session_file',
- sessionId: 'sess-1',
- relativePath: 'attachments/chart.png',
+ role: 'user',
+ author: 'user',
+ content: {
+ kind: 'text',
+ text: 'see the attached chart',
+ attachments: [
+ {
+ kind: 'image',
+ name: 'chart.png',
+ mimeType: 'image/png',
+ bytes: 123,
+ ref: {
+ kind: 'session_file',
+ sessionId: 'sess-1',
+ relativePath: 'attachments/chart.png',
+ },
},
- },
- ],
- },
- {
- type: 'assistant',
- id: 'projection-a',
- turnId: 'turn-prev',
- ts: 2,
- text: 'projection assistant',
- modelId: 'm',
- },
- ],
- runtimeContext: [
- {
- id: 'rt-terminal',
- invocationId: 'inv-1',
- runId: 'run-prev',
- sessionId: 'session-1',
+ ],
+ },
+ }),
+ runtimeTextEvent({
+ id: 'rt-a',
turnId: 'turn-prev',
- ts: 1,
- partial: false,
role: 'model',
author: 'agent',
- status: 'completed',
- actions: { endInvocation: true },
- },
+ text: 'projection assistant',
+ }),
],
}),
);
@@ -2471,7 +2382,7 @@ describe('AiSdkBackend model history', () => {
const imageLike = parts.find((p) => p.type !== 'text' && p.mediaType === 'image/png');
assert.ok(
imageLike,
- `expected a historical image/png part in stored-message fallback, got: ${JSON.stringify(parts)}`,
+ `expected a historical image/png part in RuntimeEvent replay, got: ${JSON.stringify(parts)}`,
);
});
@@ -4730,8 +4641,7 @@ describe('AiSdkBackend model history', () => {
const previous = buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: oldEvents.slice(0, 1),
- summary: 'MANUAL_V2_PREVIOUS_SUMMARY',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('MANUAL_V2_PREVIOUS_SUMMARY'),
charsPerToken: 1,
});
const summaryInputs: Array<{ previous?: string; newlyFoldedIds: string[] }> = [];
@@ -4782,7 +4692,7 @@ describe('AiSdkBackend model history', () => {
assert.deepEqual(summaryInputs, [
{
- previous: 'MANUAL_V2_PREVIOUS_SUMMARY',
+ previous: previous.summary,
newlyFoldedIds: ['manual-v2-roll-old-2', 'manual-v2-roll-recent'],
},
]);
@@ -4817,8 +4727,7 @@ describe('AiSdkBackend model history', () => {
const previous = buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: [...oldEvents, recentEvent],
- summary: 'MANUAL_V2_REUSED_SUMMARY',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('MANUAL_V2_REUSED_SUMMARY'),
charsPerToken: 1,
});
let summarizeCalls = 0;
@@ -6146,9 +6055,8 @@ describe('AiSdkBackend model history', () => {
test('keeps RuntimeEvent replay when a tool result is unmatched (orphan dropped, rest replayed)', async () => {
// `unmatched_tool_result` is a non-blocking diagnostic: the materializer
- // drops the orphan itself (a standalone tool message is an Anthropic 400),
- // so the ledger stays on RuntimeEvent replay instead of falling back to
- // StoredMessage projection.
+ // drops the orphan itself (a standalone tool message is an Anthropic 400)
+ // while retaining the rest of canonical history.
const model = completionModel();
let imageReads = 0;
const backend = createTestAiSdkBackend({
@@ -6213,7 +6121,7 @@ describe('AiSdkBackend model history', () => {
}),
);
- // RuntimeEvent replay (not the StoredMessage projection), orphan gone.
+ // The orphan is gone and the rest of RuntimeEvent replay remains.
assert.deepEqual(compactPrompt(model), [
{ role: 'user', content: [{ type: 'text', text: 'runtime user' }] },
{ role: 'user', content: [{ type: 'text', text: 'current user' }] },
@@ -6280,7 +6188,7 @@ describe('AiSdkBackend model history', () => {
]);
});
- test('uses StoredMessage projection instead of leaking unsupported thinking text', async () => {
+ test('drops unsupported thinking while preserving RuntimeEvent text', async () => {
const model = completionModel();
const openAiConnection = { ...connection(), providerType: 'openai' as const };
const backend = createTestAiSdkBackend({
@@ -6301,13 +6209,19 @@ describe('AiSdkBackend model history', () => {
turnId: 'turn-current',
text: 'current user',
context: [
- { type: 'user', id: 'projection-u', turnId: 'turn-prev', ts: 1, text: 'projection user' },
+ {
+ type: 'user',
+ id: 'projection-u',
+ turnId: 'turn-prev',
+ ts: 1,
+ text: 'wrong projection',
+ },
{
type: 'assistant',
id: 'projection-a',
turnId: 'turn-prev',
ts: 2,
- text: 'projection assistant',
+ text: 'wrong projection assistant',
modelId: 'm',
},
],
@@ -14794,12 +14708,10 @@ describe('AiSdkBackend steering durability and identity', () => {
]);
});
- test('a degraded stored-message projection presents prior steering exactly once, in envelope form', async () => {
+ test('degraded RuntimeEvent replay presents prior steering exactly once, in envelope form', async () => {
// A blocking replay diagnostic (here: a tool-role text event) degrades the
- // whole ledger to the StoredMessage projection, which cannot carry the
- // RuntimeEvent steering marker. The sidecar (keyed by the projection's
- // stable ids) restores the canonical envelope + structured identity, so
- // the steering appears exactly once and dedupe still works by id.
+ // provider-native shape to text-only RuntimeEvent replay. The canonical
+ // steering marker still produces one envelope with its structured id.
const model = textCompletionModel('done');
const backend = steeringBackend(model);
const steeredEvent = runtimeTextEvent({
@@ -14822,11 +14734,7 @@ describe('AiSdkBackend steering durability and identity', () => {
backend.send({
turnId: 'turn-current',
text: 'continue',
- context: [
- { type: 'user', id: 'rt-u', turnId: 'turn-prev', ts: 1, text: 'original ask' },
- { type: 'user', id: 'rt-steer', turnId: 'turn-prev', ts: 2, text: 'steered earlier' },
- { type: 'assistant', id: 'rt-a', turnId: 'turn-prev', ts: 3, text: 'ok', modelId: 'm' },
- ],
+ context: [],
runtimeContext: [
runtimeTextEvent({
id: 'rt-u',
@@ -14856,108 +14764,6 @@ describe('AiSdkBackend steering durability and identity', () => {
]);
});
- test('the degraded-projection sidecar restores steering keyed by providerEventId', async () => {
- // A StoredMessage projection may carry the provider's event id, not the
- // runtime event id, as the message's stable id. The sidecar must match on
- // that key too, or the degraded replay silently loses the steering
- // identity (bare text, no envelope, no dedupe id).
- const model = textCompletionModel('done');
- const backend = steeringBackend(model);
- const steeredEvent = runtimeTextEvent({
- id: 'rt-steer',
- turnId: 'turn-prev',
- role: 'user',
- author: 'user',
- text: 'steered earlier',
- });
- (steeredEvent.content as { steering?: true }).steering = true;
- steeredEvent.refs = { providerEventId: 'prov-steer' };
- const degradingEvent = runtimeTextEvent({
- id: 'rt-bad',
- turnId: 'turn-prev',
- role: 'user',
- author: 'user',
- text: 'boom',
- });
- (degradingEvent as { role: string }).role = 'tool';
- await drain(
- backend.send({
- turnId: 'turn-current',
- text: 'continue',
- context: [
- { type: 'user', id: 'prov-steer', turnId: 'turn-prev', ts: 1, text: 'steered earlier' },
- { type: 'assistant', id: 'prov-a', turnId: 'turn-prev', ts: 2, text: 'ok', modelId: 'm' },
- ],
- runtimeContext: [
- steeredEvent,
- degradingEvent,
- runtimeTextEvent({
- id: 'rt-a',
- turnId: 'turn-prev',
- role: 'model',
- author: 'agent',
- text: 'ok',
- }),
- ],
- }),
- );
-
- assert.deepEqual(compactPrompt(model), [
- { role: 'user', content: [{ type: 'text', text: buildSteeringEnvelope('steered earlier') }] },
- { role: 'assistant', content: [{ type: 'text', text: 'ok' }] },
- { role: 'user', content: [{ type: 'text', text: 'continue' }] },
- ]);
- });
-
- test('the degraded-projection sidecar restores steering keyed by storedMessageId', async () => {
- const model = textCompletionModel('done');
- const backend = steeringBackend(model);
- const steeredEvent = runtimeTextEvent({
- id: 'rt-steer',
- turnId: 'turn-prev',
- role: 'user',
- author: 'user',
- text: 'steered earlier',
- });
- (steeredEvent.content as { steering?: true }).steering = true;
- steeredEvent.refs = { storedMessageId: 'sm-steer' };
- const degradingEvent = runtimeTextEvent({
- id: 'rt-bad',
- turnId: 'turn-prev',
- role: 'user',
- author: 'user',
- text: 'boom',
- });
- (degradingEvent as { role: string }).role = 'tool';
- await drain(
- backend.send({
- turnId: 'turn-current',
- text: 'continue',
- context: [
- { type: 'user', id: 'sm-steer', turnId: 'turn-prev', ts: 1, text: 'steered earlier' },
- { type: 'assistant', id: 'sm-a', turnId: 'turn-prev', ts: 2, text: 'ok', modelId: 'm' },
- ],
- runtimeContext: [
- steeredEvent,
- degradingEvent,
- runtimeTextEvent({
- id: 'rt-a',
- turnId: 'turn-prev',
- role: 'model',
- author: 'agent',
- text: 'ok',
- }),
- ],
- }),
- );
-
- assert.deepEqual(compactPrompt(model), [
- { role: 'user', content: [{ type: 'text', text: buildSteeringEnvelope('steered earlier') }] },
- { role: 'assistant', content: [{ type: 'text', text: 'ok' }] },
- { role: 'user', content: [{ type: 'text', text: 'continue' }] },
- ]);
- });
-
test('a steer that equals the current prompt still injects its envelope', async () => {
// Bare text is not an identity: deducting the steer against the verbatim
// user prompt would drop the directive from the provider request entirely
diff --git a/packages/runtime/src/__tests__/context-diagnostics.test.ts b/packages/runtime/src/__tests__/context-diagnostics.test.ts
index dcdbe9415b..4a4639ee54 100644
--- a/packages/runtime/src/__tests__/context-diagnostics.test.ts
+++ b/packages/runtime/src/__tests__/context-diagnostics.test.ts
@@ -18,6 +18,7 @@
*/
import assert from 'node:assert/strict';
+import { sectionedSummary } from './history-compact-test-fixtures.js';
import { mkdtemp, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
@@ -1200,7 +1201,8 @@ function checkpointEvent(
sourceDigest: `digest-${ts}`,
},
phase: 'pre_turn',
- summary: 'Earlier context summary.',
+ summary: sectionedSummary('Earlier context summary.'),
+ summaryFormat: 'sections_v1',
limitations: ['Estimated summary.'],
estimatedTokens,
},
diff --git a/packages/runtime/src/__tests__/continuation-replay.test.ts b/packages/runtime/src/__tests__/continuation-replay.test.ts
index 3f305894ec..798db08877 100644
--- a/packages/runtime/src/__tests__/continuation-replay.test.ts
+++ b/packages/runtime/src/__tests__/continuation-replay.test.ts
@@ -27,8 +27,12 @@ import {
import {
buildContinuationReplayPlan,
buildContinuationReplaySegment,
+ digestProviderReplayAdmission,
} from '../continuation-replay.js';
-import { PROVIDER_REPLAY_PROJECTION_VERSION } from '../model-history.js';
+import {
+ PROVIDER_REPLAY_PROJECTION_VERSION,
+ type RuntimeEventModelReplayItem,
+} from '../model-history.js';
describe('continuation replay segment', () => {
it('rejects a persisted v1 admission under the route-bound v2 projection', () => {
@@ -365,6 +369,34 @@ describe('continuation replay segment', () => {
});
});
+describe('continuation replay digest', () => {
+ it('keeps the projection v2 digest compatible while excluding internal invocation identity', () => {
+ const digest = (invocationId: string) =>
+ digestProviderReplayAdmission({
+ providerProjectionVersion: PROVIDER_REPLAY_PROJECTION_VERSION,
+ targetProviderStateIdentity: undefined,
+ targetModelId: 'test-model',
+ items: [
+ {
+ kind: 'tool_call',
+ invocationId,
+ toolCallId: 'read-1',
+ toolName: 'Read',
+ input: { path: 'notes.md' },
+ eventId: 'call-1',
+ ts: 1,
+ } satisfies RuntimeEventModelReplayItem,
+ ],
+ });
+
+ assert.equal(digest('invocation-a'), digest('invocation-b'));
+ assert.equal(
+ digest('invocation-a'),
+ 'sha256:775dac9a0959d888541d9e4930431b60dee89e4f28b62238ddde64dfd5f542ee',
+ );
+ });
+});
+
function runtimeIdentity(): RuntimePrefixIdentityV1 {
return {
sessionId: 'session-1',
diff --git a/packages/runtime/src/__tests__/conversation-copy.test.ts b/packages/runtime/src/__tests__/conversation-copy.test.ts
index 4bb9115a0f..37de5690e7 100644
--- a/packages/runtime/src/__tests__/conversation-copy.test.ts
+++ b/packages/runtime/src/__tests__/conversation-copy.test.ts
@@ -43,6 +43,7 @@ import {
import { canonicalToolArgsHash } from '@maka/core/tool-args-identity';
import { createSqliteAgentRunStore } from '@maka/storage/agent-run-store';
import { createWorkspaceRuntimeStore } from '@maka/storage/runtime-event-persistence';
+import { sectionedSummary } from './history-compact-test-fixtures.js';
import { OPERATIONAL_STATE_DATABASE_NAME } from '@maka/storage/operational-state-store';
import { createSqliteRuntimeStore } from '@maka/storage/sqlite-runtime-store';
import {
@@ -1926,8 +1927,7 @@ test('conversation copy clones one terminal Runtime ledger with new owned identi
const checkpoint = buildHistoryCompactCheckpoint({
sessionId: 'session-source',
coveredRuntimeEvents: sourceEvents.filter(isHistoryCompactContentEvent),
- summary: 'The source turn called one opaque tool.',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('The source turn called one opaque tool.'),
highWaterSeq: 3,
});
const providerCheckpoint = buildHistoryCompactCheckpoint({
@@ -2401,8 +2401,7 @@ test('conversation copy rebuilds an inline checkpoint without legacy child event
const checkpoint = buildHistoryCompactCheckpoint({
sessionId: 'session-source',
coveredRuntimeEvents: sourceEvents.filter(isHistoryCompactContentEvent),
- summary: 'Both retained turns are complete.',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('Both retained turns are complete.'),
highWaterSeq: 5,
});
await runStore.appendEvent('session-source', 'run-2', {
@@ -2516,8 +2515,7 @@ test('conversation copy drops a checkpoint from a superseded source policy inste
const current = buildHistoryCompactCheckpoint({
sessionId: 'session-source',
coveredRuntimeEvents: sourceEvents.filter(isHistoryCompactContentEvent),
- summary: 'Everything so far is complete.',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('Everything so far is complete.'),
highWaterSeq: 5,
});
const legacyPolicyCheckpoint = {
@@ -2688,8 +2686,7 @@ test('conversation copy rebuilds a resumed child checkpoint over its child run c
const checkpoint = buildHistoryCompactCheckpoint({
sessionId: 'session-source',
coveredRuntimeEvents: childSourceEvents,
- summary: 'The resumed child retained both child turns.',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('The resumed child retained both child turns.'),
highWaterSeq: 8,
});
await runStore.appendEvent('session-source', 'run-child-2', {
diff --git a/packages/runtime/src/__tests__/history-compact-checkpoint.test.ts b/packages/runtime/src/__tests__/history-compact-checkpoint.test.ts
index ef9100d561..db635d0fcf 100644
--- a/packages/runtime/src/__tests__/history-compact-checkpoint.test.ts
+++ b/packages/runtime/src/__tests__/history-compact-checkpoint.test.ts
@@ -38,6 +38,7 @@ import {
} from '../history-compact-ledger.js';
import { estimateRuntimeEventsTokens } from '../context-budget.js';
import { applyRuntimeEventHistoryCompact } from '../history-compaction.js';
+import { sectionedSummary } from './history-compact-test-fixtures.js';
// Satisfies the sectioned summary contract for marked-checkpoint fixtures.
const STRUCTURED_SUMMARY = [
@@ -206,8 +207,7 @@ describe('history compact checkpoint', () => {
const v2 = buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: [textEvent(0)],
- summary: 'text summary',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('text summary'),
});
assert.equal(validateHistoryCompactCheckpointShape({ ...v2, providerState: {} }), false);
assert.equal(
@@ -235,8 +235,7 @@ describe('history compact checkpoint', () => {
const checkpoint = buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: events,
- summary: 'Continuation summary.',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('Continuation summary.'),
now: 1_800_000_010_000,
});
@@ -271,7 +270,6 @@ describe('history compact checkpoint', () => {
sessionId: 'session-1',
coveredRuntimeEvents: [textEvent(0)],
summary: ' ',
- summaryFormat: 'legacy_freeform',
}),
/non-empty summary/,
);
@@ -280,18 +278,22 @@ describe('history compact checkpoint', () => {
test('preserves the complete model-produced summary instead of truncating it after generation', () => {
const summary = [
'## Goal',
- 'Keep every section intact.',
+ 'Keep every section intact.'.repeat(80),
+ '',
+ '## Progress',
+ '- done',
+ '',
+ '## Next Steps',
+ '1. continue',
+ '',
'## Critical Context',
'LAST_REQUIRED_FACT',
- ]
- .join('\n')
- .repeat(80);
+ ].join('\n');
const checkpoint = buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: [textEvent(0)],
summary,
- summaryFormat: 'legacy_freeform',
});
assert.equal(checkpoint.summary, summary);
@@ -304,8 +306,7 @@ describe('history compact checkpoint', () => {
buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: [textEvent(0), { ...textEvent(1), sessionId: 'session-2' }],
- summary: 'mixed source',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('mixed source'),
}),
/one session/,
);
@@ -316,8 +317,7 @@ describe('history compact checkpoint', () => {
const checkpoint = buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: events,
- summary: 'source-bound',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('source-bound'),
});
const invalid = {
...checkpoint,
@@ -338,28 +338,24 @@ describe('history compact checkpoint', () => {
const current = buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: source,
- summary: 'current',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('current'),
});
const successor = buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: source,
- summary: 'smaller replacement',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('smaller replacement'),
previousCheckpointId: current.checkpointId,
});
const stale = buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: source,
- summary: 'stale replacement',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('stale replacement'),
previousCheckpointId: 'another-checkpoint',
});
const differentSource = buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: [textEvent(2), textEvent(3)],
- summary: 'different source',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('different source'),
previousCheckpointId: current.checkpointId,
});
@@ -377,15 +373,13 @@ describe('history compact checkpoint', () => {
const first = buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: [textEvent(0)],
- summary: 'first',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('first'),
now: 10,
});
const latest = buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: [textEvent(0), textEvent(1)],
- summary: 'latest',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('latest'),
previousCheckpointId: first.checkpointId,
now: 20,
});
@@ -421,19 +415,17 @@ describe('history compact checkpoint', () => {
);
});
- test('binds an automatic Memory boundary into checkpoint identity while legacy remains valid', () => {
+ test('binds an automatic Memory boundary into checkpoint identity', () => {
const source = [textEvent(0)];
- const legacy = buildHistoryCompactCheckpoint({
+ const manual = buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: source,
- summary: 'same summary',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('same summary'),
});
const automatic = buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: source,
- summary: 'same summary',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('same summary'),
memoryExtractionBoundary: {
runId: 'run-1',
turnId: 'turn-1',
@@ -443,8 +435,7 @@ describe('history compact checkpoint', () => {
const denied = buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: source,
- summary: 'same summary',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('same summary'),
memoryExtractionBoundary: {
runId: 'run-1',
turnId: 'turn-1',
@@ -453,9 +444,9 @@ describe('history compact checkpoint', () => {
},
});
- assert.notEqual(automatic.checkpointId, legacy.checkpointId);
+ assert.notEqual(automatic.checkpointId, manual.checkpointId);
assert.notEqual(denied.checkpointId, automatic.checkpointId);
- assert.equal(validateHistoryCompactCheckpointShape(legacy, 'session-1'), true);
+ assert.equal(validateHistoryCompactCheckpointShape(manual, 'session-1'), true);
assert.equal(validateHistoryCompactCheckpointShape(automatic, 'session-1'), true);
assert.equal(
validateHistoryCompactCheckpointShape(
@@ -521,53 +512,21 @@ describe('history compact checkpoint', () => {
const valid = buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: [textEvent(0), textEvent(1)],
- summary: 'legacy summary without sections but complete.',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('complete summary'),
now: 10,
});
// A truncated fragment that would otherwise win by coverage: the load gate
// must drop it and fall back to the previous complete checkpoint (#3041).
- const poisoned = buildHistoryCompactCheckpoint({
- sessionId: 'session-1',
- coveredRuntimeEvents: [textEvent(0), textEvent(1), textEvent(2)],
- summary: 'stops mid-thought...',
- summaryFormat: 'legacy_freeform',
- previousCheckpointId: valid.checkpointId,
- now: 20,
- });
- const runIds = ['run-valid', 'run-poisoned'];
- const store = new StubAgentRunStore(
- new Map([
- ['run-valid', [checkpointEvent('ledger-valid', 'run-valid', valid, 10)]],
- ['run-poisoned', [checkpointEvent('ledger-poisoned', 'run-poisoned', poisoned, 20)]],
- ]),
- );
-
- const loaded = await loadLatestHistoryCompactCheckpointFromRunLedger(
- store,
- 'session-1',
- runIds,
- );
-
- assert.equal(loaded?.checkpointId, valid.checkpointId);
- });
-
- test('uses the shared fence scan to quarantine only an unclosed legacy summary', async () => {
- const valid = buildHistoryCompactCheckpoint({
- sessionId: 'session-1',
- coveredRuntimeEvents: [textEvent(0), textEvent(1)],
- summary: 'Legacy context:\n\n```ts\nconst ready = true;\n```',
- summaryFormat: 'legacy_freeform',
- now: 10,
- });
- const poisoned = buildHistoryCompactCheckpoint({
- sessionId: 'session-1',
- coveredRuntimeEvents: [textEvent(0), textEvent(1), textEvent(2)],
- summary: 'Legacy context:\n\n```ts\nconst ready =',
- summaryFormat: 'legacy_freeform',
- previousCheckpointId: valid.checkpointId,
- now: 20,
- });
+ const poisoned = {
+ ...buildHistoryCompactCheckpoint({
+ sessionId: 'session-1',
+ coveredRuntimeEvents: [textEvent(0), textEvent(1), textEvent(2)],
+ summary: STRUCTURED_SUMMARY,
+ previousCheckpointId: valid.checkpointId,
+ now: 20,
+ }),
+ summary: '## Goal\nstops mid-thought...',
+ };
const runIds = ['run-valid', 'run-poisoned'];
const store = new StubAgentRunStore(
new Map([
@@ -585,20 +544,13 @@ describe('history compact checkpoint', () => {
assert.equal(loaded?.checkpointId, valid.checkpointId);
});
- test('stamps new text checkpoints with the sectioned format; legacy_freeform stays unmarked', () => {
+ test('stamps new text checkpoints with the sectioned format', () => {
const stamped = buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: [textEvent(0)],
summary: STRUCTURED_SUMMARY,
});
assert.equal(stamped.version === 2 ? stamped.summaryFormat : undefined, 'sections_v1');
- const legacy = buildHistoryCompactCheckpoint({
- sessionId: 'session-1',
- coveredRuntimeEvents: [textEvent(0)],
- summary: 'legacy free-form summary',
- summaryFormat: 'legacy_freeform',
- });
- assert.equal(legacy.version === 2 ? legacy.summaryFormat : undefined, undefined);
});
test('the builder refuses to mint the sectioned marker for unvalidated text', () => {
@@ -631,11 +583,21 @@ describe('history compact checkpoint', () => {
);
});
+ test('shape validation rejects unmarked V2 checkpoints from 0.1.x', () => {
+ const stamped = buildHistoryCompactCheckpoint({
+ sessionId: 'session-1',
+ coveredRuntimeEvents: [textEvent(0)],
+ summary: STRUCTURED_SUMMARY,
+ });
+ const { summaryFormat: _summaryFormat, ...unmarked } = stamped;
+
+ assert.equal(validateHistoryCompactCheckpointShape(unmarked, 'session-1'), false);
+ });
+
test('a marked checkpoint is held to the complete predicate at load', async () => {
// A section-less summary written through a seam that bypassed the write
// gates (direct recorder, older copy) but carrying the sectioned marker
- // must never become authoritative again after restart; the unmarked
- // legacy policy stays truncation-only.
+ // must never become authoritative again after restart.
const valid = buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: [textEvent(0), textEvent(1)],
@@ -644,18 +606,16 @@ describe('history compact checkpoint', () => {
});
// The builder refuses to mint the marker for unvalidated text, so a
// malformed marked checkpoint can only exist as pre-existing durable data
- // (or via a hand-rolled object) — modeled here by restamping a legacy
- // build.
+ // (or via a hand-rolled object).
const markedMalformed = {
...buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: [textEvent(0), textEvent(1), textEvent(2)],
- summary: 'complete-sounding free-form prose without the mandated sections.',
- summaryFormat: 'legacy_freeform',
+ summary: STRUCTURED_SUMMARY,
previousCheckpointId: valid.checkpointId,
now: 20,
}),
- summaryFormat: 'sections_v1' as const,
+ summary: 'complete-sounding free-form prose without the mandated sections.',
};
const runIds = ['run-valid', 'run-marked'];
const store = new StubAgentRunStore(
@@ -678,16 +638,17 @@ describe('history compact checkpoint', () => {
const valid = buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: [textEvent(0)],
- summary: 'canonical complete summary',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('canonical complete summary'),
});
const canonicalEvent = checkpointEvent('canonical-event', 'run-canonical', valid, 20);
- const poisoned = buildHistoryCompactCheckpoint({
- sessionId: 'session-1',
- coveredRuntimeEvents: [textEvent(0), textEvent(1)],
- summary: 'projection fragment cut off:',
- summaryFormat: 'legacy_freeform',
- });
+ const poisoned = {
+ ...buildHistoryCompactCheckpoint({
+ sessionId: 'session-1',
+ coveredRuntimeEvents: [textEvent(0), textEvent(1)],
+ summary: STRUCTURED_SUMMARY,
+ }),
+ summary: '## Goal\nprojection fragment cut off:',
+ };
const poisonedProjection = checkpointEvent('projection-event', 'run-projection', poisoned, 30);
const replacedEventIds: Array = [];
const store = {
@@ -717,14 +678,12 @@ describe('history compact checkpoint', () => {
const furthest = buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: [textEvent(0), textEvent(1), textEvent(2)],
- summary: 'furthest coverage',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('furthest coverage'),
});
const stale = buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: [textEvent(0), textEvent(1)],
- summary: 'stale coverage',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('stale coverage'),
});
const runIds = ['run-furthest', 'run-stale'];
const store = new StubAgentRunStore(
@@ -748,23 +707,20 @@ describe('history compact checkpoint', () => {
const first = buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: source,
- summary: 'first',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('first'),
now: 10,
});
const second = buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: source,
- summary: 'second',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('second'),
previousCheckpointId: first.checkpointId,
now: 20,
});
const tip = buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: source,
- summary: 'tip',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('tip'),
previousCheckpointId: second.checkpointId,
now: 30,
});
@@ -797,8 +753,7 @@ describe('history compact checkpoint', () => {
const checkpoint = buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: [textEvent(0), textEvent(1)],
- summary: 'bounded projection',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('bounded projection'),
});
const projectedEvent = checkpointEvent('projection-event', 'run-projection', checkpoint, 20);
const store = {
@@ -834,8 +789,7 @@ describe('history compact checkpoint', () => {
const checkpoint = buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: [textEvent(0), textEvent(1)],
- summary: 'recovered checkpoint',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('recovered checkpoint'),
});
const event = checkpointEvent('recovered-event', 'run-recovered', checkpoint, 20);
const repaired: Array = [];
@@ -866,8 +820,7 @@ describe('history compact checkpoint', () => {
const checkpoint = buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: [textEvent(0)],
- summary: 'recovered checkpoint',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('recovered checkpoint'),
});
const event = checkpointEvent('recovered-event', 'run-recovered', checkpoint, 20);
let repaired = false;
@@ -891,8 +844,7 @@ describe('history compact checkpoint', () => {
const checkpoint = buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: [textEvent(0)],
- summary: 'canonical checkpoint',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('canonical checkpoint'),
});
const canonicalEvent = checkpointEvent('canonical-event', 'run-canonical', checkpoint, 20);
const invalidProjection = {
@@ -951,8 +903,7 @@ describe('history compact checkpoint', () => {
const checkpoint = buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: events.slice(0, 4),
- summary: 'checkpoint summary',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('checkpoint summary'),
});
const replay = applyRuntimeEventHistoryCompact(events, {
@@ -983,8 +934,7 @@ describe('history compact checkpoint', () => {
const checkpoint = buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: events.slice(0, 4),
- summary: 'recovery checkpoint summary',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('recovery checkpoint summary'),
});
// The raw history is deliberately small. Once a durable
diff --git a/packages/runtime/src/__tests__/history-compact-mid-turn-checkpoint.test.ts b/packages/runtime/src/__tests__/history-compact-mid-turn-checkpoint.test.ts
index 99b04fad39..a1cf96236c 100644
--- a/packages/runtime/src/__tests__/history-compact-mid-turn-checkpoint.test.ts
+++ b/packages/runtime/src/__tests__/history-compact-mid-turn-checkpoint.test.ts
@@ -28,6 +28,7 @@ import {
validateHistoryCompactCheckpointShape,
} from '../history-compact-checkpoint.js';
import { applyRuntimeEventHistoryCompact } from '../history-compaction.js';
+import { sectionedSummary } from './history-compact-test-fixtures.js';
describe('mid-turn history compact checkpoint', () => {
test('builds a mid_turn checkpoint that re-renders the covered head anchor verbatim', () => {
@@ -41,8 +42,7 @@ describe('mid-turn history compact checkpoint', () => {
const checkpoint = buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: events,
- summary: 'Prior work plus the current turn opening.',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('Prior work plus the current turn opening.'),
phase: 'mid_turn',
headAnchor: { runtimeEventId: 'anchor', turnId: 'turn-1' },
now: 1_800_000_010_000,
@@ -84,8 +84,7 @@ describe('mid-turn history compact checkpoint', () => {
buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: events,
- summary: 'x',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('x'),
phase: 'mid_turn',
}),
/requires a head anchor/,
@@ -95,8 +94,7 @@ describe('mid-turn history compact checkpoint', () => {
buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: events,
- summary: 'x',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('x'),
phase: 'mid_turn',
headAnchor: { runtimeEventId: 'missing', turnId: 'turn-1' },
}),
@@ -112,8 +110,7 @@ describe('mid-turn history compact checkpoint', () => {
buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: events,
- summary: 'x',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('x'),
phase: 'mid_turn',
headAnchor: { runtimeEventId: 'a', turnId: 'turn-9' },
}),
@@ -125,8 +122,7 @@ describe('mid-turn history compact checkpoint', () => {
buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: events,
- summary: 'x',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('x'),
phase: 'mid_turn',
headAnchor: { runtimeEventId: 'b', turnId: 'turn-1' },
}),
@@ -150,8 +146,7 @@ describe('mid-turn history compact checkpoint', () => {
buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: events,
- summary: 'x',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('x'),
phase: 'mid_turn',
headAnchor: { runtimeEventId: 'prior-user', turnId: 'turn-0' },
}),
@@ -163,8 +158,7 @@ describe('mid-turn history compact checkpoint', () => {
const checkpoint = buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: events,
- summary: 'mid turn summary',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('mid turn summary'),
phase: 'mid_turn',
headAnchor: { runtimeEventId: 'anchor', turnId: 'turn-1' },
});
@@ -190,8 +184,7 @@ describe('mid-turn history compact checkpoint', () => {
buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: events,
- summary: 'x',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('x'),
phase: 'mid_turn',
headAnchor: { runtimeEventId: 'a', turnId: 'turn-1' },
}),
@@ -208,8 +201,7 @@ describe('mid-turn history compact checkpoint', () => {
const checkpoint = buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: events.slice(0, 2),
- summary: 'mid turn summary',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('mid turn summary'),
phase: 'mid_turn',
headAnchor: { runtimeEventId: 'anchor', turnId: 'turn-1' },
});
@@ -251,15 +243,13 @@ describe('mid-turn history compact checkpoint', () => {
const implicit = buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: events,
- summary: 'same',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('same'),
now: 5,
});
const explicit = buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: events,
- summary: 'same',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('same'),
phase: 'pre_turn',
now: 5,
});
@@ -269,8 +259,7 @@ describe('mid-turn history compact checkpoint', () => {
const mid = buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: events,
- summary: 'same',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('same'),
now: 5,
phase: 'mid_turn',
headAnchor: { runtimeEventId: 'a', turnId: 'turn-0' },
@@ -289,8 +278,7 @@ describe('mid-turn history compact checkpoint', () => {
const checkpoint = buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: events.slice(0, 4),
- summary: 'mid turn summary',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('mid turn summary'),
phase: 'mid_turn',
headAnchor: { runtimeEventId: 'anchor', turnId: 'turn-1' },
});
diff --git a/packages/runtime/src/__tests__/history-compact-summarizer.test.ts b/packages/runtime/src/__tests__/history-compact-summarizer.test.ts
index 9d93700b86..158a41f336 100644
--- a/packages/runtime/src/__tests__/history-compact-summarizer.test.ts
+++ b/packages/runtime/src/__tests__/history-compact-summarizer.test.ts
@@ -38,6 +38,7 @@ import {
} from '../history-compact-summarizer.js';
import { buildHistoryCompactCheckpoint } from '../history-compact-checkpoint.js';
import { SUMMARY_FORMAT_TEMPLATE } from '../history-compact-summary-validation.js';
+import { sectionedSummary } from './history-compact-test-fixtures.js';
const ts = 1_700_000_000_000;
let __seq = 0;
@@ -1242,8 +1243,7 @@ describe('buildLlmHistorySummarizer', () => {
const previousCheckpoint = buildHistoryCompactCheckpoint({
sessionId: 'sess-1',
coveredRuntimeEvents: [old],
- summary: 'PRIOR_SUMMARY',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('PRIOR_SUMMARY'),
});
const summarize = buildLlmHistorySummarizer({
resolveModel: () => 'fake-model',
@@ -1356,8 +1356,7 @@ describe('buildLlmHistorySummarizer', () => {
const previousCheckpoint = buildHistoryCompactCheckpoint({
sessionId: 'sess-1',
coveredRuntimeEvents: [old],
- summary: 'PRIOR_SUMMARY',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('PRIOR_SUMMARY'),
});
const input = inputWith([old, newer]);
diff --git a/packages/runtime/src/__tests__/history-compact-test-fixtures.ts b/packages/runtime/src/__tests__/history-compact-test-fixtures.ts
new file mode 100644
index 0000000000..3041927a36
--- /dev/null
+++ b/packages/runtime/src/__tests__/history-compact-test-fixtures.ts
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+
+export function sectionedSummary(goal: string): string {
+ return `## Goal\n${goal}\n\n## Progress\n- done\n\n## Next Steps\n1. continue\n\n## Critical Context\n- (none)`;
+}
diff --git a/packages/runtime/src/__tests__/memory-extraction.test.ts b/packages/runtime/src/__tests__/memory-extraction.test.ts
index 5d9eb07b69..e420c331c2 100644
--- a/packages/runtime/src/__tests__/memory-extraction.test.ts
+++ b/packages/runtime/src/__tests__/memory-extraction.test.ts
@@ -22,6 +22,7 @@ import { describe, test } from 'node:test';
import type { RuntimeEvent } from '@maka/core/runtime-event';
import { buildHistoryCompactCheckpoint } from '../history-compact-checkpoint.js';
+import { sectionedSummary } from './history-compact-test-fixtures.js';
import {
bindProviderVisibleEvidence,
projectMemoryExtractionEvidence,
@@ -204,8 +205,7 @@ describe('bounded Memory Extraction', () => {
const previousCheckpoint = buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: [compacted],
- summary: 'The old summary remains interpretation context.',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('The old summary remains interpretation context.'),
});
const source = buildMemoryCompactionSourceContext(
[
diff --git a/packages/runtime/src/__tests__/model-history-timeline.test.ts b/packages/runtime/src/__tests__/model-history-timeline.test.ts
new file mode 100644
index 0000000000..4811cd4afb
--- /dev/null
+++ b/packages/runtime/src/__tests__/model-history-timeline.test.ts
@@ -0,0 +1,224 @@
+/*
+ * 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.
+ */
+
+import assert from 'node:assert/strict';
+import { test } from 'node:test';
+import {
+ buildRuntimeEventModelReplayPlan,
+ buildRuntimeEventReplayTimeline,
+} from '../model-history.js';
+import type { RuntimeEvent } from '@maka/core/runtime-event';
+
+test('model history keeps reused step ids as separate chronological segments', () => {
+ const items = buildRuntimeEventModelReplayPlan([
+ assistantText('text-a', 'shared-step', 'Text A'),
+ assistantText('text-b', 'intervening-step', 'Text B'),
+ toolCall('call', 'shared-step'),
+ toolResult('result'),
+ ]).items;
+
+ assert.deepEqual(
+ buildRuntimeEventReplayTimeline(items).map((entry) => {
+ if (entry.kind !== 'assistant_step') return entry.kind;
+ return {
+ stepId: entry.stepId,
+ text: entry.text?.content,
+ calls: entry.calls.map(({ call, result }) => ({
+ id: call.toolCallId,
+ settled: result !== undefined,
+ })),
+ };
+ }),
+ [
+ { stepId: 'shared-step', text: 'Text A', calls: [] },
+ { stepId: 'intervening-step', text: 'Text B', calls: [] },
+ { stepId: 'shared-step', text: undefined, calls: [{ id: 'read-1', settled: true }] },
+ ],
+ );
+});
+
+test('model history separates settled legacy calls but keeps overlapping calls together', () => {
+ const settledItems = buildRuntimeEventModelReplayPlan([
+ toolCall('call-1', undefined, 'read-1'),
+ toolResult('result-1', 'read-1', 'first'),
+ toolCall('call-2', undefined, 'read-2'),
+ toolResult('result-2', 'read-2', 'second'),
+ ]).items;
+ const overlappingItems = buildRuntimeEventModelReplayPlan([
+ toolCall('call-1', undefined, 'read-1'),
+ toolCall('call-2', undefined, 'read-2'),
+ toolResult('result-1', 'read-1', 'first'),
+ toolResult('result-2', 'read-2', 'second'),
+ ]).items;
+
+ assert.deepEqual(
+ buildRuntimeEventReplayTimeline(settledItems).map((entry) =>
+ entry.kind === 'assistant_step' ? entry.calls.map(({ call }) => call.toolCallId) : [],
+ ),
+ [['read-1'], ['read-2']],
+ );
+ assert.deepEqual(
+ buildRuntimeEventReplayTimeline(overlappingItems).map((entry) =>
+ entry.kind === 'assistant_step' ? entry.calls.map(({ call }) => call.toolCallId) : [],
+ ),
+ [['read-1', 'read-2']],
+ );
+});
+
+test('model history pairs reused tool call ids by durable occurrence', () => {
+ const items = buildRuntimeEventModelReplayPlan([
+ toolCall('call-1', 'step-1'),
+ toolResult('result-1', 'read-1', 'first'),
+ toolCall('call-2', 'step-2'),
+ toolResult('result-2', 'read-1', 'second'),
+ ]).items;
+
+ assert.deepEqual(
+ buildRuntimeEventReplayTimeline(items).flatMap((entry) =>
+ entry.kind === 'assistant_step' ? entry.calls.map(({ result }) => result?.output) : [],
+ ),
+ ['first', 'second'],
+ );
+});
+
+test('model history does not pair an orphan result with a later reused call id', () => {
+ const items = buildRuntimeEventModelReplayPlan([
+ toolResult('orphan-result', 'read-1', 'orphan'),
+ toolCall('call', 'step-1'),
+ toolResult('matching-result', 'read-1', 'matching'),
+ ]).items;
+
+ assert.deepEqual(
+ buildRuntimeEventReplayTimeline(items).flatMap((entry) =>
+ entry.kind === 'assistant_step' ? entry.calls.map(({ result }) => result?.output) : [],
+ ),
+ ['matching'],
+ );
+});
+
+test('model history scopes reused tool call ids to their invocation', () => {
+ const callA = inInvocation(toolCall('call-a', 'shared-step'), 'a');
+ const callB = inInvocation(toolCall('call-b', 'shared-step'), 'b');
+ const resultB = inInvocation(toolResult('result-b', 'read-1', 'second'), 'b');
+
+ const plan = buildRuntimeEventModelReplayPlan([callA, callB, resultB]);
+
+ assert.deepEqual(
+ plan.diagnostics
+ .filter(({ code }) => code === 'unmatched_tool_call')
+ .map(({ eventId }) => eventId),
+ ['call-a'],
+ );
+ assert.deepEqual(
+ buildRuntimeEventReplayTimeline(plan.items).flatMap((entry) =>
+ entry.kind === 'assistant_step'
+ ? entry.calls.map(({ call, result }) => ({
+ call: call.eventId,
+ result: result?.eventId,
+ }))
+ : [],
+ ),
+ [{ call: 'call-b', result: 'result-b' }],
+ );
+});
+
+test('model history keeps reused step ids separate across invocations', () => {
+ const textA = inInvocation(assistantText('text-a', 'shared-step', 'First invocation text'), 'a');
+ const callB = inInvocation(toolCall('call-b', 'shared-step'), 'b');
+ const resultB = inInvocation(toolResult('result-b'), 'b');
+
+ const items = buildRuntimeEventModelReplayPlan([textA, callB, resultB]).items;
+
+ assert.deepEqual(
+ buildRuntimeEventReplayTimeline(items).map((entry) => {
+ if (entry.kind !== 'assistant_step') return entry.kind;
+ return {
+ text: entry.text?.eventId,
+ calls: entry.calls.map(({ call }) => call.eventId),
+ };
+ }),
+ [
+ { text: 'text-a', calls: [] },
+ { text: undefined, calls: ['call-b'] },
+ ],
+ );
+});
+
+function inInvocation(event: RuntimeEvent, suffix: string): RuntimeEvent {
+ return {
+ ...event,
+ invocationId: `invocation-${suffix}`,
+ runId: `run-${suffix}`,
+ turnId: `turn-${suffix}`,
+ };
+}
+
+function assistantText(id: string, stepId: string, text: string): RuntimeEvent {
+ return event({
+ id,
+ role: 'model',
+ author: 'agent',
+ refs: { providerEventId: stepId },
+ content: { kind: 'text', text },
+ });
+}
+
+function toolCall(id: string, stepId?: string, toolCallId = 'read-1'): RuntimeEvent {
+ return event({
+ id,
+ role: 'model',
+ author: 'agent',
+ refs: stepId ? { stepId } : undefined,
+ content: {
+ kind: 'function_call',
+ id: toolCallId,
+ name: 'Read',
+ args: { path: 'notes.md' },
+ },
+ });
+}
+
+function toolResult(id: string, toolCallId = 'read-1', result = 'contents'): RuntimeEvent {
+ return event({
+ id,
+ role: 'tool',
+ author: 'tool',
+ content: {
+ kind: 'function_response',
+ id: toolCallId,
+ name: 'Read',
+ result,
+ },
+ });
+}
+
+function event(
+ input: Pick &
+ Partial>,
+): RuntimeEvent {
+ return {
+ ...input,
+ invocationId: 'invocation-1',
+ runId: 'run-1',
+ sessionId: 'session-1',
+ turnId: 'turn-1',
+ ts: 1,
+ partial: false,
+ };
+}
diff --git a/packages/runtime/src/__tests__/overflow-reactive-recovery.test.ts b/packages/runtime/src/__tests__/overflow-reactive-recovery.test.ts
index a778ba50c6..36a3b8e2e9 100644
--- a/packages/runtime/src/__tests__/overflow-reactive-recovery.test.ts
+++ b/packages/runtime/src/__tests__/overflow-reactive-recovery.test.ts
@@ -30,6 +30,7 @@ import { z } from 'zod';
import type { ModelCallCommit } from '@maka/core/agent-run';
import type { RuntimeInvocationRecord } from '@maka/core/runtime-invocation';
import { decodeModelCallAttempt, type ModelCallAttempt } from '@maka/core/model-call-attempt';
+import { sectionedSummary } from './history-compact-test-fixtures.js';
import { AiSdkBackend } from '../ai-sdk-backend.js';
import {
LATEST_CONTEXT_PROJECTION_TYPE,
@@ -1265,8 +1266,7 @@ describe('reactive overflow recovery in the streaming backend', () => {
const checkpoint = buildHistoryCompactCheckpoint({
sessionId: 'session-1',
coveredRuntimeEvents: fixture.priorEvents,
- summary: 'EARLIER_TURN_SUMMARY',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('EARLIER_TURN_SUMMARY'),
});
carried = checkpoint;
await runTurn(fixture);
@@ -1295,8 +1295,7 @@ describe('reactive overflow recovery in the streaming backend', () => {
coveredRuntimeEvents: [
runtimeTextEvent('never-happened', 'turn-x', 'user', 'AN EVENT THIS LEDGER NEVER HELD'),
],
- summary: 'SUMMARY_OF_ANOTHER_HISTORY',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('SUMMARY_OF_ANOTHER_HISTORY'),
});
const fixture = buildReactiveFixture({
script: ['done'],
diff --git a/packages/runtime/src/__tests__/session-manager.test.ts b/packages/runtime/src/__tests__/session-manager.test.ts
index 3acbdb5837..4aff33d521 100644
--- a/packages/runtime/src/__tests__/session-manager.test.ts
+++ b/packages/runtime/src/__tests__/session-manager.test.ts
@@ -18,6 +18,7 @@
*/
import { nextId } from '@maka/core/test-only/async-primitives';
+import { sectionedSummary } from './history-compact-test-fixtures.js';
import { runtimeInvocationFailureClass } from '../runtime-event-read-model.js';
import { describe, test } from 'node:test';
import assert from 'node:assert/strict';
@@ -2355,9 +2356,9 @@ describe('SessionManager child-session runtime primitive', () => {
childContext?.tools?.map((tool) => tool.name),
['Read', 'Glob', 'Grep'],
);
- assert.deepStrictEqual(
+ assert.strictEqual(
backendsBySession.get(result.childSessionId)?.sendInputs[0]?.context,
- [],
+ undefined,
);
assert.strictEqual(
backendsBySession
@@ -8956,13 +8957,9 @@ describe('SessionManager permission mode updates', () => {
secondInput.runtimeContext?.map((event) => event.turnId),
['turn-1', 'turn-1', 'turn-1', 'turn-1'],
);
- const turnState = secondInput.context.find(
- (message) => message.type === 'turn_state' && message.turnId === 'turn-1',
- );
- if (turnState?.type !== 'turn_state')
- throw new Error('prior failed turn_state was not projected');
- assert.strictEqual(turnState.status, 'failed');
- assert.strictEqual(turnState.errorClass, 'tool_failed');
+ const failed = secondInput.runtimeContext?.find((event) => event.status === 'failed');
+ assert.strictEqual(failed?.actions?.stateDelta?.failureClass, 'tool_failed');
+ assert.strictEqual(secondInput.context, undefined);
});
test('next parent turn excludes child run RuntimeEvents from model context', async () => {
@@ -9049,12 +9046,7 @@ describe('SessionManager permission mode updates', () => {
secondInput.runtimeContext?.some((event) => event.turnId === 'child-turn'),
false,
);
- assert.strictEqual(
- secondInput.context.some(
- (message) => message.type === 'user' && message.turnId === 'child-turn',
- ),
- false,
- );
+ assert.strictEqual(secondInput.context, undefined);
});
test('stopSession owns an active Run and a parent turn admitted before reservation', async () => {
@@ -11098,8 +11090,7 @@ describe('SessionManager permission mode updates', () => {
content: { kind: 'text', text: `source ${index}` },
}),
),
- summary: 'durable checkpoint before projection loss',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('durable checkpoint before projection loss'),
});
const durableEvent = makeRunEvent({
sessionId: session.id,
@@ -12535,8 +12526,7 @@ class HistoryCompactCheckpointBackend implements AgentBackend {
content: { kind: 'text', text: 'source' },
},
],
- summary: 'persist the bounded checkpoint',
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary('persist the bounded checkpoint'),
});
this.ctx.recordHistoryCompactCheckpoint?.(
{ ...checkpoint, checkpointId: 'hcheckpoint-test' },
@@ -12588,8 +12578,7 @@ class SameCoverageCheckpointReplacementProbeBackend implements AgentBackend {
buildHistoryCompactCheckpoint({
sessionId: this.sessionId,
coveredRuntimeEvents,
- summary: `${input.turnId} summary`,
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary(`${input.turnId} summary`),
...(current ? { previousCheckpointId: current.checkpointId } : {}),
}),
input.turnId,
@@ -12647,8 +12636,7 @@ class CheckpointRecorderContractProbeBackend implements AgentBackend {
buildHistoryCompactCheckpoint({
sessionId: this.sessionId,
coveredRuntimeEvents,
- summary: `${input.turnId} checkpoint`,
- summaryFormat: 'legacy_freeform',
+ summary: sectionedSummary(`${input.turnId} checkpoint`),
}),
input.turnId,
);
diff --git a/packages/runtime/src/__tests__/session-recap.test.ts b/packages/runtime/src/__tests__/session-recap.test.ts
index f717047340..2a7d9d4fcf 100644
--- a/packages/runtime/src/__tests__/session-recap.test.ts
+++ b/packages/runtime/src/__tests__/session-recap.test.ts
@@ -66,13 +66,14 @@ test('session recap keeps bounded evidence from one oversized latest turn', () =
assert.ok(serialized.length <= 13_000);
});
-test('session recap bounds an oversized tool result without splitting its protocol pair', () => {
+test('session recap includes a concise durable outcome without tool protocol or raw output', () => {
const oversizedOutput = 'tool-output-sentinel '.repeat(2_000);
+ const durableOutcome = 'state.txt says ready=true';
const messages = buildSessionRecapMessages({
events: [
textEvent('latest-user', 'turn-1', 'user', 'Inspect the current state.'),
toolCallEvent('call-event', 'call-1', 'turn-1'),
- toolResultEvent('result-event', 'call-1', 'turn-1', oversizedOutput),
+ toolResultEvent('result-event', 'call-1', 'turn-1', oversizedOutput, durableOutcome),
],
connection: connection(),
modelId: 'gpt-4',
@@ -87,15 +88,75 @@ test('session recap bounds an oversized tool result without splitting its protoc
.filter((part) => part.type === 'tool-call' || part.type === 'tool-result')
.map((part) => ({ type: part.type, toolCallId: part.toolCallId })),
),
- [
- { type: 'tool-call', toolCallId: 'call-1' },
- { type: 'tool-result', toolCallId: 'call-1' },
- ],
+ [],
);
+ assert.equal(serialized.includes(durableOutcome), true);
assert.equal(serialized.includes(oversizedOutput), false);
assert.ok(serialized.length <= 13_000);
});
+test('session recap budgets only the evidence it sends', () => {
+ const earlierSentinel = 'EARLIER-RECAP-SENTINEL';
+ const oversizedArgs = 'tool-args-sentinel '.repeat(4_000);
+ const call = toolCallEvent('call-event', 'call-1', 'turn-2');
+ const messages = buildSessionRecapMessages({
+ events: [
+ textEvent('earlier-user', 'turn-1', 'user', earlierSentinel),
+ textEvent('latest-user', 'turn-2', 'user', 'Inspect the current state.'),
+ {
+ ...call,
+ content: {
+ kind: 'function_call',
+ id: 'call-1',
+ name: 'Read',
+ args: { query: oversizedArgs },
+ },
+ },
+ toolResultEvent('result-event', 'call-1', 'turn-2', 'raw output', 'state is ready'),
+ ],
+ connection: {
+ ...connection(),
+ defaultModel: 'declared-16k-model',
+ relayModelProfiles: { 'declared-16k-model': { contextWindow: 16_384 } },
+ },
+ modelId: 'declared-16k-model',
+ });
+ const serialized = JSON.stringify(messages);
+
+ assert.equal(serialized.includes(earlierSentinel), true);
+ assert.equal(serialized.includes(oversizedArgs), false);
+});
+
+test('session recap excludes model-hidden tool outcomes', () => {
+ const messages = buildSessionRecapMessages({
+ events: [
+ {
+ ...toolResultEvent(
+ 'hidden-result',
+ 'nested-call',
+ 'turn-1',
+ 'raw nested output',
+ 'internal nested outcome',
+ ),
+ modelVisibility: 'hidden',
+ },
+ ],
+ connection: {
+ ...connection(),
+ defaultModel: 'declared-4k-model',
+ relayModelProfiles: { 'declared-4k-model': { contextWindow: 4_096 } },
+ },
+ modelId: 'declared-4k-model',
+ });
+
+ assert.deepEqual(messages, [
+ {
+ role: 'user',
+ content: SESSION_RECAP_INSTRUCTION,
+ },
+ ]);
+});
+
test('session recap treats a zero evidence budget as no evidence, not unbounded input', () => {
const sentinel = 'ZERO-BUDGET-SENTINEL';
const messages = buildSessionRecapMessages({
@@ -153,7 +214,13 @@ function toolCallEvent(id: string, callId: string, turnId: string): RuntimeEvent
};
}
-function toolResultEvent(id: string, callId: string, turnId: string, result: string): RuntimeEvent {
+function toolResultEvent(
+ id: string,
+ callId: string,
+ turnId: string,
+ result: string,
+ durableOutcome: string,
+): RuntimeEvent {
return {
id,
sessionId: 'session-1',
@@ -164,7 +231,13 @@ function toolResultEvent(id: string, callId: string, turnId: string, result: str
partial: false,
role: 'tool',
author: 'tool',
- content: { kind: 'function_response', id: callId, name: 'Read', result },
+ content: {
+ kind: 'function_response',
+ id: callId,
+ name: 'Read',
+ result,
+ modelProjection: { version: 1, kind: 'text', text: durableOutcome },
+ },
};
}
diff --git a/packages/runtime/src/agent-run.ts b/packages/runtime/src/agent-run.ts
index 7f119bfda3..ff919edf1d 100644
--- a/packages/runtime/src/agent-run.ts
+++ b/packages/runtime/src/agent-run.ts
@@ -74,7 +74,6 @@ import type { AgentBackend, BackendSendInput } from '@maka/core/backend-types';
import type { RunTraceEvent } from './run-trace.js';
import type { StopSessionInput } from './session-manager.js';
import type { HistoryCompactCheckpoint } from './history-compact-checkpoint.js';
-import { projectRuntimeEventsToStoredMessages } from './runtime-event-read-model.js';
import {
buildPriorRuntimeContext as buildPriorRuntimeContextProjection,
type PriorRuntimeContext,
@@ -704,11 +703,6 @@ export class AgentRun {
await this.input.hooks.updateStatus(this.sessionId, 'running', undefined, this.lastTs);
const priorRuntimeContext = await this.buildPriorRuntimeContext();
- const projectionContext = priorRuntimeContext
- ? projectRuntimeEventsToStoredMessages(priorRuntimeContext.events, {
- invocations: priorRuntimeContext.invocations,
- }).messages
- : [];
return {
backend: this.active.backend,
@@ -727,7 +721,6 @@ export class AgentRun {
? { directoryReferences: this.input.userInput.directoryReferences }
: {}),
...(this.input.userInput.quotes ? { quotes: this.input.userInput.quotes } : {}),
- context: projectionContext,
...(priorRuntimeContext
? {
runtimeContext: priorRuntimeContext.events,
diff --git a/packages/runtime/src/ai-sdk-backend.ts b/packages/runtime/src/ai-sdk-backend.ts
index f314471fef..2a065fffb2 100644
--- a/packages/runtime/src/ai-sdk-backend.ts
+++ b/packages/runtime/src/ai-sdk-backend.ts
@@ -18,59 +18,14 @@
*/
/**
- * AiSdkBackend — single backend for all LLM providers via Vercel AI SDK.
- *
- * Provides one `streamText` API across Anthropic / OpenAI / Google / DeepSeek /
- * OpenAI-compatible endpoints, while keeping all of our home-grown
- * machinery: session sandbox boundaries, materializer, AsyncEventQueue,
- * SessionStore SQLite persistence.
- *
- * Maka owns the agent loop. Each ModelAdapter call performs exactly one
- * provider request; returned tool calls settle through ToolRuntime, become
- * durable, and are reloaded before the next provider request.
- *
- * Design:
- * send()
- * ├─ build AsyncEventQueue
- * ├─ resolve LanguageModelV2 via deps.modelFactory(connection, modelId)
- * ├─ expose schema-only tools to the provider
- * ├─ background task: project → stream one step → settle → reload
- * └─ yield from queue
+ * Session-level AI SDK backend. It wires provider, compaction, projection,
+ * telemetry, and tool services once, then delegates each `send()` to an
+ * isolated AiSdkTurn. Provider-message construction and turn execution live in
+ * their own modules; this file owns composition and cross-turn control only.
*/
-import type {
- SessionEvent,
- CompleteEvent,
- AbortEvent,
- ErrorEvent,
- TextCompleteEvent,
- ThinkingCompleteEvent,
- TokenUsageEvent,
- TextDeltaEvent,
- ThinkingDeltaEvent,
- ProviderRetryEvent,
- ProviderRetryReason,
- ToolResultEvent,
- ToolResultContent,
- ToolStartEvent,
- StorageRef,
- AttachmentRef,
- DirectoryReference,
- QuoteRef,
-} from '@maka/core/events';
-import type {
- StoredMessage,
- AssistantMessage,
- AssistantStepContentKind,
- AssistantThinkingPart,
- ToolCallMessage,
- ToolResultMessage,
- PermissionDecisionMessage,
- TokenUsageMessage,
- SystemNoteMessage,
- BackendKind,
- SessionHeader,
-} from '@maka/core/session';
+import type { SessionEvent } from '@maka/core/events';
+import type { BackendKind, SessionHeader, StoredMessage } from '@maka/core/session';
import type {
AgentBackend,
BackendCompactHistoryInput,
@@ -78,169 +33,37 @@ import type {
BackendSendInput,
HostedInteractionBridge,
} from '@maka/core/backend-types';
-import type { RuntimeEvent } from '@maka/core/runtime-event';
import type { SandboxBoundaryResponse } from '@maka/core/sandbox-boundary';
import type { UserQuestionResponse } from '@maka/core/user-question';
-import { DEFAULT_TOOL_MODE, isToolMode, type ToolMode } from '@maka/core/tool-mode';
-import {
- resolveEffectiveOrchestration,
- type EffectiveOrchestration,
-} from '@maka/core/orchestration';
-import type { PlanToolResult } from './plan-tools.js';
-import {
- bindToolResultArchiveDecoder,
- type ToolResultArchiveCapability,
-} from './tool-result-archive-capability.js';
-import {
- YIELD_AGENT_GRAPH_TOOL_NAME,
- type YieldAgentGraphToolResult,
-} from './stream-graph-supervisor-tools.js';
+import type { EffectiveOrchestration } from '@maka/core/orchestration';
import type { AttachmentByteReader } from '@maka/core/attachments';
-import {
- MAX_PROVIDER_IMAGE_REQUEST_BYTES,
- PROVIDER_IMAGE_BUDGET_EXCEEDED_MESSAGE,
-} from '@maka/core/attachments';
-import { stripUndefinedDeep } from '@maka/core/tool-args-identity';
import { pricingModelKey } from '@maka/core/usage-stats/pricing';
-import type {
- LlmCallRecord,
- PricingConfig,
- ToolInvocationRecord,
-} from '@maka/core/usage-stats/types';
-import type { ContextBudgetDiagnostic } from '@maka/core/usage-stats/types';
-import type {
- JSONValue,
- ModelFinishReason,
- ModelMessage,
- ModelStepOutcome,
- ReasoningPart,
- ModelFailure,
- ModelToolSet,
- NormalizedUsage,
- ModelFailureKind,
- ToolCallPart,
- ToolResultOutput,
- UserContent,
-} from './model-protocol.js';
+import type { PricingConfig, ToolInvocationRecord } from '@maka/core/usage-stats/types';
import type { ModelCallCommit } from '@maka/core/agent-run';
-import Ajv, { type AnySchema, type ErrorObject, type ValidateFunction } from 'ajv';
-import Ajv2019 from 'ajv/dist/2019.js';
-import Ajv2020 from 'ajv/dist/2020.js';
-import { z } from 'zod';
+import type { ModelCallAttempt } from '@maka/core/model-call-attempt';
-import { AsyncEventQueue } from './async-queue.js';
import { AdmissionLimiter } from './admission-limiter.js';
import {
- type CodeModeExecutionResult,
- DEFAULT_CODE_MODE_EXECUTION_POLICY,
- executeCodeCell,
-} from './code-mode.js';
-import {
- StreamWatchdog,
- formatStreamWatchdogError,
- type StreamWatchdogInput,
- type StreamWatchdogPhase,
-} from './stream-watchdog.js';
-import {
- MAX_ACTIVE_CHILD_AGENT_RUNS_PER_TURN,
- MAX_ACTIVE_SUBAGENT_TOOLS_PER_TURN,
- TOOL_ERROR_RESULT_MAX_CHARS,
ToolRuntime,
- formatSyntheticToolErrorText,
- formatToolArgsViolationText,
- isRuntimeCommitBoundaryError,
type MakaTool,
type MakaToolContext,
- type DurableSessionEventSink,
type ToolRuntimeInput,
} from './tool-runtime.js';
import type { RuntimeCommitSink } from './runtime-commit-sink.js';
-import {
- ModelAdapter,
- type ModelFactoryInput,
- type NormalizedAiSdkUsage,
- type ModelStreamResult,
- type RepairableAiSdkToolCall,
-} from './model-adapter.js';
+import { ModelAdapter, type NormalizedAiSdkUsage } from './model-adapter.js';
import { buildProviderOptions } from './model-factory.js';
-import { persistedOpenAiResponsesStepMessages } from './openai-responses-continuation.js';
import type { OpenAiResponsesTransportState } from './openai-responses-websocket.js';
-import { nonCanonicalContentOrder } from './runtime-event-read-model.js';
-import {
- composeRequestProjection,
- type DispatchRequestShape,
- type RequestProjection,
- type RequestProjectionContext,
- type RequestProjectionStage,
-} from './request-projection.js';
-import {
- decodePlaintextResponsesReasoningState,
- replayPlaintextResponsesProviderOptions,
- responsesReasoningItemId,
-} from './responses-reasoning-state.js';
-import type { ActiveToolResultPruneDiagnosticPatch } from './active-tool-result-prune.js';
-import { toolResultOutput } from './tool-result-output.js';
-import { finitePositive } from './context-budget-helpers.js';
-import { compactionDecisionDiagnosticPatch } from './compaction-boundary.js';
-import type {
- AutomaticMemoryCompactionDecision,
- AutomaticMemoryCompactionDispatch,
- MidTurnCapacityCompactState,
- ProviderImageBudget,
-} from './ai-sdk-compaction.js';
-import {
- contextDiagnosticsCompactionOf,
- type ContextDiagnosticsCompaction,
-} from './context-diagnostics.js';
-import {
- AiSdkCompaction,
- hasActiveToolResultPruneDiagnosticPatch,
- hasBlockingReplayDiagnostics,
-} from './ai-sdk-compaction.js';
+import type { StreamWatchdogInput } from './stream-watchdog.js';
+import { AiSdkCompaction } from './ai-sdk-compaction.js';
import type { AiSdkCompactionCapabilities } from './ai-sdk-compaction-contract.js';
import type { ToolArtifactRecorder } from './tool-artifacts.js';
-import { durableProjectionToToolResultOutput } from './durable-tool-result-projection.js';
-import type { DurableToolResultProjection } from '@maka/core/durable-tool-result-projection';
-import { openAiChatReasoningFieldFromProviderOptions } from './openai-chat-reasoning-transport.js';
-import { RunTrace, type RunTraceRecorder } from './run-trace.js';
-import { SandboxCommandError } from './sandbox/errors.js';
-import {
- REQUEST_SANDBOX_BOUNDARY_TOOL_NAME,
- SANDBOX_BOUNDARY_DENIED_FOR_TURN,
- SANDBOX_BOUNDARY_FINALIZATION_PROMPT,
-} from './sandbox-boundary-tool.js';
-import { computeCost } from './telemetry/cost.js';
+import type { RunTraceRecorder } from './run-trace.js';
import { getBuiltinPricing } from './telemetry/builtin-pricing.js';
-import {
- admitProviderReasoningReplayItems,
- buildRuntimeEventModelReplayPlan,
- buildSteeringEnvelope,
- collectToolActivityTurnIds,
- compatibleProviderReasoningReplayEventIds,
- formatTextWithInlineRefs,
- steeringMessagesMissingFromBase,
- steeringModelMessage,
- steeringProviderOptions,
- stripSteeringMessages,
- type RuntimeEventModelReplayItem,
- type RuntimeEventModelReplayPlan,
- type RuntimeEventReplayFallbackGate,
-} from './model-history.js';
-import { toolSchemaCharsForDiagnostics } from './request-shape.js';
-import type { ModelCallAttempt, ModelCallKind } from '@maka/core/model-call-attempt';
-import {
- ProviderRequestTracker,
- type ModelCallAccountingInput,
- type ProviderRequestUsage,
- type ResolvedModelCallCost,
-} from './provider-request-telemetry.js';
-import {
- ToolAvailabilityRuntime,
- type ToolAvailabilityConfig,
- type ToolAvailabilityPlan,
-} from './tool-availability.js';
-import { renderSwarmModePrompt } from './swarm-mode.js';
-import { renderGraphModePrompt } from './graph-mode.js';
+import { ProviderRequestTelemetry } from './provider-request-telemetry.js';
+import { AiSdkMessageProjection } from './ai-sdk-message-projection.js';
+import { AiSdkTurn, type AiSdkSessionState } from './ai-sdk-turn.js';
+import { buildInvalidMakaTool } from './ai-sdk-tool-repair.js';
+import { ToolAvailabilityRuntime, type ToolAvailabilityConfig } from './tool-availability.js';
import {
MEMORY_EXTRACT_TOOL_NAME,
MEMORY_REMEMBER_TOOL_NAME,
@@ -250,34 +73,8 @@ import {
type MemoryExtractionTrigger,
} from './memory-extraction.js';
import { modelUsesNativeOpenAiResponses, resolveModelRuntime } from './model-runtime.js';
-import {
- applyPatchReplayFactText,
- normalizeApplyPatchReplayInput,
- routeApplyPatchTools,
- type ApplyPatchProfile,
-} from './apply-patch-profile.js';
-import {
- applyRuntimeEventContextBudget,
- buildContextBudgetDiagnosticShell,
- estimateRuntimeEventsTokens,
- mergeContextBudgetDiagnostic,
- mergeContextBudgetDiagnosticPatches,
- minimalContextBudgetDiagnostic,
- shouldAppendContextCompactedNote,
- shouldAppendContextCompactionFailedOpenNote,
- type ContextBudgetPolicy,
-} from './context-budget.js';
-import { isHistoryCompactContentEvent } from './history-compaction.js';
-import {
- canContinueHistoryCompactCheckpointForModel,
- historyCompactCheckpointToModelMessage,
- historyCompactCheckpointToRuntimeEvent,
- isProviderHistoryCompactCheckpoint,
- isTextHistoryCompactCheckpoint,
- matchHistoryCompactCheckpointPrefix,
- projectHistoryCompactCheckpointReplay,
- type HistoryCompactCheckpoint,
-} from './history-compact-checkpoint.js';
+import { routeApplyPatchTools } from './apply-patch-profile.js';
+import { bindToolResultArchiveDecoder } from './tool-result-archive-capability.js';
import { resolveSelectedModelContextWindow } from './context-budget-policy.js';
export {
DEFAULT_PERMISSION_TIMEOUT_MS,
@@ -294,395 +91,14 @@ export type {
} from './model-adapter.js';
export type { RunTraceEvent, RunTraceRecorder } from './run-trace.js';
-const CHILD_STEP_BUDGET_FINALIZATION_PROMPT = [
- '',
- 'This is the final budgeted step for this child-agent turn.',
- 'Do not call tools. Return the best concise final answer now using evidence already gathered.',
- 'Clearly separate verified findings from inference and explicitly name any remaining gaps.',
- '',
-].join('\n');
-
-function providerToolResultContent(
- toolName: string,
- output: unknown,
- input?: unknown,
-): ToolResultContent {
- if (output === undefined) {
- return {
- kind: 'text',
- text: `${toolName} completed without a structured result.`,
- };
- }
- if (toolName !== 'WebSearch') {
- return { kind: 'json', value: output };
- }
- const queryFromInput = providerWebSearchQuery(input);
- if (Array.isArray(output)) {
- const rows: Array<{
- title: string;
- url: string;
- snippet: string;
- source: string;
- }> = [];
- for (const result of output) {
- if (
- !result ||
- typeof result !== 'object' ||
- (result as { type?: unknown }).type !== 'web_search_result' ||
- typeof (result as { url?: unknown }).url !== 'string'
- ) {
- continue;
- }
- const item = result as {
- url: string;
- title?: unknown;
- pageAge?: unknown;
- };
- try {
- const parsed = new URL(item.url);
- if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') continue;
- rows.push({
- title: typeof item.title === 'string' && item.title.trim() ? item.title : parsed.hostname,
- url: parsed.toString(),
- snippet: typeof item.pageAge === 'string' ? item.pageAge : '',
- source: parsed.hostname,
- });
- } catch {
- // Provider source rows are untrusted; malformed URLs are dropped.
- }
- }
- return {
- kind: 'web_search',
- provider: 'model',
- query: queryFromInput,
- rows,
- };
- }
- if (!output || typeof output !== 'object') return { kind: 'json', value: output };
- const providerError = output as { type?: unknown; errorCode?: unknown };
- if (
- providerError.type === 'web_search_tool_result_error' ||
- typeof providerError.errorCode === 'string'
- ) {
- return {
- kind: 'web_search_error',
- ok: false,
- provider: 'model',
- ...(queryFromInput ? { query: queryFromInput } : {}),
- reason: 'provider_error',
- message:
- typeof providerError.errorCode === 'string'
- ? `Provider web search failed: ${providerError.errorCode}`
- : 'Provider web search failed.',
- };
- }
- const action = (output as { action?: unknown }).action;
- const sources = (output as { sources?: unknown }).sources;
- let query = queryFromInput;
- if (action && typeof action === 'object') {
- const value = action as {
- type?: unknown;
- query?: unknown;
- queries?: unknown;
- };
- if (Array.isArray(value.queries)) {
- query = value.queries.filter((item): item is string => typeof item === 'string').join(' | ');
- } else if (typeof value.query === 'string') {
- query = value.query;
- }
- }
- const rows: Array<{
- title: string;
- url: string;
- snippet: string;
- source: string;
- }> = [];
- if (Array.isArray(sources)) {
- for (const source of sources) {
- if (
- !source ||
- typeof source !== 'object' ||
- (source as { type?: unknown }).type !== 'url' ||
- typeof (source as { url?: unknown }).url !== 'string'
- ) {
- continue;
- }
- const url = (source as { url: string }).url;
- try {
- const parsed = new URL(url);
- if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') continue;
- rows.push({
- title: parsed.hostname,
- url: parsed.toString(),
- snippet: '',
- source: parsed.hostname,
- });
- } catch {
- // Provider source rows are untrusted; malformed URLs are dropped.
- }
- }
- }
- return { kind: 'web_search', provider: 'model', query, rows };
-}
-
-function providerWebSearchQuery(input: unknown): string {
- let value = input;
- if (typeof input === 'string') {
- try {
- value = JSON.parse(input);
- } catch {
- return '';
- }
- }
- if (!value || typeof value !== 'object') return '';
- const query = (value as { query?: unknown }).query;
- return typeof query === 'string' ? query : '';
-}
-
-function mergeTextProviderOptions(
- current: NonNullable | undefined,
- next: NonNullable,
- textOffset: number,
-): NonNullable {
- const shifted = structuredClone(next);
- const shiftedOpenAi = shifted.openai;
- if (shiftedOpenAi && typeof shiftedOpenAi === 'object' && !Array.isArray(shiftedOpenAi)) {
- const annotations = (shiftedOpenAi as { annotations?: unknown }).annotations;
- if (Array.isArray(annotations) && textOffset > 0) {
- (shiftedOpenAi as { annotations: unknown[] }).annotations = annotations.map((annotation) => {
- if (!annotation || typeof annotation !== 'object' || Array.isArray(annotation)) {
- return annotation;
- }
- const value = { ...annotation } as Record;
- if (typeof value.startIndex === 'number') value.startIndex += textOffset;
- if (typeof value.endIndex === 'number') value.endIndex += textOffset;
- if (typeof value.start_index === 'number') value.start_index += textOffset;
- if (typeof value.end_index === 'number') value.end_index += textOffset;
- return value;
- });
- }
- }
- if (!current) return shifted;
-
- const merged = { ...structuredClone(current), ...shifted };
- const currentOpenAi = current.openai;
- if (
- currentOpenAi &&
- typeof currentOpenAi === 'object' &&
- !Array.isArray(currentOpenAi) &&
- shiftedOpenAi &&
- typeof shiftedOpenAi === 'object' &&
- !Array.isArray(shiftedOpenAi)
- ) {
- const left = currentOpenAi as Record;
- const right = shiftedOpenAi as Record;
- const openai: Record = { ...left, ...right };
- const leftAnnotations = Array.isArray(left.annotations) ? left.annotations : [];
- const rightAnnotations = Array.isArray(right.annotations) ? right.annotations : [];
- if (leftAnnotations.length > 0 || rightAnnotations.length > 0) {
- openai.annotations = [...leftAnnotations, ...rightAnnotations];
- }
- if (
- typeof left.itemId === 'string' &&
- typeof right.itemId === 'string' &&
- left.itemId !== right.itemId
- ) {
- delete openai.itemId;
- }
- merged.openai = openai as NonNullable[string];
- }
- return merged;
-}
-
-// ============================================================================
-// AgentBackend interface — port contract now lives in @maka/core/backend-types;
-// re-exported here for backward compatibility with existing import sites.
-// ============================================================================
-
+// AgentBackend's port contract lives in core; keep the historical exports.
export type {
AgentBackend,
BackendCompactHistoryInput,
BackendCompactHistoryResult,
} from '@maka/core/backend-types';
+export { INVALID_TOOL_NAME, repairMakaToolCall } from './ai-sdk-tool-repair.js';
-export const INVALID_TOOL_NAME = 'invalid';
-
-function projectToolModePlan(
- plan: ToolAvailabilityPlan,
- toolMode: ToolMode,
- execTool: MakaTool,
-): ToolAvailabilityPlan {
- if (toolMode === 'direct') return plan;
- const withExec = (names: readonly string[]): string[] =>
- [...new Set([...names, execTool.name])].sort((a, b) => a.localeCompare(b));
- const invalid = plan.providerTools.filter((tool) => tool.name === INVALID_TOOL_NAME);
- const visible = [
- ...plan.providerTools.filter((tool) => tool.name !== INVALID_TOOL_NAME),
- execTool,
- ].sort((a, b) => a.name.localeCompare(b.name));
- return {
- ...plan,
- providerTools: [...visible, ...invalid],
- activeTools: withExec(plan.activeTools),
- ...(plan.projectActiveTools
- ? {
- projectActiveTools: (options) => ({
- activeTools: withExec(plan.projectActiveTools?.(options).activeTools ?? []),
- }),
- }
- : {}),
- currentRepairToolNames: () => withExec(plan.currentRepairToolNames()),
- diagnostics: (activeTools, visibleToolSchemaChars) => {
- const baseActive = activeTools.filter((name) => name !== execTool.name);
- const baseChars = toolSchemaCharsForDiagnostics(plan.providerTools, baseActive);
- const diagnostic = plan.diagnostics(baseActive, baseChars);
- if (!diagnostic) return undefined;
- const execSchemaChars = Math.max(0, visibleToolSchemaChars - baseChars);
- return {
- ...diagnostic,
- visibleToolCount: (diagnostic.visibleToolCount ?? baseActive.length) + 1,
- fullToolCount:
- (diagnostic.fullToolCount ?? baseActive.length + (diagnostic.hiddenToolCount ?? 0)) + 1,
- visibleToolSchemaChars,
- fullToolSchemaChars:
- (diagnostic.fullToolSchemaChars ??
- baseChars + (diagnostic.toolSchemaCharReduction ?? 0)) + execSchemaChars,
- };
- },
- };
-}
-
-function nestableToolSnapshot(
- providerTools: readonly MakaTool[],
- activeToolNames: readonly string[],
-): ReadonlyMap {
- const active = new Set(activeToolNames);
- return new Map(
- providerTools
- .filter(
- (tool) =>
- active.has(tool.name) &&
- tool.name !== INVALID_TOOL_NAME &&
- tool.name !== 'exec' &&
- tool.providerTool === undefined &&
- tool.nesting !== 'direct_only',
- )
- .map((tool) => [tool.name, tool] as const),
- );
-}
-
-const codeModeJsonSchemaOptions = {
- allErrors: true,
- strict: false,
- validateFormats: false,
-} as const;
-const codeModeDraft7Validator = new Ajv(codeModeJsonSchemaOptions);
-const codeModeDraft2019Validator = new Ajv2019(codeModeJsonSchemaOptions);
-const codeModeDraft2020Validator = new Ajv2020(codeModeJsonSchemaOptions);
-const codeModeCompiledSchemas = new WeakMap