compact: the summary's provenance records its call envelope

compact/summary gains { model, maxTokens? } — the envelope the
summarize call actually used, reported by the backend that made the
call: summarize() now returns { summary, model, maxTokens? } instead of
bare blocks, so an overriding backend (template or remote summarizer)
reports its own envelope honestly and compactRegion logs it. 'Which
model wrote this summary' becomes answerable from the log alone, and
the one-shot summarize request — outside the loop's header-event fold
by design — is reconstructable from log + code (the reconstructability
RFC's scope statement).
This commit is contained in:
Tianyi Cui
2026-07-06 03:21:29 +08:00
parent 65d644b161
commit 7539231eca
9 changed files with 44 additions and 14 deletions
@@ -58,13 +58,13 @@ class TestCompactService extends BasicCompactService {
return blocks.length * 10
}
override async summarize(text: string, agent: Agent): Promise<ContentBlock[]> {
override async summarize(text: string, agent: Agent): Promise<{ summary: ContentBlock[]; model: string; maxTokens?: number }> {
const model = this.config.summarizationModel || agent.options.model || ''
this.summarizeCalls.push({ text, model })
if (this.summarizeError) throw this.summarizeError
const summary = this.mockSummaryQueue.shift() ?? this.mockSummary
this.summaryOutputs.add(summary)
return summary
return { summary, model }
}
}
@@ -402,6 +402,9 @@ describe('BasicCompactService.compactRegion', () => {
expect(startEvent).toBeDefined()
expect(summaryEvent).toBeDefined()
expect(endEvent).toBeDefined()
// The provenance record carries the summarize call's envelope, so "which
// model wrote this summary" is answerable from the log alone.
expect(summaryEvent?.type === 'compact/summary' && summaryEvent.data.model).toBe('test-model')
// compact/* events are log-only — no surfaceOp (type system enforces this).
const startRaw = startEvent as unknown as { surfaceOp?: unknown }
@@ -1003,8 +1006,12 @@ describe('BasicCompactService.summarize (real ctx.llm.stream)', () => {
const { ctx, adapter } = await ctxWithModel('SUMMARY TEXT')
const svc = new BasicCompactService(ctx, cfg({ auto: false, maxTokens: 512 }))
const summary = await summarize(svc, 'User: hi\n\nAssistant: hello', 'test-model')
const { summary, model, maxTokens } = await summarize(svc, 'User: hi\n\nAssistant: hello', 'test-model')
expect(summary).toEqual([{ type: 'text', text: 'SUMMARY TEXT' }])
// The returned envelope reports what the call actually used — the caller
// logs it on compact/summary (the reconstructability RFC).
expect(model).toBe('test-model')
expect(maxTokens).toBe(512)
// The fixed system prompt and maxTokens flow through.
expect(adapter.lastOptions!.system).toContain('compaction engine')
expect(adapter.lastOptions!.system).toContain('## Next Step')
@@ -1035,7 +1042,7 @@ describe('BasicCompactService.summarize (real ctx.llm.stream)', () => {
])
const svc = new BasicCompactService(ctx, cfg({ auto: false }))
const summary = await summarize(svc, 'User: hi', 'test-model')
const { summary } = await summarize(svc, 'User: hi', 'test-model')
expect(summary).toEqual([{ type: 'text', text: 'PUBLIC SUMMARY' }])
})