如何把整个会话事件日志折叠成上下文时间线?dsh-context 的 fold 设计与有界状态技巧
如何把整个会话事件日志折叠成上下文时间线dsh-context 的 fold 设计与有界状态技巧【免费下载链接】dsh-contextThe best DeepSeek Harness plugin for context insight and management, with context dashboard / browser and context command, for context statistics, composition, breakdown, evolution details, understanding how the context is made of, and how it evolves. 一站式 DeepSeek Harness 上下文可视化插件Context 面板及浏览器与 Context 命令透视上下文组成、演进、压缩、剪枝等事件与动作。项目地址: https://gitcode.com/gh_mirrors/ds/dsh-contextdsh-context 是 DeepSeek Harness 的一站式上下文可视化插件。它的核心是一座折叠器fold把整条会话的持久化事件日志逐条重放折叠成一条按请求划分的上下文时间线context timeline同时用一套有界状态bounded state技巧保证状态体积恒定——会话再长内存与存储占用也不会失控。本文带你读懂这套设计背后的 5 个关键技巧。为什么需要上下文时间线事件日志的三个问题DeepSeek Harness 会把每一次用户消息、助手回复、工具调用、注入、压缩compaction和剪枝prune都写进一份只追加的事件日志。这份日志是事实之源但直接读它有三个痛点太碎一次对话产生上百条事件看不出每次请求时上下文由什么组成太长日志只增不减全量载入内存不可行会缩水压缩事件把旧消息替换成新摘要事后重建当时的上下文变得很困难。dsh-context 的解法不是解析日志全文而是维护一份折叠状态每来一条事件就把状态向前推一步状态本身记录当前模型看到的表面surface、每个请求的 token 组成和压缩/注入等事件。最终这份状态被投影成 UI 里的上下文时间线。fold 如何工作把事件日志逐步重放成上下文状态在源码 src/host/fold.ts 中折叠器是一个纯函数三件套init / apply / view注册为会话投影单元contextTimelineinit创建一个空状态createTimelineState——表面列表为空、各类 token 和为 0apply框架每提交一条session/event就驱动一次applyTimeline把状态向前推一步view会话结束或推送时用buildTimelineView把状态快照成浏览器渲染的时间线数据。apply 步骤中几个巧妙的设计无关事件返回同一引用。turn 边界、流式 chunk 等不影响时间线的事件apply 直接返回原状态对象框架靠引用是否变化判断是否要推送更新避免无谓的界面刷新。变更时做惰性浅克隆。只有真正修改某条路径时才复制相关数组未动的部分与旧状态共享兼顾了不可变契约与性能。影子序号协议。压缩事件先武装一批将被遮蔽的节点序号pendingShadowedSeqs紧随其后的替换事件按序号精确移除——保证我们的分类记账与官方 token 计量的口径分毫不差。项目还用差分测试tests/diff-fold.ts在真实会话日志上验证折叠结果与官方投影完全一致。有界状态技巧让状态永远保持小而全折叠状态会被持久化所以它必须永远是纯 JSON 且有上界。dsh-context 用了五道上限src/host/config.ts中可配置按整轮turn裁剪绝不截断半轮请求记录超过maxKeptTurns默认 300 轮时折叠器从新到旧数轮次运行整轮地丢弃最旧的部分永远保留最近 N 个完整轮次。测试里 400 轮 × 4 步 1600 条请求会被裁到最近 300 轮的 1200 条且边界正好落在整轮起点见tests/host.spec.ts的 400 轮用例。这样按轮浏览时永远能展示完整的近期轮次不会出现半截轮。软限 硬限双保险整轮裁剪之上还有maxRequestSteps默认 1500 步作为硬底限防单轮几百步的病态会话事件列表maxEvents默认 400与归档maxArchiveNodes默认 400同样保留最新尾部。裁剪只截断历史明细不改变状态形状所以调整这些配置无需迁移旧状态。消费即删的映射callNames 不随会话生长tool/call事件会把调用 ID → 工具名暂存进状态对应tool/result折叠进来时立刻把该条目删掉。映射因此始终只有未返回的待决调用大小而不是整个会话的工具调用总数——对一个每步都要浅拷贝的持久化状态来说这点至关重要。永不裁剪的累计量被裁剪的只有明细请求记录、事件、归档而成本累计、图片总数这类运行总计永不裁剪——即使明细早已被保留窗口裁掉会话成本估算依然覆盖完整日志。这是有界与完整的分工明细可丢总数不丢。纯 JSON 前置条件一个隐蔽的坑持久化投影缓存要求状态是纯 JSON值为undefined的属性会让整个检查点不可序列化进而让整个会话的缓存写入失败包括重启后恢复会话列表依赖的标题行。因此折叠器对所有可选字段model、turn、step、pendingShadowedSeqs…采用有值才写入、用完即delete的写法从不留下undefined属性——这也是状态里反复注释强调的规则。被裁剪的历史去哪了归档与近似重建诚实声明被压缩/替换移出的表面节点不会直接丢弃而是打上gone时间戳存进归档archived供上下文浏览器逐步重建任意一次请求当时组装了什么。归档同样有界既按条数封顶也裁掉早于最旧保留请求的移除记录并把被裁掉部分记为archiveFloor——UI 据此明确提示该步骤的重建只是近似而不是静默少显示。有界但从不撒谎。上图是一次真实会话48 轮增长到约 563k token一次压缩✂ 标记回收 −535.5k 后从小窗口继续。切到 Step 粒度悬停任意一根柱还能立刻看到该步的轮次/步号、时间戳与估算 vs 服务商上报的 token 对照从状态到界面时间线如何驱动上下文浏览器buildTimelineView把状态投影成推送给浏览器的快照其中两个细节值得注意发送切片而非全量只推送最新 N 个节点 全部注入节点注入项如 AGENTS.md 总是排在最前若只推尾部会有 token 和、没有条目并附droppedNodes/surfaceFloor告知客户端哪些被截事件归属每个压缩/注入/切换事件被归因到它前后的请求Step 2→3或Turn 50 Step 8 → Turn 51 Step 1双指针一次遍历完成正是历史图上 ✂ 标记的由来。浏览器端拿到快照后历史图悬停可直接驱动上下文浏览器实时预览该步的组装结果延伸阅读fold 设计相关文件折叠器主体apply / view / 归档 / 裁剪src/host/fold.ts投影单元注册contextTimelinesrc/host/timeline.ts五道保留上限的配置与默认值src/host/config.tstoken 计价消息、工具 schema、图片src/host/pricing.ts有界状态与整轮裁剪的测试用例tests/host.spec.ts与官方投影的差分校验脚本tests/diff-fold.ts小结dsh-context 的 fold 设计给出了一套通用套路用纯函数重放事件日志维护可持久化的折叠状态状态形状固定且全部字段有界明细可裁剪而累计值不裁剪被裁部分用 floor 标记让 UI 诚实声明近似性。掌握了整轮裁剪、消费即删、纯 JSON、运行总计不裁剪这四板斧你就能把任何只追加的事件日志折叠成一条永远轻量的上下文时间线。【免费下载链接】dsh-contextThe best DeepSeek Harness plugin for context insight and management, with context dashboard / browser and context command, for context statistics, composition, breakdown, evolution details, understanding how the context is made of, and how it evolves. 一站式 DeepSeek Harness 上下文可视化插件Context 面板及浏览器与 Context 命令透视上下文组成、演进、压缩、剪枝等事件与动作。项目地址: https://gitcode.com/gh_mirrors/ds/dsh-context创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考