Files
deepseek-harness/packages/compact/compact-basic

@deepseek-ai/dsh-compact-basic

The basic compaction backend: a BasicCompactService implementing the @deepseek-ai/dsh-compact seam with reusable ctx.tokenMeter pressure, token-budget retention, and summarization as a direct one-shot ctx.llm.stream() call (interceptable at llm/stream).

This is the implementation tier of the compaction capability — see the interface package for the seam and the capability-seam Agent Note for the design.

What it owns

This backend owns the compaction policy:

  • Measurement — the singleton ctx.tokenMeter prices the latest canonical logged envelope and current surface at one consumed-log revision. Post-step pressure therefore includes the actual system prompt, tools, prefix, routing, assistant completion, tool results, buffered context, and steering.
  • Model-free pruning — after pressure or canonical overflow qualifies, the optional ctx.toolResultPrune service rewrites oversized tool results before range selection. Compact-basic remeasures through ctx.tokenMeter, skips summarization when pressure becomes safe, and otherwise summarizes the pruned surface. Below-pressure post-step checks never prune.
  • Retention — compact the oldest whole surface units while preserving a recent tail and balanced tool-call/result cuts through the dsh-compact boundary helpers. Turn boundaries do not protect old steps inside a runaway turn. An open indivisible tail declines until it closes. The optional pruner can repair an oversized closed tool unit when its text-bearing result is the removable bulk; indivisible non-tool units and non-prunable tool remainders remain out of scope.
  • Convergence — retry head-checkpoint compaction up to compactionRetries; reject a summary that does not shrink its source, and throw if retries cannot return below threshold.
  • Summarization — a direct llm/stream call uses the configured provider/model pair and cap, falling back to the latest logged request target and then the agent target, without running the loop-only agent/request seam. The input transcript preserves non-text blocks as tagged placeholders; only returned text enters the checkpoint, excluding reasoning and tool calls that would leak private reasoning or create an orphaned call.
  • Framing — the replacement user message marks established checkpoint context with <compacted-summary> tags. The raw summary remains on the provenance event, and later automatic cycles merge the prior checkpoint.
  • LifecyclecompactRegion() mutates agent.session and records its start, summary, replacement, and end. The serial agent/post-step listener checks pressure after successful output and tool work are durable but before step/end. Canonical provider overflow is handled through agent/request-error after the failed step closes.
  • Overflow recovery — below-threshold overflow bypasses normal retention, prunes, then attempts one maximal balanced head reduction while leaving the newest indivisible unit. Retry is authorized whenever surface.replaceGeneration advances, including when pruning lands before later summary work throws. No replacement, an exhausted cap, cancellation, or an unknown/noncanonical error preserves the original provider failure.
  • Failure handling — an unmatched compact/start is an inert crash marker because no summary replacement landed. A region failure records an error end; the surface remains unchanged unless pruning already landed. Operational post-step failures warn and continue, while overflow-recovery failure preserves the original provider error only when no earlier replacement advanced the surface. Cancellation remains authoritative after any progress.

The protected summarize() method is the sole subclass hook. A template- or remote-summarizer subclass can override it while pressure, retention, provenance, shrink validation, and shadowed-token accounting stay on ctx.tokenMeter. The hook returns the summary blocks together with the call envelope it used ({ summary, provider, model, maxTokens? }), which is logged on compact/summary.

Config (BasicCompactConfig)

Every setting is optional. The pressure and retention policy applies to the token meter's single context window. Unrecognized top-level keys are rejected.

Key Required Meaning
thresholdRatio no (default 0.8) Compact at floor(contextWindow × ratio).
retainTokens no (default floor(contextWindow × 0.16)) Recent surface budget kept verbatim; must be below the threshold.
summarizationProvider no (default '') Set together with summarizationModel; an empty pair resolves the latest logged request target, then the AgentOptions pair.
summarizationModel no (default '') Set together with summarizationProvider; an empty pair resolves the latest logged request target, then the AgentOptions pair.
maxTokens no (default 8192) Provider generation cap for the summarization call; may include reasoning tokens.
compactionRetries no (default 1) Extra attempts after the first when pressure remains above threshold.
maxOverflowRetries no (default 1) Maximum retries after canonical context-window overflow; 0 disables recovery only.
auto no (default true) Register post-step pressure and overflow-recovery listeners. Set false for manual-only.

Usage

import type { Context } from 'cordis'
import { BasicCompactService } from '@deepseek-ai/dsh-compact-basic'
import TokenMeterService from '@deepseek-ai/dsh-token-meter'

export const name = 'compact-basic'
export const inject = ['llm', 'tokenMeter']

export function apply(ctx: Context): void {
  ctx.plugin(TokenMeterService)
  ctx.plugin(BasicCompactService)
}

Loading the plugin registers ctx.compact. Add dsh-compact-tool-result-prune as a sibling before this plugin to enable the optional model-free pass. With auto: true (the default) it compacts automatically under token pressure; a consumer (a future /compact tool) can also call ctx.compact.compactIfNeeded(...) or ctx.compact.compactRegion(...) directly.

Model Experience

Conversation history

What the model sees

After a successful step crosses the threshold, oversized tool results are first rewritten when the optional pruner is loaded. If summarization remains necessary, the next request receives the checkpoint preamble below, a blank line, <compacted-summary>, the data-dependent summary, and </compacted-summary>. Overflow recovery rebuilds the immediate retry from whatever replacement advanced the surface. A checkpoint replaces the selected older range and is followed by the retained recent units.

Conversation checkpoint preamble
This is an automatically generated checkpoint condensing an earlier span of the conversation to free up context. Treat the captured context as established background and build on it without restating it. Continue the task directly from the messages that follow, without acknowledging this checkpoint.

Token effect

Model-free pruning can avoid the auxiliary call entirely; otherwise it reduces that call's transcript before the summary replaces an older range. The replacement reduces future input history rather than appending a second copy. A summary remains until a later compaction replaces it, while an indivisible non-tool unit can still exceed the budget.

KV Cache effect

Replacing rather than append-only. Each checkpoint invalidates reuse from the first replaced history token; the unchanged request prefix before that range remains reusable.

Auxiliary summarizer user message

What the model sees

The summarization model receives exactly Summarize this conversation history: followed by a blank line, the data-dependent renderTranscript() output, another blank line, and Summary:. The conversation model never sees this private request or its reasoning; only returned text is stored.

Token effect

This is a separate model call with data-dependent input and maxTokens-capped output. Convergence retries can pay this cost more than once.

KV Cache effect

Independent of the conversation request cache. An auxiliary call can reuse an exact transcript prefix, while a different selected range or rendering invalidates reuse from its first changed token.

Auxiliary summarizer system prompt

What the model sees

The summarization model receives the checkpoint-writing instruction below.

Auxiliary summarizer system prompt
You are a compaction engine for an AI coding assistant. Condense the conversation transcript into a structured checkpoint that lets another model resume the work with no loss of essential context.

Output EXACTLY the Markdown structure below: keep every section, in order. Use terse bullets, not prose paragraphs. Write "(none)" for an empty section — never drop a section.

## Primary Request and Intent
- [the user's original and evolving goals; quote verbatim where the exact wording matters]

## Key Technical Concepts
- [technologies, frameworks, patterns, and conventions in play]

## Files and Code
- [exact path: why it matters, key changes or snippets]

## Errors and Fixes
- [error: how it was resolved, plus any related user feedback]

## Pending Tasks
- [explicitly requested work not yet completed]

## Current Work
- [precisely what was in progress at this checkpoint]

## Next Step
- [the single next action, directly in line with the most recent request, or "(none)"]

## Critical Context
- [decisions and their rationale, constraints, user preferences, open questions, data needed to continue]

Rules:
- Preserve exact file paths, commands, error strings, identifiers, and function signatures.
- Capture user feedback and explicit instructions faithfully, especially corrections.
- Do NOT mention this summarization process or that the context was compacted.
- If the transcript already contains a <compacted-summary> block, it is a PRIOR checkpoint. Do not copy it forward verbatim: preserve still-true facts, drop stale ones, and merge newer information into a single consolidated summary under the same structure.

Token effect

Fixed auxiliary input cost plus the data-dependent transcript on every summarization attempt.

KV Cache effect

Prefix-stable for auxiliary calls while this instruction and the summarizer route are unchanged. Changing either starts a different prefix; transcript changes occur after the instruction.

Known Limitations and Deferred Work

  • Meter accuracy follows the fixed heuristic — missing reusable provider usage falls back to character count plus structural overhead rather than exact tokenization.
  • Overflow classification is adapter-maintained — provider wording can change; both DeepSeek adapters normalize currently recognized context-limit failures to CONTEXT_WINDOW_EXCEEDED.
  • Some indivisible-unit and envelope-only overflow remains outside surface compaction — recovery cannot shrink system/tools/prefix, split an indivisible non-tool node, or repair a tool unit whose non-prunable remainder still exceeds the window. The optional pruner can shrink text-bearing tool-result bulk inside an otherwise indivisible pair.
  • compactRegion requires an open turn — a manual call on a fully-closed session throws ("no open turn") rather than compacting.
  • Summarization failure preserves the latest durable surface — before any replacement, the auto path logs a warning and proceeds with full over-budget history. If pruning already landed, a later summarization failure proceeds from that durable pruned surface. Summarization truncation at maxTokens, which hidden reasoning tokens can consume, follows the same rule.
  • The summarization call has no transcript-snapshot coveragedsh-llm-replay derives calls from assistant/chunk events, so this chunk-less direct ctx.llm.stream() call cannot replay (named deferred replay infrastructure in the seam Agent Note).