压缩(compaction)

September 21, 2026 · View on GitHub

English | 中文

压缩 seam 是一个能力 seam,与 bash 一样分为 Service Definition(dsh-compactionctx.compaction)、一个 Service Provider,例如 dsh-compaction-basicdsh-compaction-lossless,以及包括 dsh-command-compactdsh-tool-compactiondsh-tool-compaction-history 在内的 Consumer。压缩是一项可选能力,不属于 agent loop(智能体循环)主干,因此其事件类型定义在此而非 core.md 中。与 bash 不同,该接口必然依赖 dsh-sessiondsh-llm:其动词作用于 agent 所有的 Session,而其持久摘要事件使用 ContentBlock(见压缩能力 seam Agent Note)。

源码:packages/compaction/compaction/src/types.ts

compaction/* 会话事件

压缩通过声明合并为 SessionEventMap 扩展四种事件类型。四者都仅写入日志——它们记录锁、摘要或剪枝影子价格、选中范围、被遮蔽事件 seq、token 数以及可选的模型调用,绝不进入 surface。这里有意不扩展 SurfaceEventType(只有产生消息的事件才到达模型),因此摘要承载在另一条替换用 user/message 上,而剪枝承载在另一条仅改内容的 tool/result 替换上。Agent Note 负责复用 user/message 的决策依据。

事件载荷作用
compaction/start{ turn }获取日志记录的锁;数字标识尚未结束的活动轮次,null 标识独立手动尝试
compaction/summary{ summary, rawOutput?, llmStreamCall?, shadowedRange, shadowedSeqs, shadowedTokenCount, provider, model, maxTokens?, usage? }安全摘要投影、可选的完整提供方输出与 usage、生成结果时恰好通过此上下文的 ctx.llm.stream() 发起一次调用所带的 llmStreamCall: true 标记(此时必须提供完整的 rawOutput)、被遮蔽的 surface 边界对(start/end seq——位置跨度,而非数值区间)、按 surface 顺序排列的被遮蔽 seq、估算 token 数,以及摘要调用的 envelope(providermodel,若有生成上限则还包括该上限)——写入日志后,该一次性请求可由日志 + 代码重建(见可重建性 Agent Note);未带标记的 rawOutput 并不能判定调用路径
compaction/prune{ shadowedRange, shadowedSeqs, shadowedTokenCount }紧随其后的仅改内容 tool/result 替换所对应的精确范围、有序来源和启发式 token 影子价格
compaction/end{ turn, error? }使用相同的数字或 null 归属值释放锁(error 记录失败尝试)

compaction/summary 上由提供方报告的用量会计入一次累计 tokenUsage,但不计入表示主请求占用量的上下文压力。持久的 compaction/end 记录还会投影为出站 compaction.settled;通知详情由通知包负责。

锁括住整个操作:先追加 compaction/start,然后执行摘要生成、写入 compaction/summary 记录与 user/message 替换,最后才追加 compaction/end。最后释放锁意味着操作中途崩溃会表现为可检测的遗留锁(有 compaction/start 而无匹配的 compaction/end),而非一个虚假声称压缩已完成的 compaction/end

不变量要求 compaction/summary 后立即跟随其检查点替换,并且范围、有序来源 [startSeq, summarySeq, ...shadowedSeqs]compactionId 和可选 sourceCommandId 完全匹配;该替换提交之前,成功的 compaction/end 会被拒绝。同样,每个 compaction/prune 后必须立即跟随一个范围与来源完全匹配的 tool/result 替换,并保留除 content 之外的所有字段。

这些标记表示锁的时间点,而不是排他的容器。摘要等待期间,不相关的空闲注入可以出现在独立的手动 start 与 end 之间。手动路径只重新验证所选位置 span,因此替换检查点之后仍保留该注入上下文。活动的未匹配 start 会阻塞所有入口点;较新 session/end-seed 之前的未匹配 start 是先前生命周期留下的陈旧证据,会被忽略。

这些变体在 declare module '@deepseek-ai/dsh-session/types' 块内合并,因此——与其他子系统页面上的顶层类型不同——它们不以漂移检查的 ```ts type-equiv 块粘贴(verify-type-equiv 提取器只按名称匹配顶层声明)。上方的载荷表即为目录条目;权威字段请循源码链接查看。

CompactionResult

成功压缩向调用方返回:记账事件 seq、安全摘要投影、被遮蔽的范围与 seq,以及估算 token 数。

/** Result of a successful compaction operation. */
interface CompactionResult {
  /** Stable identity shared by this compaction's complete durable lifecycle. */
  compactionId: CompactionId
  /** Human command that initiated this compaction, when it was manual. */
  sourceCommandId?: CommandId
  /** The seq of the appended `compaction/start` event. */
  startSeq: number
  /** The seq of the appended `compaction/summary` event. */
  summarySeq: number
  /** The seq of the appended `compaction/end` event. */
  endSeq: number
  /** The summary content blocks produced by the backend. */
  summary: ContentBlock[]
  /**
   * The surface-boundary pair that was shadowed: the seqs of the first
   * (`start`) and last (`end`) surface nodes of the replaced range. A
   * surface-POSITION span, not a numeric seq interval — after a prior replace
   * lands a fresh high-seq summary node at an older range's position, `start`
   * can be GREATER than `end`. {@link CompactionResult.shadowedSeqs} is the
   * authoritative set of shadowed nodes, in surface order.
   */
  shadowedRange: { start: number; end: number }
  /** The seqs of all shadowed surface nodes, in surface order. */
  shadowedSeqs: number[]
  /** Estimated token count of the shadowed content. */
  shadowedTokenCount: number
}

服务

策略调用方会说明压缩为何运行;实现可以比普通压力或 agent request 更激进地处理已确认的溢出。

/** Why policy is asking a backend to consider compaction. */
type CompactionTrigger = 'pressure' | 'context-overflow' | 'agent-request'

CompactionEngine 暴露 compactIfNeeded(agent, trigger, signal) 以执行自动 pressurecontext-overflow 恢复或 agent-request,暴露 compactNow(agent, signal) 以便即使未达到压力也对空闲会话进行一次有效缩减,还针对显式、两端均包含的 surface 范围暴露 compactRegion(...)compactNow() 作为轮次之间的 agent maintenance 运行;没有有效范围时返回 null 且不写入;在摘要前记录独立的 turn: null 标记对,并在后续排队提示词能够从新表层派生前 flush 已闭合尝试。每个后端都使用 compactCheckpointSource(compactionId, sourceCommandId?) 创建替换用 user/message 的源;client 与 wire 消费方从无 Cordis 的 @deepseek-ai/dsh-compaction/checkpoint 子路径导入该构造函数、CompactionCheckpointSourceisCompactCheckpointSource(),包根则为 host 消费方重新导出它们。必填的事务身份会关联替换检查点,而该判定函数使检查点识别不依赖任一特定后端。实现必须把传入的 signal 转发给摘要流程。该 seam 不拥有计价 API:单例 ctx.tokenMeter拥有估算与回放,而选定的 provider 拥有保留策略、事件排序、按路由执行的摘要调用及配置。

预期的手动失败使用 ManualCompactionErrorCode

/** Expected failure classes for an explicit idle-session compaction request. */
type ManualCompactionErrorCode =
  | 'busy'
  | 'cancelled'
  | 'changed'
  | 'summary'
  | 'commit'
  | 'persistence'

changedsummary 保持会话表层不变,但仍会闭合失败尝试并将其持久化到日志。commit 可能发生在部分变更之后;persistence 表示内存中的标记对已闭合,但 flush 失败。取消独立于这些失败,并在完成必要清理后抛出原始 abort 原因。

压力压缩在串行 agent/pre-step 中运行,先于请求推导。随附的 base、standard、code、Cordis 和 standalone headless 组合选择启用自动策略的 dsh-compaction-lossless。根层持有的 compaction Settings 命名空间可以为每次新决策覆盖 Profile 默认阈值;精确 Provider/Model 策略仍拥有更高优先级,且一次决策会在等待模型元数据期间保持最初的策略快照。全局 /compact 注册可在冷 Agent 恢复前被发现,执行时再解析调用方 Profile 的 scoped Provider。Base、standard、Cordis 与 headless 还挂载 direct context_compact Consumer;Code 会省略它,因为 Code Mode 对原生工具的访问是 nested。它的 agent-request 触发会绕过压力、应用已路由的保留尾部策略、跳过剪枝,并最多执行一次缩减。bundle 与 preset 组合保持剪枝行禁用,headless 则不挂载剪枝器,因此摘要替换是唯一的默认历史改写。显式 overlay 可以启用可选的 ctx.toolResultPruner;一旦压力或规范化溢出满足条件,继承的 basic 策略会在选择范围前调用它,再通过 ctx.tokenMeter 重新测量,并且可以在不生成摘要的情况下推进 surface。失败请求的恢复在失败的步骤关闭后通过 agent/request-error 运行;仅当 surface replacement generation 前进时才返回重试动作,即便后续摘要工作在剪枝后抛异常亦如此;取消仍然优先。区域边界保持工具调用/结果配对,但不保持整个轮次,因此一个过大轮次中较早关闭的步骤可以被压缩。

该 Service Definition 导出 toolPairingBalancedBefore(session, seq)toolPairingBalancedAfter(session, seq),用于检查 seq 之前与之后的工具调用/结果配对。两者都会验证当前 surface 成员关系,并拒绝缺失的 seq 与遗留结果;包约定定义其缓存行为。

可回溯 summary DAG

dsh-compaction-losslessctx.compaction 旁注册 ctx.compactionHistory。该服务只在匹配的 compact checkpoint 提交后发布 summary 节点;后续替换遮蔽旧 checkpoint 时推导 parent id;并把每个节点剩余的 raw message seq 保留为 source 引用。resume 或 HMR 后,它从 append-only log 重建 live Session projection;没有 replacement checkpoint 的失败 summary 不会进入索引。

search(sessionId, query, limit?) 对已提交 summary 文本执行有界且不区分大小写的 term matching。expand(sessionId, summaryId, options?) 在配置的深度与确定性 token 估算 cap 内返回 summary ancestry 和可选 raw message source。source 类型与配置声明在 compaction-lossless/src/types.ts;面向模型的 Consumer 负责工具 schema 与不可信历史提示。

工具结果剪枝产出

可选的工具结果剪枝服务会报告每次持久内容替换以及 Unicode code point 的总减少量。剪枝具有追溯性:第一次替换会使 KV cache 从该变更起失效,插件禁用后替换仍然有效,完整原始事件则保持仅追加。新结果另由 ToolRuntime 在进入 cache 前应用 artifact 支持的最终 50,000 码点上限。剪枝服务的公开结果类型位于 compaction-tool-result-pruner/src/types.ts

/** Cited source event and size accounting for one landed surface replacement. */
interface PrunedEntry {
  /** Full-fidelity tool-result event shadowed by the replacement. */
  readonly originalSeq: number
  /** Newly appended pruned tool-result event. */
  readonly replacementSeq: number
  /** Tool call shared by the original and replacement. */
  readonly callId: CallId
  /** Original text size in Unicode code points. */
  readonly charsBefore: number
  /** Replacement text size in Unicode code points. */
  readonly charsAfter: number
}
/** Aggregate outcome of one stable-surface pruning pass. */
interface PruneResult {
  /** Replacements in the snapshotted surface order. */
  readonly pruned: readonly PrunedEntry[]
  /** Total Unicode code points removed across replacements. */
  readonly charsRemoved: number
}

Cordis API

Generated from source by scripts/gen-cordis-catalog.ts (verified fresh by pnpm run verify-cordis-catalog in doc-sync; regenerate with pnpm run gen-cordis-catalog) — this section is byte-identical in both language sides of the page. Signature blocks use a ts cordis-catalog fence and keep the original source JSDoc; dispatch modes are defined in the primer, and the framework-inherited ctx API lives in cordis-api/inherited.md.

ctx.compactionCompactionEngine (abstract seam)

Abstract compaction service. Implementations own trigger policy, retention, and summarization, and may consume a separate measurement service. A successful run replaces the selected surface span with one summary node and prevents concurrent compaction of the same session. The replacement user message uses compactCheckpointSource with the transaction identity so consumers recognize and correlate it independently of the backend. Load one implementation per context as ctx.compaction.

/**
 * Consider compaction for one explicit trigger. Pressure policy uses the
 * latest durable routed request, context-overflow policy may force a useful
 * balanced reduction without retention, and an agent request bypasses the
 * pressure threshold while retaining the configured recent tail. Return
 * `null` when no safe range can be compacted. A single oversized retained unit
 * or request envelope cannot be repaired through surface compaction.
 *
 * @param agent - agent context owning the session surface and routing options.
 * @param trigger - normal pressure, provider-confirmed overflow, or an active agent request.
 * @param signal - cancellation signal; model-backed implementations must forward it.
 * @returns the compaction result, or `null` if no compaction was needed.
 */
abstract compactIfNeeded( agent: CompactionAgentContext, trigger: CompactionTrigger, signal: AbortSignal, ): Promise<CompactionResult | null>

/**
 * Explicitly compact useful history even below automatic pressure thresholds.
 * Implementations synchronously start an idle task before any asynchronous
 * work, select a useful range without writing on a no-op, then
 * append a standalone `compaction/start` before summarization. That durable
 * marker is the compaction lock until one `compaction/end` attempt. Later waking
 * prompts remain accepted in FIFO order and start only after the optional
 * durability checkpoint and idle-task settlement. Context injected while the
 * summary runs may sit between the marker pair; only the selected span must
 * remain stable.
 *
 * @param agent - idle agent whose durable history should be compacted.
 * @param signal - cancellation scoped to this compaction request.
 * @param sourceCommandId - initiating command identity for a manual compaction.
 * @returns the compaction result, or `null` when no safe useful range exists.
 * @throws {@link ManualCompactionError} for expected busy, agent-cancellation,
 * changed-span, summarization/shrink, commit-stage, or persistence failures;
 * an aborted request preserves its exact abort reason. Failed attempts remain
 * visible in the log.
 */
compactNow( agent: ManualCompactAgentContext, signal: AbortSignal, sourceCommandId?: CommandId, ): Promise<CompactionResult | null>

/**
 * Forcibly compact a range of surface nodes into a single summary node.
 * `start` and `end` name an inclusive span by surface position, not numeric seq
 * order; replacements can make visible seqs non-monotonic. Both edges must be
 * balanced so assistant tool calls remain paired with their results. A model-
 * backed implementation forwards cancellation and rejects active, missing,
 * reversed, or unbalanced ranges. The target session is `agent.session`.
 * Its replacement user message must use {@link compactCheckpointSource} with
 * the transaction's `CompactionId`.
 * Use {@link toolPairingBalancedBefore} and {@link toolPairingBalancedAfter}
 * for the edge checks.
 *
 * @param start - first surface seq, inclusive.
 * @param end - last surface seq, inclusive.
 * @param agent - context whose session is mutated and whose routing options guide summarization.
 * @param signal - optional cancellation; model-backed implementations must forward it.
 * @throws when compaction is active or the range is missing, reversed, or unbalanced.
 * @returns the appended event seqs, summary, replaced range, and token accounting.
 */
abstract compactRegion( start: number, end: number, agent: CompactionAgentContext, signal?: AbortSignal, ): Promise<CompactionResult>

Types: CommandId

Source: packages/compaction/compaction/src/index.ts:116

ctx.compactionHistoryCompactionHistory

Live in-memory projection of committed summary nodes recorded in each Session log.

/**
 * Search summary content belonging to one live session.
 * @param sessionId - session whose committed summary nodes are searched.
 * @param query - case-insensitive terms that every matching summary contains.
 * @param limit - requested result count, capped by provider configuration.
 * @returns newest matching committed summary nodes first.
 * @throws when the session is not live in this projection.
 */
search(sessionId: SessionId, query: string, limit: number = this.config.maxSearchResults): CompactionSummarySearchHit[]

/**
 * Expand one summary through its parent DAG and optional raw message sources.
 * @param sessionId - session that owns the summary identity.
 * @param summaryId - committed summary node to expand.
 * @param options - requested depth, token estimate, and source inclusion.
 * @returns bounded summary ancestry and source messages.
 * @throws when the session or summary is unavailable.
 */
expand( sessionId: SessionId, summaryId: CompactionSummaryId, options: CompactionSummaryExpansionOptions = {}, ): CompactionSummaryExpansion

/**
 * Return projection statistics for one live session.
 * @param sessionId - session whose committed nodes are counted.
 * @returns committed summary count and greatest DAG depth.
 * @throws when the session is not live in this projection.
 */
stats(sessionId: SessionId): { summaries: number; maxDepth: number }

Types: SessionId

Source: packages/compaction/compaction-lossless/src/index.ts:143

ctx.contextInspectorContextInspector

Read-only projection service over the same assembly primitives the agent loop uses (systemPrompt.assemble, renderPrompt, the session surface fold, and the shared token meter). Nothing here mutates or wakes anything.

/**
 * Project one agent's next request surface for audit.
 * @param agent - the agent whose session and prompt assembly are inspected.
 * @param signal - optional cancellation forwarded to prompt assembly.
 * @returns the ordered manifest with per-segment provenance.
 */
async manifest(agent: Agent, signal?: AbortSignal): Promise<ContextManifest>

Types: Agent

Source: packages/context/context-inspector/src/index.ts:64

ctx.toolResultPrunerToolResultPruner

Deterministic head/middle/tail pruning for current tool-result surface nodes.

/**
 * Measure text content in Unicode code points; non-text blocks cost zero.
 * @param blocks - tool-result content to measure.
 * @returns total Unicode code points across text blocks.
 */
measureContent(blocks: readonly ContentBlock[]): number

/**
 * Replace an over-budget text middle while retaining rich-block order.
 * Text slicing is by Unicode code point, not UTF-16 code unit, so a retained
 * boundary cannot split a surrogate pair. Grapheme clusters may still split.
 * @param blocks - original tool-result content.
 * @param preserve - projected rich-block placeholders that must remain whole.
 * @returns pruned content, or `null` when the text is within budget.
 */
pruneContent(blocks: readonly ContentBlock[], preserve: ReadonlySet<ContentBlock> = new Set()): ContentBlock[] | null

/**
 * Prune every over-budget tool result from one stable current-surface snapshot.
 * Each replacement preserves the complete event data except for `content`,
 * cites the shadowed node so replay can recover the replacement input, and is
 * immediately preceded by a `compaction/prune` shadow-price event pricing the
 * shadowed node through the injected token meter, so pure consumers can
 * subtract it without per-node state.
 * @param session - session whose current surface is rewritten.
 * @returns landed replacements and aggregate Unicode-code-point savings.
 * @throws when the session rejects a replacement; replacements committed
 * earlier in the pass remain durable.
 */
pruneSession(session: Session): PruneResult

Types: ContentBlock · Session

Source: packages/compaction/compaction-tool-result-pruner/src/index.ts:45

compaction/* events

compaction/progress — emit

Live compaction output; the durable summary remains the session fact.

/** Live compaction output; the durable summary remains the session fact.
 * @mode emit
 * @param payload - transient reasoning or summary text for one compaction.
 */
'compaction/progress'(payload: CompactionProgress): void

Source: packages/compaction/compaction/src/index.ts:103