# @deepseek-ai/dsh-spill-policy

[English](README.md) | 中文

**工具结果 spill 策略**：一个 `tools/post-execute` 转换器，用于防止过大的纯文本工具结果进入模型上下文。当最终结果超过 `maxInlineBytes` 时，它会通过 [`ctx.spillStore`](../spill) 保存完整文本，并将面向模型的结果替换为有界的首尾预览、后端定位信息与取回指引。

该插件**不注册任何服务**，也不负责存储或预览机制：预览由 [`@deepseek-ai/dsh-retention`](../../util/retention) （`TextRetainer`）负责，存储由 `ctx.spillStore` 负责。它只决定何时 spill，并组合通知。

## 配置

| 键 | 默认值 | 含义 |
|---|---|---|
| `maxInlineBytes` | *（省略）* | 面向模型的纯文本结果上下文上限，以 UTF-8 字节数计（在加载时验证为非负整数）。**省略时完全禁用该策略**（插件不注册任何内容）。设置后，较大的结果会被 spill，并替换为从同一预算派生的预览（首尾拆分）。 |

## 行为

1. 允许工具运行（通过 `next()` 委托，因此可以限制任何下游钩子接受的内容）。
2. 跳过嵌套执行（存在 `exec.parent`——其持久副本由下方的 dispatch-log 分支设界）、已接受的值替换（注册表必须重新验证并渲染它们）、`read`（避免 `read → spill → read again` 循环）以及任何非 `accept` 决定（`block` 的纠正反馈会原样通过）。
3. 仅在已接受的内容为**纯文本**（全部都是 `text` 块）时才将其展平；包含任何非文本块的结果都保持不变。
4. 如果 UTF-8 大小为 `≤ maxInlineBytes`，则保持不变。
5. 否则，保存完整文本，并将结果替换为预览和以下通知。系统会调整大小，使整个替换内容（预览、空行和通知）不超过 `maxInlineBytes`：先从预算中保留通知所需字节，再缩小预览以适配剩余空间，因此面向模型的结果绝不会超过上限：

   ```text
   <retained head/tail preview>

   (Omitted N bytes. Full formatted result stored at: /…/session-…/…-web_fetch.txt. Use read with offset/limit, or grep this path to search within it.)
   ```

   当通知本身已占满预算时（上限极小或定位信息很长），预览为空，只返回通知。如果仅通知的替换内容仍会超过 `maxInlineBytes`，策略将保留内联结果；它绝不会发出超过上限的替换内容（而且上限内的替换内容总比原结果更小，因此这也意味着 spill 绝不会增加字节数）。

**尽力而为**：没有会话 owner、没有 `ctx.spillStore` 后端，或 `saveText` 拒绝 ⇒ 策略记录警告并返回原始结果。spill 失败绝不会将成功调用变为 `isError`，也不会隐藏内联结果。成功替换时只会更改 `content`；规范程序值保持不变。

**dispatch-log 分支：**注册在 `tools/code-dispatch-log` 上的第二个监听器，把同一套上限、替换流水线与尽力而为的回退应用到每个 `run_code` 子调用结果的持久副本上（工件标签为 `dispatch`，按子调用 id 归档）。程序取得的值不受影响——它早已完整跨过 worker 边界；`read` 子调用同样设界：日志副本不是模型上下文，因此不会发生 read-again 循环，而 `read` 恰恰是最容易产生巨型日志的工具（[原理](../../../.agents/notes/implemented/feature/2026-07-26-code-dispatch-log-spill.md)）。

## 范围

该策略只能看到最终格式化接口结果，看不到工具的内部资源或规范值。如果提供方已经截断内容（例如 `web-fetch-local.maxBodyChars`），spill 产物保存的是工具返回的完整格式化结果，而非完整原始源。提供方/资源上限仍必须存在，并且与该策略分离。`glob`/`grep` 负责对项级接口结果执行 spill，因为渲染前仍然存在完整的已获取值；bash 流负责在获取时 spill。通用策略预先注册自己的 waterfall 监听器，然后再委托，因此无论插件加载顺序如何，普通工具拥有的异步投影都会在通用字节限制之前完成。详见[工具输出 spill Agent Note](../../../.agents/notes/implemented/architecture/2026-07-08-tool-output-spill-files.md)。

## 模型体验

### 过大的纯文本结果

#### 模型所见

大小不超过 `maxInlineBytes` 的结果、嵌套结果、`read` 结果、已阻止的决定和包含非文本块的结果都保持不变。过大的纯文本接口结果会变为有界的首尾预览，后面附加 `(Omitted <bytes> bytes. Full formatted result stored at: <locator>. <retrievalHint>)`；存储或 owner 失败时，原始结果仍然可见。

#### Token 影响

成功替换后的内容最多为 `maxInlineBytes` 个 UTF-8 字节，并会保留在历史中直到压缩；完整 spill 文本不会重新发送给模型。

#### KV 缓存影响

仅追加；新可见内容位于可重用请求前缀之后，不会使现有 KV 缓存条目失效。

## 已知限制与待完成工作

- **只能对最终纯文本结果执行 spill**：混合内容结果、阻止反馈和 `read` 会原样通过；无法在此恢复先前已经发生的提供方截断或工具自有保留。
- **通知无法容纳时，该次调用的替换功能会禁用**：当上限极小或定位信息很长时，后端已经保存了无引用的 spill，但过大的原始结果仍会保留在内联位置。
