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:
@@ -18,8 +18,8 @@ Both methods are **abstract** — the backend owns the entire strategy (token es
|
||||
|
||||
| Member | Semantics |
|
||||
|---|---|
|
||||
| `compactIfNeeded(agent, turn, step, fullSystemPrompt, signal)` | Estimate the surface-derived history size; if over the backend's threshold, compact an older range via `compactRegion`, keeping recent context intact. Returns the `CompactionResult`, or `null` if nothing needed compacting. All parameters required — the loop's `agent/pre-step` checkpoint supplies the agent, lifecycle context, assembled `fullSystemPrompt`, and turn `signal`; router-aware summarizers can use the agent lifecycle context to route their own model call through `agent/request`. |
|
||||
| `compactRegion(session, start, end, agent, turn, step, signal?)` | Forcibly summarize surface nodes `[start, end]` (inclusive seqs) into a single replacement node. **Throws** if a compaction is already in progress, if `start`/`end` aren't surface nodes, or if `start` is positioned after `end` on the surface. The range is a SURFACE-POSITION span, not a numeric seq interval — after a prior replace lands a fresh high-seq summary node at the shadowed range's position, surface order no longer tracks seq order. |
|
||||
| `compactIfNeeded(agent, fullSystemPrompt, signal)` | Estimate the surface-derived history size; if over the backend's threshold, compact an older range via `compactRegion`, keeping recent context intact. Returns the `CompactionResult`, or `null` if nothing needed compacting. All parameters required — the loop's `agent/pre-step` checkpoint supplies the agent, assembled `fullSystemPrompt`, and turn `signal`. A backend's summarization request is a direct `ctx.llm.stream()` call (not a loop step), so per-call interception happens at `llm/stream`. |
|
||||
| `compactRegion(session, start, end, agent, signal?)` | Forcibly summarize surface nodes `[start, end]` (inclusive seqs) into a single replacement node. **Throws** if a compaction is already in progress, if `start`/`end` aren't surface nodes, or if `start` is positioned after `end` on the surface. The range is a SURFACE-POSITION span, not a numeric seq interval — after a prior replace lands a fresh high-seq summary node at the shadowed range's position, surface order no longer tracks seq order. |
|
||||
|
||||
`compactIfNeeded` takes a required `signal`; `compactRegion`'s is optional. A backend that summarizes via `ctx.llm.stream()` **must** forward it into the call's `GenerateOptions.signal`, so an abort or fiber dispose tears down the in-flight summarization instead of leaving an orphaned model call running past the cancellation. The session being compacted comes from the agent context; the turn that the `compact/*` events belong to is recoverable from the log (the currently-open turn), so the backend stamps it from the log rather than trusting a caller-supplied value.
|
||||
|
||||
|
||||
@@ -91,8 +91,6 @@ export abstract class CompactService extends Service {
|
||||
* over-budget. Bounding an individual unit's size is a separate concern.
|
||||
*
|
||||
* @param agent - agent context owning the session surface and model options.
|
||||
* @param turn - turn number of the pre-step checkpoint.
|
||||
* @param step - step number about to start.
|
||||
* @param fullSystemPrompt - assembled system prompt, counted toward the estimate.
|
||||
* @param signal - cancellation signal. A backend summarizing via
|
||||
* `ctx.llm.stream()` MUST forward this into the call's `GenerateOptions.signal`
|
||||
@@ -102,8 +100,6 @@ export abstract class CompactService extends Service {
|
||||
*/
|
||||
abstract compactIfNeeded(
|
||||
agent: CompactAgentContext,
|
||||
turn: number,
|
||||
step: number,
|
||||
fullSystemPrompt: string,
|
||||
signal: AbortSignal,
|
||||
): Promise<CompactionResult | null>
|
||||
@@ -129,8 +125,6 @@ export abstract class CompactService extends Service {
|
||||
* @param start - inclusive seq of the first surface node to compact.
|
||||
* @param end - inclusive seq of the last surface node to compact.
|
||||
* @param agent - agent context used by router-aware summarizers.
|
||||
* @param turn - lifecycle turn forwarded to request-routing seams.
|
||||
* @param step - lifecycle step forwarded to request-routing seams.
|
||||
* @param signal - optional cancellation signal. A backend that summarizes via
|
||||
* `ctx.llm.stream()` MUST forward this into the call's `GenerateOptions.signal`
|
||||
* so an abort/dispose tears down the in-flight summarization rather than
|
||||
@@ -148,8 +142,6 @@ export abstract class CompactService extends Service {
|
||||
start: number,
|
||||
end: number,
|
||||
agent: CompactAgentContext,
|
||||
turn: number,
|
||||
step: number,
|
||||
signal?: AbortSignal,
|
||||
): Promise<CompactionResult>
|
||||
}
|
||||
|
||||
@@ -17,8 +17,6 @@ class StubCompactService extends CompactService {
|
||||
|
||||
override async compactIfNeeded(
|
||||
_agent: CompactAgentContext,
|
||||
_turn: number,
|
||||
_step: number,
|
||||
_fullSystemPrompt: string,
|
||||
signal: AbortSignal,
|
||||
): Promise<CompactionResult | null> {
|
||||
@@ -31,8 +29,6 @@ class StubCompactService extends CompactService {
|
||||
start: number,
|
||||
end: number,
|
||||
_agent: CompactAgentContext,
|
||||
_turn: number,
|
||||
_step: number,
|
||||
signal?: AbortSignal,
|
||||
): Promise<CompactionResult> {
|
||||
this.lastSignal = signal
|
||||
@@ -81,7 +77,7 @@ describe('CompactService seam', () => {
|
||||
const ctx = new Context()
|
||||
const svc = new StubCompactService(ctx)
|
||||
const session = new Session(SessionId('s'))
|
||||
expect(await svc.compactIfNeeded(stubAgent(session), 1, 1, '', new AbortController().signal)).toBeNull()
|
||||
expect(await svc.compactIfNeeded(stubAgent(session), '', new AbortController().signal)).toBeNull()
|
||||
})
|
||||
|
||||
it('compact/* events merge into SessionEventMap and are log-only', async () => {
|
||||
@@ -89,7 +85,7 @@ describe('CompactService seam', () => {
|
||||
const svc = new StubCompactService(ctx)
|
||||
const session = new Session(SessionId('s'))
|
||||
|
||||
const result = await svc.compactRegion(session, 0, 0, stubAgent(session, 'm'), 1, 1)
|
||||
const result = await svc.compactRegion(session, 0, 0, stubAgent(session, 'm'))
|
||||
|
||||
const startEvent = session.events.find(e => e.type === 'compact/start')
|
||||
expect(startEvent).toBeDefined()
|
||||
@@ -107,10 +103,10 @@ describe('CompactService seam', () => {
|
||||
const session = new Session(SessionId('s'))
|
||||
const controller = new AbortController()
|
||||
|
||||
await svc.compactRegion(session, 0, 0, stubAgent(session, 'm'), 1, 1, controller.signal)
|
||||
await svc.compactRegion(session, 0, 0, stubAgent(session, 'm'), controller.signal)
|
||||
expect(svc.lastSignal).toBe(controller.signal)
|
||||
|
||||
await svc.compactIfNeeded(stubAgent(session), 1, 1, '', controller.signal)
|
||||
await svc.compactIfNeeded(stubAgent(session), '', controller.signal)
|
||||
expect(svc.lastSignal).toBe(controller.signal)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user