dsh-cwl
v0.2.1
Published
CWL — Context Window Lifecycle for DeepSeek Harness: structured context eviction (arXiv:2606.11213). Graduated, deterministic, zero-LLM eviction of exploration/action episodes when context pressure exceeds budget — no summarization lossiness, no hallucina
Maintainers
Readme
dsh-cwl
CWL — Context Window Lifecycle(上下文窗口生命周期) for DeepSeek Harness: 面向长时任务智能体的结构化上下文驱逐(eviction)。
范式论文:Beyond Compaction: Structured Context Eviction for Long-Horizon Agents(arXiv:2606.11213)
English | 简体中文
为什么不用摘要压缩(compaction)?
Compaction(上下文压力的常规应对手段)是用 LLM 把历史总结成摘要。根据 CWL 论文,它有四个结构性问题:
- 损失不可预测 —— 摘要器决定什么重要,而不是任务本身。
- 破坏结构 —— 因果链(工具调用 → 输出 → 决策 → 动作)被压平成散文。
- 阻塞性开销 —— 任务进行中、token 紧张时还要触发一次完整 LLM 调用。
- 压缩诱发幻觉 —— 在长度压力下做摘要,是已知的失败模式。
CWL 把对话记录当作结构化的工作记录,做确定性驱逐:智能体的轨迹被自动推导成类型化 episode 图(探索 expl / 动作 act,带依赖边);当上下文压力超过预算时,一个零 LLM、确定性的策略按分级逐步剥除内容——先驱逐探索段(纯上下文,最安全),再驱逐效果已落盘的动作段。用户消息永不驱逐。
工作原理
- Episode 推导(自动,无需智能体标注):连续的同类工具批次合并为语义段(
expl表示纯读/搜索类,含只读 bash(如 grep/cat);act表示有副作用类:edit/write/写型 bash);每条用户消息关闭当前段(轮次边界),且每段有批次上限——单请求的连续自主长跑(几十次工具调用)也会分成多个有界、可驱逐的段,而不是塌缩成单个巨型段;某个act触碰的文件如果之前被某个expl读过,则建立依赖边。 - 压力计量:真实上下文压力 = input + cacheRead + output + reasoning tokens(从
assistant/messageusage 事件累计——tokenMeter.measure().totalTokens不含 cacheRead,而 cacheRead 在长会话中占大头)。 - 分级驱逐(挂在
agent/pre-step瀑布上,每次 LLM 调用前,由细到粗):- 内容裁剪(细):
expl段内的大工具结果先改写为短标记([cwl-stub: …])——保留结构、削减 token、工具配对不受影响 - 整段驱逐(粗):先
expl段(纯上下文,保留一行"已探索: …"标记),再最旧的已完成act段;一律按 surface 位置块执行(位置是 replace 后唯一可靠的不变量——驱逐永不切开 tool-call/result 对造成孤儿消息) - 永不触碰最新尾巴(preserve-recent)和用户消息
- 被驱逐区间用轻量标记替换(官方 surface-replace 接口;原始事件保留在日志中,
cwl_recall可恢复文件路径)
- 内容裁剪(细):
安装
dsh plugin --profile <name> add dsh-cwl # 从 npm 安装
dsh plugin --profile <name> add github:kalifun/dsh-cwl # 或从 GitHub 安装或者把目录放进你的 composition:
- id: dsh-cwl
name: ./dsh-cwl/index.js使用
无需配置。上下文在预算内(默认模型上下文窗口的 80%)时插件完全不干预,压力超过预算才开始驱逐。
# 可选:覆盖预算(tokens)——用于测试压力行为
DSH_CWL_BUDGET=30000 dsh web驱逐策略(确定性重放验证:驱逐价值 −24% cacheRead、策略无关;batch 均值最优 −24.7%、7 会话方向一致 → 默认如下,可用环境变量覆盖):
| 环境变量 | 默认 | 取值 | 作用 |
|---------|------|------|------|
| DSH_CWL_EVICT_ORDER | tail | tail / oldest | oldest 优先驱逐最老段 |
| DSH_CWL_EVICT_BATCH | 开 | 0 / false / off 关闭 | 合并相邻 episode 为一次 surface replace(减少缓存打断) |
| DSH_CWL_EVICT_TAIL_WINDOW | 0 | N | 只驱逐 end 落在最近 N 个 surface 节点内的段 |
| DSH_CWL_STRIP | 开 | 0 关闭 | 细粒度级:整段驱逐前先裁剪 expl 段内的大工具结果内容(保留结构) |
| DSH_CWL_STRIP_THRESHOLD | 1500 | 字符 | 结果文本超过该长度才裁剪 |
# 回退到保守配置(oldest + 逐段 replace)
DSH_CWL_EVICT_ORDER=oldest DSH_CWL_EVICT_BATCH=0 dsh web会话分析(逐轮 token 明细 + "驱逐后下一轮 cacheRead" 指标):
node tools/analyze-session.mjs <session.jsonl>面向智能体的工具:
| 工具 | 用途 |
|------|------|
| cwl_recall | 列出被驱逐 episode 涉及的文件路径,按需重新读取 |
观测端点:
| 端点 | 用途 |
|------|------|
| GET /api/cwl/evictions | 驱逐日志(会话 → 被驱逐的 episode) |
| POST /api/cwl/force | 调试:对某个会话强制驱逐一次 |
验证
node check.js # 纯函数单元检查(episode 推断/驱逐策略/裁剪/配对)能力基准(live,helmsman 平台):BENCHMARKS.md —— 固定测试方案 (场景 A:12 轮长会话;场景 B:单请求自主长任务 ×3)配逐版本数据行,每次行为变更后刷新。
离线回归工具(用你自己的本地会话运行,数据不出机器):
tools/cache-replay.mjs(确定性 cacheRead)、tools/replay-real.mjs --apply(真实 surface fold +
工具配对断言的引擎 apply 层回归)、tools/eval-episodes.mjs。
License
MIT
