Skip to content
←返回开源项目

OPEN SOURCE DEEP DIVE

Agent ContextLLM智能体ObservabilityDeveloper Tools

dsh-context:把 DeepSeek Harness 的上下文窗口变成可对账的账本

DeepSeek Harness(dsh)的上下文洞察与管理插件:五个入口覆盖跨会话总览、单会话组成/趋势/事件/文件活动与子 Agent 编队;宿主侧把 V4 持久事件日志 fold 成逐请求组成时间线,计量口径与运行时自身投影逐条验等,兼容矩阵按基线 tag 用真实 harness 源码探针。

bowenliang123/dsh-context2.8kTypeScriptApache-2.03 min read

它解决的是什么:上下文窗口是 Agent 真正的预算表

跑过长会话的人都知道那种失控感:对话越来越慢、工具调用开始丢信息、某一次自动压缩之后 Agent 像是换了个人。问题几乎从来不在模型,而在上下文窗口里装了什么、什么时候装的、谁把它挤掉的。dsh-context 是 DeepSeek Harness(@deepseek-ai/dsh,官方开源的 Agent 运行时与 Web UI)的一个插件,做的事情只有一件:把「上下文」从一个黑盒变成一个可以对账的账本——每一轮请求窗口里装着什么、每个部分值多少 token、哪一步把它改写了、整个 Agent 家族一共花了多少钱,全部摊在明处。

它在 npm 上的包名就是 dsh-context,Apache-2.0 协议,仓库同时提供英文与简体中文界面。项目获得了 DeepSeek Harness 团队成员的公开推荐与加星。版本号 0.73.x 的迭代节奏很快,但每次发布都带着一份逐条给出证据的兼容矩阵文档,这在插件生态里相当少见。

五个入口,同一本账

插件把同一份投影状态投到五个面上,读者从哪个面进来都能看到同一套数字:

入口回答的问题
Context Insights 上下文洞察页跨会话总览:KPI 栏(会话数、计费 token、估算成本、缓存命中率、轮次、工具执行、活跃时长)、7 天用量柱状图、8 周活动热力图、组成环形图与可筛选可跳转的会话卡片
Context 标签页单会话完整仪表盘:统计、token 组成、逐请求趋势、事件日志、文件活动
Fleet 编队标签页Agent 家族拓扑:当前 Agent、它的父级链与所有子 Agent,每人一张实时卡片
右侧栏 Context 面板聊天旁边的同一仪表盘,读聊天时不用切页
/context 斜杠命令不离开聊天的居中弹窗:当前组成 + 上下文浏览器

外加一张 Preferences 卡片:视图默认位置、趋势粒度与模式、排序方式,以及拼接到每个会话系统提示词首尾的前缀/后缀——最后这项等于把「按部署定制的人格或行为规则」挂在了 harness 自己的提示词装配链上,对所有 Agent(含子 Agent)生效。

dsh-context 的上下文洞察页:KPI 栏、7 天用量图、活动热力图与会话卡片

宿主侧:把持久事件日志 fold 成逐请求时间线

整个插件的数据核心是 src/host/fold.ts 里的一次折叠(fold):它注册为 harness 的 session projection 单元,框架每提交一条 session/event 持久事件就调用一次 applyTimeline,把事件回放成「每一次模型请求的窗口组成」时间线,结果经投影缓存落盘。这条路径上有几条工程纪律值得单独说:

  • fold 是全函数(TOTAL):未知事件类型原样返回旧状态;畸形事件整条丢弃,绝不留下半截状态;任何 undefined 值的属性都不允许物化进持久状态。原因写在注释里:投影注册表驱动 apply 时没有错误边界,一次抛异常会让该单元的格子与推送流永久停摆,一个 undefined 属性会让这个会话之后每一次投影缓存写入全部失败。
  • 同引用即无变化:事件不改变状态时必须返回同一个引用,Object.is 是变更feed 的闸门;有变化才用懒浅克隆构造新引用。状态保持纯 JSON 且有界,这是缓存检查点的前提。
  • 分类口径:developer/message 折进「注入上下文」而不是「人类输入」;技能机制(可用技能目录、/skill 调用、skill 工具加载)单独成桶,不让某个悄悄加载的技能把 footprint 藏进注入上下文与工具结果两个桶里;工具结果是否出错只认 message.isError,只有敌意构造的 envelope 才会降级为「未标记」而不是抛错。
  • 缓存演进有规矩:只有「后续事件无法回填」的变更才 bump 单元的 stateVersion(滚动总计、step 结束才 flush 的集合、物化信封);服务期可现算的字段不 bump,新 wire 字段一律 additive-optional。过期行从持久日志重折,预热同时探针 contextActivity 与 contextTimeline,超过 2000 条事件的会话在内存里切片重折再一次性写回,与单趟冷快照逐字节相同。
  • 冷读有闸门:所有冷读走宿主级 FIFO 闸门,一次一条,且只在 V8 堆低于上限三分之二时放行;被跳过的读走调用方设计好的无数据路径,而不是失败——重观测不能把宿主自己压垮。

计量口径:mirror, don't import

插件打印的每一个 harness 也打印的数字,都不 import harness 内部实现(那些函数大多在 kernel bundle 内部、没有包导出),而是镜像:在 src/shared/estimate.ts 里按 harness token-meter 的固定密度重实现一遍(约 4 字符 1 token,每个内容块 +4,角色框架 +4),兼容矩阵里再用差分测试对着 harness 自己的投影(sessionStats / contextBreakdown / contextPressure / tokenUsage)逐条验等。头条占用率与组成读的是与聊天输入框旁上下文环同一套官方投影,所以卡片上的百分比和环上说的永远一致。

成本侧同样克制:标价来自 models.dev 的列表价,悬停可看每百万 token 单价;只有 DeepSeek 区分峰谷,其列出的是谷价,峰段按 2 倍计,峰谷判定按北京时区的工作日/周末窗口实现,并且明确声明不维护法定节假日日历——节假日的工作日请求会按峰价估算而平台按谷价计费,tooltip 里把这条规则写明而不是假装精确。自动压缩的保留区也镜像 compaction-basic 的出厂默认(thresholdRatio 0.8、headroom 65536):当前窗口减去预留输出与 headroom 非正时后端直接拒绝配置,条形图就不画遮罩。文档里甚至列出了「声明的分歧」:计时卡的等待/生成边界从第一个可观测时刻切开,而不是 meter 的首 token 锚点,避免 marker 平铺的解码桶与切分重复计同一段窗口。

客户端边界:一切形状在边界重证

浏览器那一半对宿主推送的数据不做任何信任:timelineSource.ts 在边界处重新证明每个集合、标量缺席保持 null(即加载屏)、用结构化窄化代替盲 cast、形状不过的元素跳过而不是抛错;每个条目的处理相互隔离,任何异步读最终都resolve 成数据或一条可重试的失败说明,绝不留永久转圈;推送的版本号回退会重置明细账,旧读永不并入新头。

能力挂载同样保守:每个能力都骑在可选 seam 上(延迟的 ctx.inject、用前重新证明的脸、受保护的注册),没有服务就没有能力——不挂 pending fiber、不抛错;每页最多挂载一次(issue #137 的根因是客户端插件运行器会给每次注册强配新的 shadowing 优先级,槽位注册表的同优先级重复守卫跨双挂载永不触发);会话跳转用侧边栏自己的动词 uiWorkspace.openSession,而不是 0.1.6 选择重构里已删除的 open(id);fetch 路由一律浏览器相对拼写(issue #159):部署在带路径前缀的反代后面时,根绝对 /api/... 会在网关 404,相对拼写才能保住前缀。

兼容工程:形状驱动,而不是版本分支

docs/compatibility.md 是这份仓库里最硬的一份文档:声明只支持 0.1.7-rc.2、0.2.0-rc.2、0.2.1-rc.2 三个发布(都是 V4 持久日志世代),并且规定一条形状驱动的代码路径,永不按探测到的 harness 版本分支;信任 harness 的迁移保证,仓库里不存在任何 pre-V4 拼法的读取器,不可转换的日志会被拒收为 SessionFormatUnsupportedError 而不是原样端上来。低于下限的 harness 会在运行时再被插件自己拦一次:归零的兜底单元加一个升级弹窗;版本探测失败则 fail-open 进真实单元——探针误火绝不允许把一个正常运行的部署打成空白。

验证侧是 compat 这个 vitest 项目:按基线 tag 把 harness 的真实源码 staging 出来,把构建产物插件boot 进该 tag 真实的注册表与自带 cordis 容器,探针宿主与客户端 seam,断言已退役的 seam 确实缺席(settings.register、settings get(ns)、sessions open(id)),并断言 fold.ts 开关的每个事件族在每个基线里都有声明。CI 缺 tag 即失败。另有逐发布的安装/卸载手工检查:一次性 DSH_HOME 里 dsh plugin add 后 --dump-config 必须恰好 1 行组合配置,remove 后 0 行。

Context Trend:逐请求堆叠柱、step 简报与压缩事件标记

怎么读这几张图

Context Trend 一次模型请求一根堆叠柱,比按消息更细:柱下三行白话 step 简报(User 想起始消息、In 列新进入的内容、Response 给回复与工具调用),点一行直接跳到浏览器里那条消息;压缩与裁剪用 ✂ 钉在发生的那根柱上;粒度可切 Step/Turn,模式可切 Total/Delta(Delta 模式下压缩会沉到基线以下);Overview 开关把整条会话压成一条连续堆叠带,所有压缩drop 与组成漂移一眼看完;DNA 模式则把上下文按模型阅读顺序完全摊平,每行带 prompt 位置、类别、引入 step 与 token 价。

Context Browser 打开任意一次请求的装配盒:七个可折叠类别,每个元素再展开到实际内容——系统提示词全文、每个工具的 JSON schema、消息文本、推理、工具参数与工具输出;每个工具 schema 行带来源chip(tool-* 一方包、dsh-* 能力包、mcp:<server> 代理、或被实时观察到的插件);与上一轮的 diff 用带符号徽章给出;压缩之前的 step 从已移除消息归档重建,并且卡片会自认「这一步的组成只是近似」。

File Activity 把 Agent 对你文件做过的事逐行列出:读/写/搜各自的计数chip 即过滤器,每次 edit/write 贡献估算的 +added/−removed 行delta 并按文件与总计汇总;原生工具、Minimal 预设的 str_replace_editor、以及 PTC run_code 程序里的嵌套调用都被折进按工具的行;搜索命中落在真实文件上并带命中数;点行展开完整操作日志,每条操作跳回浏览器里对应的工具结果。

Fleet 标签页的 Agent 网络:父级链与七个子 Agent 的实时卡片

Fleet 把 Agent 家族画成一张图:每个 Agent 一张卡(标题、实时上下文大小与窗口占用、组成条、运行时长与 step 数),边按血统色系从父级散开;悬停点亮整条链,点击跳进该会话自己的 Context 标签页;运行中的 Agent 入边带绿色脉冲。窄面板会把家族折成整齐的行,永不出现横向滚动条。

安装、边界,以及它为什么属于 harness 板块

安装只有两步:在 Harness 网页/桌面版的 Add plugin 向导里搜 dsh-context 点 Install,或 dsh plugin --profile web add dsh-context 后 dsh web 启动,无需构建无需重启。已知边界都写在明处:图像 offload 之后、压缩之前,折叠读的是原始持久事件,因此 offload 掉的消息仍保留 offload 前的图像计数与价格;注册了 complete 系统提示词段的部署只保留该段,前缀/后缀被丢弃。

把它放进 harness 能力域的理由很直接:harness 类基础设施的竞争点正在从「能不能跑 Agent」转向「能不能看清 Agent 在消耗什么」。dsh-context 给出的不是又一层日志,而是一套与运行时自身投影逐条验等的计量口径、一份逐 tag 探针的兼容矩阵、和一条「形状驱动、边界重证、fail-open」的插件工程范式——这三件事恰好是 harness 生态里最稀缺的部分。

作为亚马逊联盟会员,我们可能从符合条件的购买中获得佣金。