2026-08-12 12:30:19 +08:00
# spill 存储
2026-07-26 02:33:29 +08:00
[English ](spill.md ) | 中文
2026-08-18 19:00:37 +08:00
spill 存储 seam 是一项[能力 seam ](../../.agents/notes/implemented/architecture/2026-07-08-tool-output-spill-files.zh.md ), 它持久保存工具的超大文本, 并返回面向模型的定位符与检索指引; 该能力拆分到三个包: Service Definition( [dsh-spill ](../../packages/spill/spill ), `ctx.spillStore` ) 、Service Provider( [dsh-spill-local ](../../packages/spill/spill-local ),宿主文件系统中会话作用域的私有文件)和 Consumer( [dsh-spill-policy ](../../packages/spill/spill-policy ), `tools/post-execute` 策略) 。spill 是**一项可选能力**,不属于 agent loop( 智能体循环) 主干, 因此其词汇记录在此处, 而不在 [core.md ](core.zh.md ) 中。预览机制仍归 [dsh-output-retention ](../../packages/util/output-retention ) 所有;该 seam 只保存策略交给它的最终文本。
2026-07-26 02:33:29 +08:00
源码:[`packages/spill/spill/src/types.ts` ](../../packages/spill/spill/src/types.ts )
## 保存请求
2026-08-09 15:34:32 +08:00
`saveText` 是唯一的服务操作:原样持久保存 `content` ,并返回不透明的定位符、后端提供的检索提示和准确字节数。请求携带保存时的存储命名空间(`owner` )、生成内容的工具和调用(`source` ,用于命名和检查,而非访问控制)以及后端可用作命名提示的 `suggestedName` (它不是路径)。
2026-07-26 02:33:29 +08:00
```ts type-equiv
/** One request to persist text to a spill artifact. */
interface SaveTextSpill {
owner: SpillOwner
source: SpillSource
/**
* A caller-suggested base name (e.g. `web_fetch.txt` ). The backend sanitizes
* it to a single safe path segment before use — it is a hint, never a path.
*/
suggestedName: string
/** The full text to persist (UTF-8). */
content: string
}
```
```ts type-equiv
/**
* Save-time storage namespace for a spilled artifact. The session id lets a
* backend group storage under the producing session, but the returned
* {@link SpillLocator} is the model-facing handle. Forked sessions inherit
* locators already present in the seeded log; those artifacts are not copied or
* re-owned, and spills produced after the fork use the child session id.
*/
interface SpillOwner {
sessionId: SessionId
}
```
2026-08-25 23:47:20 +08:00
保留期清理可以连同其他旧会话产物一起使旧定位符失效; spill seam 不定义逐会话的清理策略。
2026-07-26 02:33:29 +08:00
```ts type-equiv
/**
2026-08-09 15:35:02 +08:00
* Tool and call that produced one spilled artifact — recorded by the backend for a readable
2026-07-26 02:33:29 +08:00
* filename and inspection. Not interpreted for access control; purely
* descriptive.
*/
interface SpillSource {
/** The tool whose result was spilled (e.g. `web_fetch` ). */
toolName: string
/** The model-issued call id the result belongs to. */
2026-08-19 13:03:32 +08:00
callId: ToolCallId
2026-07-26 02:33:29 +08:00
/** A short human label for the artifact (e.g. `result` ). */
label: string
}
```
## 结果
```ts type-equiv
/** A saved spill artifact: its locator, byte length, and backend-specific retrieval guidance. */
interface SpillRef {
locator: SpillLocator
bytes: number
retrievalHint: string
}
```
2026-08-18 19:00:37 +08:00
`SpillLocator` 是后端返回的[品牌化 ](core.zh.md#branded-ids )面向模型句柄。本地后端将它渲染为文件系统路径;远程或数据库后端可以渲染 URI、键或命令 token。消费方将它视为不透明值, 并使用 `retrievalHint` 渲染,而不是假定 `read` 始终是正确的检索机制。
2026-07-26 02:33:29 +08:00
```ts type-equiv
/**
* Opaque model-facing handle for one spilled artifact. A local backend may use a
* filesystem path; a remote or database backend may use a URI or key. Consumers
* render it with {@link SpillRef.retrievalHint}, but do not parse it.
*/
type SpillLocator = Branded< 'SpillLocator'>
```
## 服务
`SpillStore` ( `ctx.spillStore` ,定义于 [`packages/spill/spill/src/index.ts` ](../../packages/spill/spill/src/index.ts ))是只有一个方法的抽象服务:`saveText(input) → Promise<SpillRef>` 。它持久保存完整的 `content` , 并在实际存储失败( 权限、ENOSPC、后端不可用) 时拒绝。该 seam 只负责存储:不负责保留策略、工具结果替换或检索/搜索 API。
2026-08-12 12:30:19 +08:00
本地后端([dsh-spill-local ](../../packages/spill/spill-local ))写入 `<root>/session-<hash>/<random>-<safeName>` : 根目录是已配置或延迟创建的私有( 0700) 目录, 会话子目录采用 `sha256(sessionId)` ,并通过排他且仅所有者可访问的写入(`open(path, 'wx', 0o600)` )防止预先植入的符号链接重定向写入。其 `locator` 是本地路径,`retrievalHint` 则告知模型在该路径上使用 `read` 或 `grep` 。策略消费方([dsh-spill-policy ](../../packages/spill/spill-policy ))会把超过 `maxInlineBytes` 的纯文本最终结果替换为保留库生成的首尾预览和 spill 引用;该过程尽力而为:保存失败时保留原始内联结果,而不会把成功的调用变成 `isError` 。
2026-07-30 21:40:58 +08:00
<!-- BEGIN GENERATED cordis - surface (gen - cordis - catalog.ts) — do not edit between markers -->
< a id = "cordis-surface" > < / a >
2026-07-24 19:54:25 +08:00
## Cordis API
2026-07-30 21:40:58 +08:00
2026-08-18 21:02:50 +08:00
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` ) — the language sides differ only in locale-specific paired document paths. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer ](../cordis-primer.zh.md#dispatch-modes ), and the framework-inherited `ctx` API lives in [cordis-api/inherited.md ](../cordis-api/inherited.md ).
2026-07-30 21:40:58 +08:00
2026-08-09 15:34:32 +08:00
< a id = "ctxspillstore--spillstore-abstract-seam" > < / a >
2026-07-30 21:40:58 +08:00
2026-08-09 15:34:32 +08:00
### `ctx.spillStore` — `SpillStore` (abstract seam)
2026-07-30 21:40:58 +08:00
Abstract spill storage service. Subclass, implement saveText, and load the subclass as a plugin — it registers as `ctx.spillStore` (one implementation per context; loading a second throws, cordis' standard duplicate-service behavior).
Semantics every implementation must honor:
- saveText persists the FULL `content` verbatim and returns an opaque locator, exact byte length, and model-facing retrieval guidance.
- Storage is scoped by the request's SaveTextSpill.owner session; the backend chooses a private (not world-readable) location and a collision-free name derived from — never equal to — the caller's `suggestedName` .
- `saveText` REJECTS on a real storage failure (permissions, ENOSPC, backend unavailable); the caller decides how to degrade (the spill policy treats a rejection as best-effort and keeps the inline result).
```ts cordis-catalog
/**
* Persist `input.content` to a session-scoped spill artifact.
2026-08-09 15:35:02 +08:00
* @param input - the owner, caller-supplied source fields, suggested name, and full text to save.
2026-07-30 21:40:58 +08:00
* @returns the saved artifact's {@link SpillRef}; rejects on a storage failure.
*/
abstract saveText(input: SaveTextSpill): Promise< SpillRef >
```
2026-08-04 11:38:42 +08:00
Source: [`packages/spill/spill/src/index.ts` ](../../packages/spill/spill/src/index.ts )
2026-07-30 21:40:58 +08:00
<!-- END GENERATED cordis - surface -->