DSH / Atlas
2026-09-10implementedarchitecture

Durable image offload events

持久图片省略事件

A request can exceed an image-capable route's byte or image-count budget. Recomputing the omitted prefix for each request lets a route switch, inline fallback, or compaction restore previously omitted images. The model-visible image set then depends on unlogged request preparation. The [unified image pipeline](../../../../packages/attachment/attachment-local/README.md) owns attachment normalization and request versio

Source compatibility: Chinese title uses a compatible non-canonical header

English

Problem

A request can exceed an image-capable route's byte or image-count budget. Recomputing the omitted prefix for each request lets a route switch, inline fallback, or compaction restore previously omitted images. The model-visible image set then depends on unlogged request preparation. The unified image pipeline owns attachment normalization and request versions; durable omission needs its own recorded decision.

Decision

dsh-compaction-image-offload owns recovery on the agent/request-error waterfall. An IMAGE_OFFLOAD_REQUIRED failure supplies offloadImages; the plugin selects that many oldest retained input-image occurrences in current surface order, appends one image/offload event, and returns retry. Assistant output images are excluded. If no retained input image remains, the plugin delegates. Other failures do not enter this recovery. Offload does not consume the provider retry budget or emit llm/retry.

Summary requests bypass the agent error waterfall. dsh-compaction-basic preserves the full LlmFailure, checks cancellation and selection stability, and dispatches synchronous compaction/summary-error with the selected event seqs. The same image-offload plugin records omissions only within that selection. Synchronous recovery keeps the stability check and decision adjacent. The backend then re-derives input and pricing, including the shrink baseline. Each retry requires additional omitted occurrences; exhaustion delegates the failure. A later summary failure or cancellation preserves any recorded omissions, so command errors do not claim the conversation is unchanged.

The event payload is { targets: [{ seq, imageIndexes }] }. Each target identifies a current user/message or tool/result node. Its strictly increasing, zero-based indexes enumerate every image in depth-first content order, including tool-role result content and previously omitted images. Equal attachment ids remain distinct occurrences. Exact selections remain unambiguous after positional replacements reorder the surface; an attachment id or a raw-log prefix cannot identify the selected set.

The event has no surfaceOp. It neither creates a message nor replaces a message node. The plugin's pure projection validates all targets before Session commits the event and rejects missing or shadowed nodes, output images, duplicate targets, invalid indexes, and already omitted occurrences. Reconstruction derives immutable message copies with ImageBlock.offloaded: true at the recorded positions. Original events, message ids, sources, and unaffected blocks remain unchanged. Resume, fork, and replay apply the same selections from the log.

The compaction plugin owns selection, event declaration, image validation and projection, and retry policy. Plugin-owned message projections owns the generic Session integration and explicit detached assembly, superseding core-owned interpretation. The instance deriveEventMessage() and deriveMessages() return projected messages; pure reconstructors pass foldSurface(events, projections).projectedMessages to the exported deriveEventMessage(). Rewriters preserving images must consume projected messages, including tool-result pruning and summarizer input.

contentGeneration advances for message replacements and image-offload events so cached history and request series are refreshed. replaceGeneration advances only for replacements; image omission cannot masquerade as successful text compaction. An unchanged request envelope gets a request/header with reason series; a simultaneous envelope change carries startsSeries: true.

Adapters prepare only retained images and render marked occurrences as offloadedImageText with the current execution-world access path. Route-local budget checks report required counts, including the tighter inline fallback budget, but do not edit history. These checks are provisional: a provider failure that identifies uncacheable images can replace local counting without moving the durable decision into the adapter.

Token accounting applies selections to the affected nodes' image occurrences without replacing their identities or changing prior usage-anchor snapshots. Route pricing substitutes placeholder text for image cost. The fixed structural heuristic excludes the offloaded marker, so image omission does not change that heuristic or require a compaction/prune shadow price. The context-pressure and context-breakdown checkpoint versions advance to reject cached estimates that counted the marker.

image/offload is required on read. Its addition changes the recognized event vocabulary, not the Session envelope or operation grammar, so it does not advance SESSION_FORMAT_VERSION. A reader without this event type refuses the log instead of silently restoring omitted images.

Alternatives considered

Reuse compaction/prune and surfaceOp: replace. The archived replacement design records full carrying-message copies and couples omission to node replacement and shadow-price accounting. A dedicated event expresses which images are omitted while retaining message identity and keeping retry policy in the same plugin.

Record a request-local projection outcome. A record that does not affect subsequent derivation cannot prevent omitted images from returning. Logging whole request bodies repeats message history for a decision that needs only occurrence indexes.

Use an attachment id or a single watermark. Attachments can occur repeatedly, and surface order can differ from event-sequence order. Explicit per-node indexes distinguish these cases without redefining a watermark after every replacement.

Move selection into the loop or adapters. The request-error extension already owns repair and retry. The adapter knows the route budget; the plugin knows the session input order. Neither requires route-specific policy in the shared loop.

Consequences

Omission remains effective when a route budget grows, an inline fallback ends, or compaction reduces context. It does not delete attachment bytes. Reading the placeholder's access path creates a new image occurrence that can be retained independently. The execution-world path is still resolved during serialization, as it is for retained image handles; this event records the selected image set, not filesystem mappings.

Session invokes the registered pure projection and maintains one content-generation signal. Consumers reconstructing model input must use projected messages. Human views can still show original images from immutable events. The plugin requires neither token-meter nor compaction services merely to record omission.

Testing

Plugin projection tests cover nested and repeated occurrences, atomic rejection, cache invalidation, pure folding, restore, fork, and untouched source events. Recovery tests cover consecutive failures, current surface order, exhausted recovery, fresh request headers, and disposal. Token-meter tests preserve node identity and prior usage anchors while repricing only selected occurrences. Tool-result pruning and compaction tests ensure later rewrites consume the projected history. TypeScript and Python SDK expected outputs exercise the required event through shipped profiles.

中文

问题

请求可能超过图片路由的字节或图片数量预算。每次请求重新计算省略前缀,会让路由切换、内联回退或压缩恢复先前省略的图片。模型可见的图片集合因此取决于未记录的请求准备过程。统一图片流水线负责附件归一化与请求版本,持久省略需要单独记录决策。

决策

dsh-compaction-image-offload 在 agent/request-error waterfall 上负责恢复。IMAGE_OFFLOAD_REQUIRED 失败提供 offloadImages,插件按当前 surface 顺序选择该数量最旧且仍保留的输入图片出现位置,追加一个 image/offload 事件,再返回 retry。Assistant 输出图片不参与选择。没有保留的输入图片时,插件委托后续处理。其他失败不进入这项恢复。图片省略不消耗提供方重试预算,也不产生 llm/retry。

摘要请求不经过 agent 错误 waterfall。dsh-compaction-basic 保留完整的 LlmFailure,检查取消和选区稳定性,再携带所选事件序号触发同步的 compaction/summary-error。同一个图片省略插件只在选区内记录省略。同步恢复使稳定性检查与决策相邻执行。后端随后重新派生输入并计价,包括判断摘要是否缩短输入的基准。每次重试都必须继续省略图片,无图可省略时委派失败。后续摘要失败或取消仍保留已经记录的省略,因此命令错误不声称会话未变。

事件载荷为 { targets: [{ seq, imageIndexes }] }。每个目标指向当前的 user/message 或 tool/result 节点。索引从零开始且严格递增,按内容深度优先顺序枚举所有图片,包括 tool-role 结果内容和先前省略的图片。相同附件 ID 的多次出现分别计数。位置替换改变 surface 顺序后,明确选择仍没有歧义,附件 ID 或原始日志前缀无法标识这个集合。

事件不带 surfaceOp,不创建消息,也不替换消息节点。插件的纯投影在 Session 提交事件前校验全部目标,拒绝缺失或已被遮蔽的节点、输出图片、重复目标、无效索引和已被省略的位置。重建过程派生不可变消息副本,将记录的位置标为 ImageBlock.offloaded: true。原始事件、消息 ID、来源和未受影响的内容块保持不变。恢复、分叉和回放从日志应用相同的选择。

compaction 插件拥有选图、事件声明、图片校验与投影,以及重试策略。插件拥有消息投影负责通用 Session 集成和显式独立装配,取代由核心负责解释的职责划分。实例方法 deriveEventMessage() 与 deriveMessages() 返回派生消息,纯重建函数将 foldSurface(events, projections).projectedMessages 传给导出的 deriveEventMessage()。需要保留图片的改写必须读取派生消息,包括工具结果裁剪和摘要输入。

消息替换和图片省略事件都会推进 contentGeneration,使历史缓存和请求系列刷新。replaceGeneration 只随替换推进,图片省略不能被判断为文本压缩成功。请求配置未变时写入原因是 series 的 request/header,配置同时变化时携带 startsSeries: true。

适配器只准备保留的图片,将带标记的位置渲染为包含当前执行环境访问路径的 offloadedImageText。路由本地预算检查报告所需数量,包括内联回退的更低预算,但不编辑历史。这些检查是暂时方案,未来提供方报告无法缓存的图片时,可以替代本地计数,无需把持久决策移入适配器。

Token 计量将选择应用到受影响节点的图片出现位置,不替换节点身份,也不修改已有用量锚点快照。路由计价以占位文本替代图片费用。固定结构启发式不计 offloaded 标记,因此图片省略不改变该启发式,也不需要 compaction/prune 的遮蔽价格。上下文压力和分类明细检查点版本递增,拒绝曾将该标记计入的缓存估算。

image/offload 是读取时必须识别的事件。它增加已知事件类型,不改变 Session 信封或操作语法,因此不推进 SESSION_FORMAT_VERSION。不认识该类型的读取器会拒绝日志,避免静默恢复已省略的图片。

考虑过的替代方案

复用 compaction/prune 和 surfaceOp: replace。 已归档的替换设计记录携带图片的完整消息副本,把图片省略与节点替换和遮蔽价格计量绑定。专用事件表达被省略的图片,保留消息身份,重试策略仍留在同一个插件中。

记录单次请求的投影结果。 不影响后续派生的记录无法阻止省略的图片返回。记录完整请求体会重复消息历史,而决策只需要图片出现位置索引。

使用附件 ID 或单个分界点。 附件可能重复出现,surface 顺序也可能与事件序号顺序不同。逐节点记录明确索引能够区分这些情况,无需在每次替换后重新定义分界点。

把选择移入循环或适配器。 请求错误扩展点已经负责修复与重试。适配器知道路由预算,插件知道会话输入顺序,两者都不需要在共享循环中加入路由策略。

影响

路由预算增加、内联回退结束或压缩减少上下文时,省略仍然有效。省略不删除附件字节。读取占位文本中的访问路径会创建新的图片出现位置,可以独立保留。执行环境路径仍在序列化时解析,与保留图片的标识文本一致。这个事件记录选择的图片集合,不记录文件系统映射。

Session 调用已注册的纯投影,并维护一个内容版本信号。重建模型输入的消费方必须使用派生消息。面向人的视图仍可从不可变事件显示原始图片。插件记录省略时不需要 token-meter 或 compaction 服务。

测试

插件的投影测试覆盖嵌套和重复图片、原子拒绝、缓存失效、纯折叠、恢复、分叉及原始事件不变。恢复测试覆盖连续失败、当前 surface 顺序、无图可省略时的恢复、新请求头和释放。Token-meter 测试在仅重计所选位置价格时保留节点身份和已有用量锚点。工具结果裁剪和压缩测试保证后续改写读取派生历史。TypeScript 和 Python SDK 预期输出通过发布的 profile 覆盖必需事件。