DSH / Atlas
2026-07-06proposedfeature

Recallable compaction — index checkpoints, a state checkpoint, and in-session history recall

可回溯压缩:索引检查点、状态检查点与会话内历史回溯

Compaction is irreversible from the model's current context. The summary the model sees carries no reference to what it shadows — `shadowedRange` lives only on the log-only `compaction/summary` event — and no tool lets the model read a shadowed span back. Whatever the summarizer drops is unavailable to the model, even though the append-only log holds every byte. Repeated compaction compounds this: the head checkpoint

English

Problem

Compaction is irreversible from the model's current context. The summary the model sees carries no reference to what it shadows — shadowedRange lives only on the log-only compaction/summary event — and no tool lets the model read a shadowed span back. Whatever the summarizer drops is unavailable to the model, even though the append-only log holds every byte. Repeated compaction compounds this: the head checkpoint is rewritten every pass, so the request prefix takes a full prompt-cache miss each time, and earlier summaries are re-summarized generation after generation.

The root cause is one artifact playing two conflicting roles. An index wants to be frozen, chronological, and cheap; the model's working memory wants a global view, re-prioritization, and mutability. A single summary can be neither well.

No mainstream coding harness gives the model in-loop recall, and none of the surveyed implementations makes compaction prefix-cache-aware. An event-sourced session — originals durable, seq-addressable, replay-exact — is the natural substrate for both.

Proposal

Split the checkpoint into two classes and make shadowed history reachable.

Frozen index checkpoints

Newly stale history splits into chunks by deterministic policy: accumulate toward chunkTokens, snap edges with toolPairingBalancedBefore / toolPairingBalancedAfter, prefer turn boundaries, and place the final boundary as close to the retain boundary as balance allows, so the trailing slice shrinks to roughly one turn. Each chunk is compacted by one compactRegion call into an index stub (stubTokens, ~100–200 tokens):

  • two or three lines of what happened;
  • a keyword line of low-frequency literal anchors — exact error strings, values, config keys — grouped by kind;
  • a code-composed footer: [checkpoint c<summarySeq>: shadows conversation span #<start>–#<end>; originals retrievable via history_read]. Code assembles these pointers from the compaction/summary seq and shadowedRange; the model never writes them.

A committed stub is never rewritten and never re-enters a later compaction region. A stub call's input is layered: the fixed preamble and the byte-identical pass-start state checkpoint (the shared prefix across all calls in the phase), then the keyword lines of all previously committed stubs — so a new entry indexes what is distinctive to its chunk instead of repeating the directory — the one or two most recent committed stubs for chronological continuity, and the slice itself. Sibling stubs from the same pass are not inputs (the concurrent phase forbids it; turn-aligned boundaries carry local continuity instead), and the state checkpoint is background only, never material to summarize into the stub. A slice consisting of recalled content is stubbed by code alone — a pointer line, no LLM call. A failed stub call degrades the same way: its slice gets a code-only pointer stub and the pass continues, making the state rewrite the only hard LLM dependency in a pass.

The state checkpoint

One mutable working-memory document (at most one; zero before the first pass), positioned after all stubs and before the retained tail. Each pass rewrites it from the previous state plus this pass's staled content — O(previous + new), under the merge-don't-restate rule already in the summarization prompt — covering decisions, current state, constraints, and next steps. It carries its own footer and a size cap at the scale of today's summary.

An inflation guard bounds the whole pass: if the post-compaction size is not strictly below the pre-compaction size, nothing commits and the turn proceeds; the attempt defers until more stale history accumulates. The guard compares one metric on both sides — provider-reported usage from the request path, falling back to the character estimator on both sides.

Pass execution

  • Chunk slices are surface position ranges. A pass runs two phases: all summarize calls execute concurrently, buffered off-surface; then regions commit strictly left to right — chunks first, trailing slice last — so the state checkpoint lands after every stub through contiguous single-node replaces. Wall-clock stays near one summarize call.
  • The superseded state checkpoint folds into the next pass's first chunk as ordinary history: no tombstone, no new primitive. Its stub omits it, history_read renders it labeled [prior state checkpoint], and its footer travels with the rendered text, keeping every trailing slice reachable through the two-hop chain.
  • Range selection is frozen-aware: the compactable span begins after the last committed index checkpoint, at the surface head only when none exists. A legacy session's existing head checkpoint is adopted as state-class — its text the merge base, its node folded like any superseded state.
  • A crash in the summarize phase commits nothing; a crash mid-commit leaves a left-to-right prefix committed, and the resumed pass reads its merge base from the log's latest state-class compaction/summary event and commits the remaining regions unconditionally — restoring [stubs…][state][tail] outranks shrinking.

The recall tools

A new package @deepseek-ai/dsh-tool-recall (consumer-only, over the dsh-session and dsh-compaction vocabularies) registers two model-facing tools:

  • history_read(checkpoint, offset?) — renders the shadowed span of any checkpoint in the log, including superseded ones, as User:/Assistant:/Tool result: transcript, paginated by a configured budget with a continuation cursor.
  • history_search(query, checkpoint?, limit?) — case-insensitive literal scan over every shadowed span; returns snippets with checkpoint ids and coverage metadata (scanned/matched/truncated). The zero-match hint notes the scan is literal and points at direct history_read of a plausible checkpoint.

Both read exec.agent.session.events (the tool-todo access pattern; non-agent callers rejected), render only surface-type message events, and return ordinary tool/results — recalled bytes land at the context tail, logged, so reconstructability holds with no special casing. There is no new storage and no sidecar index: the session log stores the content, compaction/summary.shadowedRange and shadowedSeqs identify what each checkpoint replaced, and the tools read both. The tool schemas and the package's one system-prompt section are static strings; checkpoint ids reach the model only through footers. The transcript renderer moves from compaction-basic into dsh-session, shared by summarizer and tools.

Cache and cost

The request prefix after a pass is [system][stubs…][state][tail]. Frozen stubs are byte-stable across passes, so the miss begins at the token replacing the previous state checkpoint and stays O(new chunks + state + tail) — against position zero today. Recall output lands at the tail, leaving the prefix untouched. Per-pass summarize input is roughly twice today's plus an m·S background term, bounded by a chunkTokens floor (a small multiple of the state cap) and a validated stubTokens/chunkTokens ratio ceiling; a shared-prefix input layout (preamble, then the byte-identical pass-start state, slice content in the tail) lets sibling calls earn cached-rate rereads.

Packaging

The design ships as a new backend dsh-compact-recallable on the existing ctx.compaction seam, enabled by default in the shipped example configs; compaction-basic remains as the reference implementation and the seam's design twin, in the pattern of the paired LLM adapters. The seam JSDoc's "at most one auto-generated checkpoint, always at the head" clause is relaxed to name both backend behaviors.

Relation to in-flight work

  • Tool-result pruning (the in-flight pruning service): its replacement nodes carry sourceEventSeqs; the same registry fold lists pruned results as recallable. Follow-up scope; neither blocks the other.
  • Provider-usage token accounting (the in-flight move of compaction pressure onto provider-reported usage): supplies the guard's accounting; the implementation stacks after it.
  • "Query sessions" backlog item: the cross-session generalization; this Agent Note scopes to the live session with tool names and rendering chosen so that work extends rather than collides.
  • Training: when to recall is a learned behavior. The deterministic footers and keyword anchors give training a stable target, and recall usage is fully visible in the session log for trajectory export; benchmark and RL design proceed with the post-training side.

Follow-ups

Deferred until observation calls for them:

  • Guard degradation ladder (code-only rollup of the oldest stub prefix, footers preserved, rolled-up ids remain recall targets; then one summary after the frozen boundary) — on observed guard livelock or stub-region pressure.
  • Echo detection on stub outputs (sentence-scale n-grams, short literals exempt, retry then strip) — on observed division-of-labor leakage.
  • Periodic state refresh from chunk originals — on observed drift in the handoff probe.
  • stateFallbackThreshold (full-detail state prompt below a stub count) — on short-session regression.
  • Lazy registration of the recall tools — on measured context tax in never-compacting sessions.
  • Amortized stub drafting at pre-step: as soon as stale-but-uncompacted content accumulates past chunkTokens, draft that chunk's stub at the next pre-step (a log-only draft event, written while the chunk's surrounding context is still live) and let the compaction pass commit drafts instead of summarizing in bulk — the deterministic, replay-exact equivalent of background compaction (the Claude Code session-memory pattern; OpenClaw demonstrates the synchronous semantics are identical). Trigger: observed pass latency, or stub-quality gains from drafting near-live proving out.
  • Split summarizer models; model-chosen chunk boundaries; cross-session recall; semantic search fallback — each behind its own evidence.
  • Richer history_search query forms — regex, and structured queries over logged JSON tool results (sql/jq-style, or agent-authored queries against an indexed store) — on demand from observed search misses; literal matching ships first because the recall path stays a pure function of the log.

Alternatives considered

  • Staged delivery (ship recall tools alone over today's backend; gate the checkpoint split on observed recall usage) — rejected: untrained models under-use any new tool, so the gate would measure training absence rather than design value, while the training side needs the complete mechanism to build environments against; the pre-release window is when persisted-format changes are cheapest; and the cache economics are first-party knowledge, not a hypothesis awaiting telemetry. The implementation still lands as stacked PRs with the recall tools first — construction order, not a decision gate.
  • All-frozen full-size summaries, no state checkpoint — rejected: unbounded permanent-prefix growth, self-accelerating toward thrashing, with nothing left to re-prioritize.
  • Pure stubs, no state checkpoint — rejected: presumes the model knows what it is missing; fails on unknown unknowns.
  • LLM aging/consolidation of frozen chunks — rejected as a routine mechanism: summary-of-summary loss and frozen-prefix churn; the code-only rollup is its surviving form, deferred.
  • Full prefix as chunk-summarizer input — rejected: O(N²); the state document gives the same background at O(state).
  • One summarize call emitting all outputs — rejected: the summarize path has no structured-output enforcement; parsing one free-text response apart is the fragile boundary the fail-closed design avoids.
  • Model-chosen chunk boundaries — deferred: parse-and-validate cost against unproven value; chunk policy sits behind config.
  • Model-authored pointers — rejected: pointers must be exact; deterministic assembly is.
  • FTS/vector index sidecar — rejected in-session: the live log is in memory and bounded, a literal scan under budget suffices; an index earns its keep at cross-session scope.
  • Semantic search fallback / secondary-model extraction in the recall path — rejected: an LLM or embedding call there breaks keyless replay determinism; recall stays a pure function of the log.
  • Raw events instead of rendered transcript — rejected: leaks log-only vocabulary and chunk noise; the model reads what a model once saw.
  • Doing nothing (resume/fork as recovery) — rejected: it makes recovery a human act.

Acceptance criteria

  • Auto-compaction over a long session yields [stubs…][state][tail] after every completed pass; prior stubs stay byte-identical across passes; committed stubs never fall inside a later region; the superseded state checkpoint folds without a tombstone, renders labeled, and stays reachable and searchable through the two-hop chain.
  • Every checkpoint's surface text ends with the deterministic footer; footers round-trip through replay byte-identically; the state checkpoint's shadowedRange records its wider input range.
  • Nothing commits before all summaries exist and the guard passes on like-for-like accounting; a guard failure commits nothing and does not fail the turn; a mid-commit kill resumed at the next pre-step completes the pass with the state region committed unconditionally, merge base read from the log; a legacy head checkpoint is adopted as state-class.
  • history_read renders any logged checkpoint's span under budget with a working cursor; history_search covers every shadowed span with checkpoint-id snippets and coverage metadata, asserted in particular by finding content that exists only in a span shadowed by a superseded state checkpoint — the regression pin for trailing-slice reachability; both reject non-agent callers and never-existing ids or orphaned compaction/start with typed errors; recalled content appears as ordinary tool/results; request-reconstruction invariants pass over sessions with compaction plus recall; one keyless snapshot scenario covers compact-then-recall end to end; tool schemas and the prompt section are byte-identical across passes.
  • On the long-horizon bench suite: task success does not regress against compaction-basic at equal budgets; a handoff-fidelity probe (restate K known decisions and constraints after a pass) scores no worse; recall usage frequency and hit usefulness are reported per run via the dsh bench report pipeline, alongside the stub-directory attention measurement and cache-hit telemetry.
  • Seam JSDoc, the compaction capability-seam Agent Note, architecture.md, and the generated tool, config, persistence, and module-graph catalogs update in the same change; all budgets live in config; new source directories hold per-file 100% coverage with HMR disposal tests.

Risks

  • Recall is a learned behavior: untrained models will under-use it, and the bench report exists to track the gap while training closes it. Until then the state checkpoint keeps the floor at today's summary quality.
  • Unknown unknowns remain: a detail absent from summaries and keywords draws no recall. Recall converts "unreachable even when suspected" into "reachable when suspected".
  • The stub directory occupies attention: dozens of stable index cards per request may dilute focus; the bench measurement in the acceptance criteria tracks it against compaction-basic.
  • Cost: per-pass summarize input is roughly twice today's; short sessions sit near today's cost and quality, and the design pays off with session length.
  • State drift and division-of-labor leakage are observable through the handoff probe and stub review; their counters are specified follow-ups.
  • Two backends are a maintenance burden; the seam contract and the shared recall consumer bound it, and the bench comparison decides the default over time.

中文

问题

压缩(compaction)对模型的当前上下文不可逆。模型看到的摘要没有指向被其遮蔽内容的引用,因为 shadowedRange 只存在于仅写入日志、模型不可见的 compaction/summary 事件上,也没有工具能让模型重新读取被遮蔽的区段。即使仅追加日志仍保存每一个字节,摘要器丢弃的内容也对模型不可用。重复压缩会进一步加重这些结果:每一轮都会重写头部检查点,因此请求前缀每次都会完全失去提示词缓存命中,而更早的摘要也会在后续每一轮中重新生成摘要。

根本原因是一个产物承担了两个互相冲突的角色。索引需要冻结、按时间排序且成本低廉;模型的工作记忆则需要全局视图、重新确定优先级并且可变。单一摘要无法同时胜任两者。

主流编码 harness 都没有让模型在循环内回溯,而且调研过的实现均未让压缩感知前缀缓存。事件溯源会话具备持久原文、可按 seq 寻址和精确回放的特征,是支持这两项功能的天然底座。

提案

把检查点拆为两类,并让被遮蔽的历史重新可达。

冻结的索引检查点

新近变为陈旧的历史按确定性策略拆分为分片:向 chunkTokens 累积;使用 toolPairingBalancedBeforetoolPairingBalancedAfter 对齐边缘;优先选择轮次边界;在平衡允许的范围内,把最终边界放在尽量接近保留边界的位置,使尾随切片缩小到大约一个轮次。每个分片通过一次 compactRegion 调用压缩为一个索引存根stubTokens,约 100–200 个 token):

  • 用两三行说明发生了什么;
  • 用一行关键词记录低频字面锚点,例如确切的错误字符串、值和配置键,并按类别分组;
  • 由代码组装页脚:[checkpoint c<summarySeq>: shadows conversation span #<start>–#<end>; originals retrievable via history_read]。代码根据 compaction/summary seq 和 shadowedRange 组装这些指针,模型绝不会编写它们。

已经提交的存根永不重写,也绝不再次进入之后的压缩区域。存根调用采用分层输入:固定前导内容与逐字节相同的本轮开始状态检查点(该阶段所有调用共享的前缀);随后是先前所有已提交存根的关键词行,使新条目索引其分片的独特内容,而不是重复整个目录;再加最近一两个已提交存根以维持时间连续性;最后是切片本身。同一轮中的同级存根不作为输入,因为并发阶段禁止这种依赖,而与轮次对齐的边界已经维持局部连续性。状态检查点只作为背景,绝不能成为存根需要总结的材料。完全由回溯内容构成的切片只通过代码生成存根,即只写一行指针,不调用 LLM(大语言模型)。存根调用失败时采用相同降级方式:其切片获得一个仅由代码生成的指针存根,本轮继续执行,使状态重写成为一轮中唯一的强制 LLM 依赖。

状态检查点

系统维护一份可变的工作记忆文档(最多一份;第一次压缩前为零份),位于所有存根之后、保留尾部之前。每一轮根据先前状态与本轮变为陈旧的内容重写它,成本为 O(previous + new);过程遵守摘要提示词中已有的「合并而不重复陈述」规则,并覆盖决策、当前状态、约束和后续步骤。它带有自己的页脚,其大小上限与当前摘要处于同一量级。

膨胀保护会约束整轮操作:如果压缩后大小没有严格小于压缩前大小,就不提交任何内容,并继续当前轮次;本次尝试延后至积累更多陈旧历史后再进行。保护逻辑在两侧比较同一项度量:优先使用请求路径上提供方报告的用量;如果不可用,则两侧都回退为字符估算器。

一轮的执行过程

  • 分片切片由表面位置范围表示。一轮分两个阶段运行:所有摘要调用先并发执行并在表面之外缓冲;随后严格从左到右提交区域,先提交各分片,最后提交尾随切片,使状态检查点通过连续的单节点替换落在所有存根之后。墙钟时间维持在接近一次摘要调用的水平。
  • 被取代的状态检查点会作为普通历史折入下一轮的第一个分片,不需要墓碑或新原语。其存根省略该状态,history_read 会把它渲染为 [prior state checkpoint],并让页脚随渲染文本一同传递,使每个尾随切片都可通过两跳链路触达。
  • 范围选择会感知冻结边界:可压缩区段从最后一个已提交索引检查点之后开始;只有在不存在索引检查点时,才从表面头部开始。旧会话现有的头部检查点会被视为状态类检查点:其文本作为合并基线,其节点则像其他被取代状态一样折入历史。
  • 摘要阶段发生崩溃时不会提交任何内容;提交中途崩溃会留下一个从左到右的已提交前缀,恢复后的一轮从日志中最新的状态类 compaction/summary 事件读取合并基线,并无条件提交剩余区域。恢复 [stubs…][state][tail] 的优先级高于缩减大小。

回溯工具

新增包 @deepseek-ai/dsh-tool-recall,它只是 dsh-sessiondsh-compaction 词汇之上的消费方,注册两个面向模型的工具:

  • history_read(checkpoint, offset?):把日志中任意检查点(包括已被取代的检查点)遮蔽的区段渲染为 User:Assistant:Tool result: transcript(文本记录),并按配置预算分页,提供续传游标。
  • history_search(query, checkpoint?, limit?):对每个被遮蔽区段进行不区分大小写的字面量扫描;返回带检查点 id 的片段与覆盖元数据(scannedmatchedtruncated)。零匹配提示会说明扫描按字面量执行,并建议对可能的检查点直接使用 history_read

两个工具都读取 exec.agent.session.events(沿用 tool-todo 访问模式;拒绝非 agent(智能体)调用方),只渲染表面类型的消息事件,并返回普通 tool/result:回溯字节会进入上下文尾部并记录到日志,因此无需特殊处理即可满足可重建性。系统不增加新存储或伴随索引:会话日志存储内容,compaction/summary.shadowedRangeshadowedSeqs 指明每个检查点替换了什么,这些工具读取两者。工具 schema 与该包唯一的系统提示词章节都是静态字符串;检查点 id 只会通过页脚抵达模型。transcript 渲染器从 compaction-basic 移入 dsh-session,供摘要器与工具共享。

缓存与成本

一轮后的请求前缀为 [system][stubs…][state][tail]。冻结存根在各轮之间逐字节稳定,因此缓存缺失从替换先前状态检查点的 token 才开始,规模保持 O(new chunks + state + tail),而当前实现会从位置零开始缺失。回溯输出落在尾部,不会改变前缀。每轮摘要输入大约是当前实现的两倍,另加一个 m·S 背景项;该成本受到 chunkTokens 下限(状态上限的小倍数)以及经过校验的 stubTokenschunkTokens 比例上限约束。共享前缀输入布局依次为前导内容、逐字节相同的本轮开始状态、位于尾部的切片内容,使同级调用可以按缓存费率重复读取。

打包方式

该设计以新的后端 dsh-compact-recallable 交付,挂在现有 ctx.compaction seam 上,并在已交付的示例配置中默认启用。compaction-basic 保留为参考实现和该 seam 的设计对照,与成对 LLM 适配器的模式一致。seam JSDoc 中「最多一个自动生成的检查点,始终位于头部」这一条会放宽,改为说明两个后端各自的行为。

与进行中工作的关系

  • 工具结果裁剪(进行中的裁剪服务):其替换节点携带 sourceEventSeqs;同一注册表折叠会把经过裁剪的结果列为可回溯。它属于后续范围,两项工作互不阻塞。
  • 提供方 token 用量核算(正在把压缩压力迁移至提供方报告用量的工作):为保护逻辑提供核算基础;本实现堆叠在它之后。
  • 「查询会话」backlog(待办清单)条目:它是跨会话的泛化方案;本 Agent Note 把范围限定在实时会话内,并选择工具名称与渲染方式,使该工作能够扩展本设计而不产生冲突。
  • 训练:何时回溯属于学习到的行为。确定性页脚与关键词锚点为训练提供稳定目标,而回溯使用情况在会话日志中完全可见,可供轨迹导出;基准测试与 RL 设计由后训练侧推进。

后续事项

以下项目延后至观察结果证明需要时再实现:

  • 保护逻辑降级阶梯(通过代码汇总最早的存根前缀,保留页脚,已汇总 id 仍可作为回溯目标;随后在冻结边界后生成一份摘要):触发条件是观察到保护逻辑活锁或存根区域压力。
  • 存根输出回声检测(句子级 n-gram,豁免短字面量;先重试,再剥离):触发条件是观察到职责分工泄漏。
  • 定期使用分片原文刷新状态:触发条件是交接探针观察到漂移。
  • stateFallbackThreshold(存根数量低于阈值时使用完整细节的状态提示词):触发条件是短会话回归。
  • 延迟注册回溯工具:触发条件是在从不进行压缩的会话中测得上下文开销。
  • 在 pre-step 分摊存根起草工作:一旦已经陈旧但尚未压缩的内容积累超过 chunkTokens,就在下一个 pre-step 起草该分片的存根(一个仅写入日志的草稿事件,在分片周围的上下文仍然存活时写入),让压缩轮提交草稿,而不是集中执行摘要。这是后台压缩的确定性、精确回放等价形式(Claude Code 会话记忆采用这种模式;OpenClaw 证明同步语义完全相同)。触发条件是观察到压缩轮次的执行延迟,或近实时起草带来的存根质量收益得到验证。
  • 拆分摘要模型;由模型选择分片边界;跨会话回溯;语义搜索回退:每项都必须由各自证据支持。
  • 更丰富的 history_search 查询形式:正则表达式,以及对日志 JSON 工具结果执行的结构化查询(sql/jq 风格,或由 agent 针对索引存储编写查询)。触发条件是观察到搜索漏检;首版先交付字面量匹配,使回溯路径保持为日志的纯函数。

考虑过的替代方案

  • 分阶段交付(先在当前后端之上单独交付回溯工具;观察到回溯使用后,再决定是否拆分检查点):不予采纳。未经训练的模型会低频使用任何新工具,因此该条件测量的是训练缺失,而不是设计价值;训练侧需要完整机制来构建环境;预发布阶段修改持久化格式的成本最低;缓存经济性则属于第一方已经掌握的知识,不是等待遥测验证的假设。实现仍以堆叠 PR(Pull Request)方式落地,并先交付回溯工具,但这只是构建顺序,不是决策门槛。
  • 只保留冻结的全尺寸摘要,不设状态检查点:不予采纳,因为永久前缀会无界增长、自我加速并最终发生颠簸,而且没有任何内容可以重新确定优先级。
  • 只保留纯存根,不设状态检查点:不予采纳,因为这假定模型知道自己缺少什么,在面对未知的未知时会失败。
  • 由 LLM 老化/整合冻结分片:不作为常规机制,因为摘要的摘要会丢失信息,并使冻结前缀频繁变化;其保留下来的形式是由代码汇总,且延后实现。
  • 把完整前缀作为分片摘要器输入:不予采纳,因为成本为 O(N²);状态文档以 O(state) 提供相同背景。
  • 一次摘要调用输出全部结果:不予采纳,因为摘要路径没有结构化输出约束;解析一份自由文本响应并将其拆开,正是保守失败设计要避免的脆弱边界。
  • 由模型选择分片边界:延后实现,因为相对于未经证明的收益,解析与校验成本过高;分片策略由配置控制。
  • 由模型编写指针:不予采纳,因为指针必须精确,应由确定性代码组装。
  • FTS/向量索引伴随存储:在会话内不予采纳,因为实时日志已在内存中且大小有界,在预算内进行字面量扫描已经足够;只有跨会话范围才能证明索引的价值。
  • 回溯路径中的语义搜索回退/次级模型提取:不予采纳,因为其中的 LLM 或嵌入调用会破坏无密钥回放的确定性;回溯必须保持为日志的纯函数。
  • 使用原始事件而不是渲染后的 transcript:不予采纳,因为这会泄漏仅日志可见的词汇与分片噪声;模型应读取模型曾经看到的内容。
  • 什么都不做(用恢复/fork 补救):不予采纳,因为这会把恢复变成人工操作。

验收标准

  • 长会话自动压缩后,每一轮完成时都会得到 [stubs…][state][tail];先前存根在各轮之间保持逐字节相同;已提交存根绝不落入后续区域;被取代的状态检查点无需墓碑即可折入历史,渲染时带有标签,并可通过两跳链路触达和搜索。
  • 每个检查点的表面文本都以确定性页脚结束;页脚通过回放逐字节往返;状态检查点的 shadowedRange 记录其更宽的输入范围。
  • 在全部摘要就绪且保护逻辑通过同类核算前,不提交任何内容;保护失败不会提交任何内容,也不会让轮次失败;提交中途被终止后,下一次 pre-step 会恢复处理,从日志读取合并基线,并无条件提交状态区域以完成本轮;旧版头部检查点会被视为状态类。
  • history_read 在预算内渲染任意已记录检查点的区段,并提供可用游标;history_search 覆盖每个被遮蔽区段,返回带检查点 id 的片段与覆盖元数据,测试尤其要能找到只存在于被已取代状态检查点遮蔽区段中的内容,这是锁定尾随切片可达性的回归用例;两个工具都会拒绝非 agent 调用方,并对从未存在的 id 或遗留 compaction/start 返回类型化错误;回溯内容作为普通 tool/result 出现;请求重建不变量会在同时包含压缩与回溯的会话上通过;一项无密钥快照场景端到端覆盖先压缩再回溯;工具 schema 与提示词章节在各轮之间逐字节相同。
  • 在长时间跨度 bench 套件中:任务成功率在预算相同的条件下不低于 compaction-basic;交接保真探针(在一轮后重新陈述 K 项已知决策和约束)的得分不降低;每次运行都通过 dsh bench 报告流水线汇报回溯使用频率和命中有效性,并同时报告存根目录注意力度量与缓存命中遥测。
  • seam JSDoc、压缩能力 seam Agent Note、architecture.md,以及生成的工具、配置、持久化与模块图目录都在同一改动中更新;全部预算位于配置中;新源码目录具备逐文件 100% 覆盖率与 HMR(热模块替换)dispose(资源释放)测试。

风险

  • 回溯属于学习到的行为:未经训练的模型会低频使用它,bench 报告会持续追踪这项差距,直至训练弥合问题。在此之前,状态检查点会让质量下限保持在当前摘要水平。
  • 未知的未知仍然存在:如果某项细节既未出现在摘要中,也未出现在关键词中,就不会触发回溯。回溯把「即使已经怀疑也无法触达」变成「怀疑时可以触达」。
  • 存根目录会占用注意力:每次请求中包含数十张稳定的索引卡,可能稀释模型关注点;验收标准中的 bench 度量会将其与 compaction-basic 对比。
  • 成本:每轮摘要输入大约是当前实现的两倍;短会话的成本和质量接近当前水平,而设计收益随会话长度增长。
  • 状态漂移与职责分工泄漏可以通过交接探针和存根评审观察;对应措施已列为后续事项。
  • 两个后端会扩大维护范围;seam 约定和共享回溯消费方会限制这一范围,bench 对比则用于逐步决定默认实现。