A harness session can run for days. Its transcript grows until a model call passes the context limit. Compaction shrinks what the model sees. In a durable harness, the result must also survive a restart. A session that opens again must see the same context as before.
Set durable: true. Compaction then writes a tanstack.compaction record into the session log. The host folds that record into the messages with projectCompaction, live and when the session opens again.
import { createHarnessHost, defineHarness } from '@tanstack/ai-harness'
import { memoryPersistence } from '@tanstack/ai-persistence'
import { openaiText } from '@tanstack/ai-openai'
import {
conversationSummarizer,
projectCompaction,
summarizeOldest,
withCompaction,
} from '@tanstack/ai-compaction'
const compaction = withCompaction({
maxTokens: 180_000,
contextWindow: 200_000,
countTokens: 'usage',
durable: true,
strategy: summarizeOldest({
cut: 'turn',
keepRecentTokens: 8_000,
summarize: conversationSummarizer({ adapter: openaiText('gpt-6.1-sol') }),
}),
})
const assistant = defineHarness({
name: 'acme/assistant',
adapter: openaiText('gpt-6.1-sol'),
middleware: [compaction],
durability: { addRecordsToModelContext: { version: 'v1', record: projectCompaction } },
})
const host = createHarnessHost({ persistence: memoryPersistence() })A summary call takes time. When compaction runs at maxTokens, the user waits for that call before the model answers. Set background to prepare the summary earlier, while the session goes on:
const earlyCompaction = withCompaction({
maxTokens: 180_000,
contextWindow: 200_000,
countTokens: 'usage',
durable: true,
background: { atTokens: 150_000 },
strategy: summarizeOldest({
cut: 'turn',
keepRecentTokens: 8_000,
summarize: conversationSummarizer({ adapter: openaiText('gpt-6.1-sol') }),
}),
})Sometimes a summary does not apply:
If your harness already folds records of its own, call projectCompaction first:
import type { ProjectOptions } from '@tanstack/ai-harness'
const project: ProjectOptions = {
version: 'v2',
record: (input) => {
const compacted = projectCompaction(input)
if (compacted) return compacted
const { record, messages } = input
if (record.type === 'app.signal' && typeof record.text === 'string') {
return [...messages, { role: 'user', content: record.text }]
}
return undefined
},
}Change version when you change the function. A log snapshot with another version is not used, so the host folds the log again from the first record.
A model call can still fail because the context is too long. withCompaction handles it for you. When a call fails with CONTEXT_OVERFLOW_CODE ('context_overflow'), the middleware compacts and calls the model again, inside the chat() call of the turn. That compaction also writes its record to the log.
For a strategy that always finds something to cut on the retry, see Give the retry something to cut.