compact: summarize is a direct one-shot llm/stream call, not a loop step
The summarization request no longer dispatches agent/request — that seam shapes the loop's conversation requests; a hand-built one-shot's interception surface is llm/stream, like every other direct call. The model comes from summarizationModel falling back to the agent's own. The turn/step parameters existed on the compact seam only to feed the agent/request dispatch and leave compactIfNeeded/compactRegion. Groundwork for making every conversation request a pure function of the session log (reconstructability RFC, this branch): the seam split keeps the loop's request path as the single thing the log must explain. Ported from worktree-prompt-prefix-stability (PR #162) where it was review-converged; catalog and producer/consumer graphs regenerated.
This commit is contained in:
9 files changed
+37
-45
No files matched your search
@@ -182,9 +182,9 @@ export class BasicCompactService extends CompactService {
|
||||
// log-only `compact/*` records and the replacement node cleanly outside a
|
||||
// step, so a crash mid-compaction leaves an inert orphan the turn-repair
|
||||
// closes — never a half-open step.
|
||||
ctx.on('agent/pre-step', async (agent: Agent, turn: number, step: number, fullSystemPrompt: string, signal: AbortSignal) => {
|
||||
ctx.on('agent/pre-step', async (agent: Agent, _turn: number, _step: number, fullSystemPrompt: string, signal: AbortSignal) => {
|
||||
try {
|
||||
const result = await this.compactIfNeeded(agent, turn, step, fullSystemPrompt, signal)
|
||||
const result = await this.compactIfNeeded(agent, fullSystemPrompt, signal)
|
||||
if (result) {
|
||||
const after = this.estimateTokens(agent.session.deriveMessages(), fullSystemPrompt)
|
||||
ctx.logger.info(
|
||||
@@ -271,9 +271,13 @@ export class BasicCompactService extends CompactService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Summarize conversation text into content blocks via `agent/request` plus
|
||||
* `ctx.llm.stream()` assembled through a `BlockAssembler` (the single
|
||||
* model-call surface).
|
||||
* Summarize conversation text into content blocks via `ctx.llm.stream()`
|
||||
* assembled through a `BlockAssembler`. A direct one-shot model call, NOT a
|
||||
* loop step: it does not run the `agent/request` waterfall (that seam shapes
|
||||
* the loop's conversation requests); per-call
|
||||
* interception happens at `llm/stream` like any other direct call. The model
|
||||
* comes from `BasicCompactConfig.summarizationModel`, falling back to the
|
||||
* agent's own model.
|
||||
* Override in a subclass for a template or remote summarizer.
|
||||
*
|
||||
* Honors the adapter failure contract: an adapter may report a model failure
|
||||
@@ -284,7 +288,7 @@ export class BasicCompactService extends CompactService {
|
||||
* Forwards `signal` into `GenerateOptions.signal` so an abort/dispose tears
|
||||
* down the in-flight summarization rather than orphaning the model call.
|
||||
*/
|
||||
async summarize(text: string, agent: Agent, turn: number, step: number, signal?: AbortSignal): Promise<ContentBlock[]> {
|
||||
async summarize(text: string, agent: Agent, signal?: AbortSignal): Promise<ContentBlock[]> {
|
||||
const assembler = new BlockAssembler()
|
||||
const options: GenerateOptions = {
|
||||
model: this.config.summarizationModel || agent.options.model || '',
|
||||
@@ -299,11 +303,10 @@ export class BasicCompactService extends CompactService {
|
||||
// exactOptionalPropertyTypes: only set `signal` when present — assigning
|
||||
// `undefined` to an optional `signal?: AbortSignal` is a type error.
|
||||
if (signal) options.signal = signal
|
||||
const request = await this.ctx.waterfall('agent/request', agent, turn, step, options, () => Promise.resolve(options))
|
||||
if (!request.model) {
|
||||
throw new Error('no model available for summarization: set BasicCompactConfig.summarizationModel, AgentOptions.model, or supply one via the agent/request waterfall')
|
||||
if (!options.model) {
|
||||
throw new Error('no model available for summarization: set BasicCompactConfig.summarizationModel or AgentOptions.model')
|
||||
}
|
||||
for await (const chunk of this.ctx.llm.stream(request)) {
|
||||
for await (const chunk of this.ctx.llm.stream(options)) {
|
||||
assembler.push(chunk)
|
||||
}
|
||||
|
||||
@@ -348,8 +351,6 @@ export class BasicCompactService extends CompactService {
|
||||
*/
|
||||
override async compactIfNeeded(
|
||||
agent: Agent,
|
||||
turn: number,
|
||||
step: number,
|
||||
fullSystemPrompt: string,
|
||||
signal: AbortSignal,
|
||||
): Promise<CompactionResult | null> {
|
||||
@@ -368,7 +369,7 @@ export class BasicCompactService extends CompactService {
|
||||
break
|
||||
}
|
||||
|
||||
result = await this.compactRegion(session, range.start, range.end, agent, turn, step, signal)
|
||||
result = await this.compactRegion(session, range.start, range.end, agent, signal)
|
||||
}
|
||||
|
||||
const totalTokens = this.estimateTokens(session.deriveMessages(), fullSystemPrompt)
|
||||
@@ -385,8 +386,6 @@ export class BasicCompactService extends CompactService {
|
||||
start: number,
|
||||
end: number,
|
||||
agent: Agent,
|
||||
turn: number,
|
||||
step: number,
|
||||
signal?: AbortSignal,
|
||||
): Promise<CompactionResult> {
|
||||
// Resolve the range by surface POSITION, not numeric seq interval. A prior
|
||||
@@ -450,7 +449,7 @@ export class BasicCompactService extends CompactService {
|
||||
try {
|
||||
// --- Extract text and summarize ---
|
||||
const text = this._extractText(session, shadowedSeqs)
|
||||
const summary = await this.summarize(text, agent, turn, step, signal)
|
||||
const summary = await this.summarize(text, agent, signal)
|
||||
|
||||
// Estimate token count of the shadowed content for provenance.
|
||||
let shadowedTokenCount = 0
|
||||
|
||||
Reference in New Issue
Block a user