# dsh-prime-agent 当前架构 ## 文档范围 本文只描述仓库当前已经实现并随包交付的架构,不记录版本路线、未来阶段或候选设计。代码、`agent-presets/prime/agent.cordis.yml` 与 `cordis.patch.yml` 是最终事实来源。 `dsh-prime-agent` 在 DeepSeek Harness(DSH)之上提供一个 RLM-first 控制面:Prime Session 通过唯一模型可见的 `repl` 工具(参数 `{ code }`)使用持久 TypeScript Realm,普通 Session 继续使用 DSH 官方 one-shot Code Runtime。插件复用 DSH 的 Agent、Subagent、Jobs、权限、日志和 Session 生命周期,不创建第二套 Agent Loop。 ## 领域术语 | 术语 | 当前含义 | | --- | --- | | Session | DSH 持有的会话身份与持久事件日志,是 Realm、child 和 local learning state 的归属键。 | | Realm identity | 由 Session 稳定映射得到的不透明 id,用于把同一 Session 的 Prime cell 路由到同一个 Realm。 | | Worker generation | 一个 Realm 当前活着的 Worker Thread。hard kill 后会换代,旧 live namespace 永久丢失。 | | live namespace | Worker generation 内跨 cell 保留的普通顶层变量、函数、对象、Map 和索引。它不是持久存储。 | | cell / run | 一次 `repl` 调用。一个 Realm 同时只执行一个 cell。 | | binding lease | 当前 cell 对 DSH 工具 binding 的临时调用权;cell 结束即撤销,保留下来的旧 wrapper 不能越轮调用工具。 | | Realm ownership lease | 一个 Realm 的跨进程 live-heap 所有权;不同 Realm 不互斥。 | | continuable child | 由 DSH 持久化、可接收后续消息并可冷恢复的子 Agent。其 id 不是 Job id。 | | Job | DSH 的通用后台任务。它有 `job_output`/`job_list`/`job_kill`;continuable child 不通过 Job 收集结果。 | | Terminal session | DSH 持有的 owner-isolated 持久 PTY;POSIX Prime 使用 Bash,Windows Prime 使用 PowerShell。后台 `terminal_send` 另产生可监控的 `pty-send-*` Job。 | | handoff file | 父子 Agent 在共享工作区传递大材料或结果的普通文件。只写一次是 policy 约定,不是文件系统授权。 | | harness entry | `refine` 注入 prompt 的一条有界、非可信建议记录。 | | refinement transaction | 对 harness entries 的一次 apply 或 rollback 审计记录。 | “Agent Family”只描述 DSH 持有的直接父子关系和后代树,不表示本插件拥有独立 family registry。“Context Capsule”不是当前运行时对象;当前跨 Agent 上下文原语是 handoff file。 ## 系统组合 ```text DSH profile ├─ dsh-prime-agent/cordis.patch.yml(纯插入) │ ├─ 官方 code-runtime row(原样保留) │ │ └─ 非 Prime 会话 ─────────────→ 官方 one-shot Worker │ ├─ dsh-prime-agent/runtime(prime-code-runtime) │ │ └─ primeRealmRuntime 服务 │ │ ├─ Realm identity ← 可信 exec.agent.id(Agent scope 解析) │ │ └─ Realm pool ──→ persistent Worker │ └─ 官方 tool-subagent-control(send_message / steer 最近 step) ├─ dsh-tool-monitor/cordis.patch.yml(同行安装的独立 bundle) │ ├─ MonitorableJobRegistry 替换具体 jobs-local adapter │ └─ job_monitor(流式 Job 的逐行 JavaScript 正则订阅) │ └─ Prime Agent scope ├─ agent-presets/prime(独立 agent-plane 组合) │ └─ owner-isolated Terminal registry + Bash/Pwsh backend + terminal tools └─ dsh-prime-agent ├─ 唯一模型可见工具 repl(参数 { code }) ├─ cell 内隐藏绑定 tools / agents / jobs ├─ cell 内组合工具 apply_patch(Codex 兼容 Add/Update) ├─ control-plane policy(Persistent REPL guidance) └─ refine ``` 包有两个运行入口: - `dsh-prime-agent`:Agent scope 内的 `repl` 工具注册、Realm 身份解析、control-plane policy、continual learning 与 prompt assembly(模型 catalog 只保留 `repl`)。 - `dsh-prime-agent/runtime`:host scope 内的 `primeRealmRuntime` 服务、Realm pool、按 Realm 的进程 lease、父进程生命周期监控与 Prime preset 落位;官方 `code-runtime` row 完全不动。 ## 安装与 preset 随包 `cordis.patch.yml` 只执行一个操作:把 `dsh-prime-agent/runtime` 作为新 row 纯插入。官方 `code-runtime` row 原样保留——Prime 包不替换它、不携带也不挂载 one-shot fallback,非 Prime 会话继续由宿主官方 runtime 按官方语义处理。该 host row 注册唯一命名的 `primeRealmRuntime` 服务、监控直接父进程并在启动时落位 Prime preset。标准安装命令把 `dsh-tool-monitor` 作为第二个 profile bundle 直接加入;不能把它仅作为 Prime 的传递依赖,因为 DSH 只把 profile 的直接依赖纳入有序 bundle patch 层。 相邻 Agent 之间的消息投递完全复用 DSH 0.1.2-rc.1 base bundle 的官方 `tool-subagent-control`。DSH 0.1.2-rc.1 撤下了 child 专用的 `tool-subagent-report` row,改为对 parent 与 child 统一暴露一个 `send_message({ agent_id, message })`。每条被接受的消息都走 `Agent.steer()`:运行中的目标在最近 step 边界消费,空闲目标则开启新一轮;continuation manager 同时负责唤醒记账以及消息先于后续 settled notice 的 next-step FIFO 顺序。本包不再替换或复制该能力。 runtime 启动时把随包 Prime preset 复制到 `$DSH_HOME/.agent-presets/prime`,仅在目标目录不存在时写入。已有目录永不覆盖;要采用新快照必须由操作者删除旧目录后重启。 Prime preset 在独立 `terminals` realm 中装配官方 `dsh-terminal` registry、平台选择的 `dsh-terminal-bash` adapter 和 `dsh-tool-terminal`。Terminal session 按调用 Agent 隔离;backend 继续消费 Host sandbox policy 与 subprocess implementation。`job_monitor` 不订阅整个 Terminal scrollback,而是订阅后台 `terminal_send` 注册的单个流式 `pty-send-*` Job;独立输入始终走官方 `terminal_send`。 默认 preset、默认 tools mode 与其他 preset 不会改变。 ## 唯一模型控制面 Prime Agent scope 的模型 catalog 只含一个执行工具 `repl`。prompt assembly 先走完整流程,再在 `next()` 之后把 tools 列表过滤到只剩 `repl`,同时删除固定 `harness:identity` 和名称以 `tool:` 开头的隐藏能力独立提示:identity 只陈述实现身份,不提供操作事实;工具提示按外层直接调用编写,会与 Prime 的唯一 `repl` 路由冲突。`refine`、Subagent、Jobs、文件系统、MCP 及其他工具不进入模型 schema,只作为 cell 内预加载的隐藏绑定出现,其生成 declaration/JSDoc 是模型使用能力的唯一工具级契约。模型直接调用 `repl` 之外的任何工具都会被 guard 拒绝,并返回包含被拒工具名及对应 `tools.(args)` 恢复路径的纠错消息;组合不满足该不变量时,assembly 明确失败。Prime preset 的 `prime-tool-restrictions.config.deny` 声明要排除的 base 全局工具,scope-local 通用插件只负责调用 `ctx.tools.restrict()`:当前排除通用 `workflow`;上游已移除 `str_replace_editor` 并默认关闭 `ralph`;spawn/fork Subagent 仍复用内部 workflow provider,但该 backend 不向 Prime 暴露 `workflow` 工具。 `repl` 的参数是单个 `code` 字符串。插件把官方“一次 async 函数”说明改写成持久 REPL cell 说明: - 支持顶层 `await`。 - cell 的末尾表达式是结果,顶层 `return` 无效。 - 普通顶层 binding 留在同一 live namespace,供后续 cell 直接使用;模型文案把 REPL 定义为 live notebook,要求把每个读取、搜索和命令结果绑定到命名 `let` 变量并从变量继续切片、过滤或转换,在来源仍有效时避免重复调用;输入或外部状态变化后刷新受影响证据与派生计划。 - cell 内预加载三个绑定命名空间:`tools.*`(当前 Agent catalog 中除 `repl` 外的全部工具)、`agents.*`(`spawn`/`fork`/`list`/`send`/`interrupt` → `subagent`/`subagent_fork`/`list_agents`/`send_message`/`interrupt_agent`,外加私有子模型成员 `query`/`queryMany`,见「子模型调用」)、`jobs.*`(`list`/`output`/`kill` → `job_list`/`job_output`/`job_kill`)。程序始终得到 canonical value,SDK 从当前 catalog 生成真实 `ToolOutputMap`;对象结果若未经转换直接成为 completion,Worker 才用关联的官方 content 展示。SDK 只额外声明 `declare const $_: unknown`,不提供 completion id 或历史管理接口。 - 固定 Agent 文案只教授唯一 `repl` 路由、cell 内预加载命名空间、declaration/JSDoc 的权威性、不可使用 `import`/`require`、code mode 的经济学(只有 completion 与 `console.log` 进入对话、每 cell 约 12 KB 的事前显示预算、先归约再显示、额外往返比额外输出更贵)、persistent TypeScript、把结果绑定到命名变量并跨 cell 复用、typed `ToolOutputMap` 的同 cell 串联、「单调用原样回流」红旗与「一次 grep 能定位就直接读」反向条款、合法参数对象字面量、解析失败不执行并报告 cell 行列、已解析工具值、completion intrinsics、spill 后从变量继续而非重打印或重调用、preview 不可解析、Windows 路径优先 `/` 与紧凑 live 工作集;declaration 之前附三个归约型样例(批量 + 归约、先 grep 后范围 read、早停),仅当 `grep` 与 `read` 都在 catalog 中时渲染。共享 orchestration guidance 把 TypeScript 定位为编排语言,另要求不确定的文件路径从已知父目录 `glob`、不确定的目录路径通过 `pwsh` 检查父目录、package manager/formatter/build/codegen 后重新读取可能被重写的文件、委派判据(并行重上下文调研或独立实现交给 agent,单次已知查找/编辑/命令直接做,只要结论、计数、路径)以及 compaction checkpoint 与 `live namespace restarted` 通知的区别(前者变量仍在,后者才从文件重建);不重复具体工具 schema,不拼接用户聊天、具体任务、仓库路径、历史失败或可选工具名。 - 必须跨 Worker 或 host 重启保存的进度写入工作区文件。 开发脚本 `scripts/dump-prime-prompt.mjs` 从空 root 应用已安装的 DSH base patch、同行 Monitor patch、本包 host patch 和随包 Prime preset,并通过一个隔离临时 `DSH_HOME` 创建真实 Agent scope 后调用 `assembleContextFor`。它把 `renderPrompt`、`renderContextSections`/`joinContextSections` 和 assembly tool schemas 渲染为分节纯文本,不调用模型。默认结果写到当前目录的 `prompt-dumps/prime-prompt.txt` 且 stdout 只返回路径,避免诊断本身制造超大终端工具块;`--stdout` 是显式 opt-in,`prompt-dumps/` 不进入版本控制。脚本移除只服务于开发热更新且要求特殊 Node 启动 flag 的 HMR row,不改变任何 prompt/tool contribution。 Prime 不增加搜索 provider。源码发现仍调用 DSH 原生 `grep`;prompt assembly 按工具名复制 schema,先从 Prime 的 `pwsh` 副本移除与外层 TypeScript 正斜杠规则冲突的 native Windows path 句子,再在 `bash`、`edit`、`glob`、`grep`、`pwsh`、`read`、`subagent`、`subagent_fork`、`web_search`、`write` 的 description 后追加 schema 尚未表达的使用约束,并为 `todo_write` 替换仅模型可见的 description;这些变换不修改 catalog 中共享定义。`read`、`grep`、`bash`/`pwsh`、`web_search` 各带一句归约提示(长文件先 grep 再范围 read 并只显示切片;`matches` 超过一屏先分组或计数;`stdout.text` 只显示需要的行;`sources` 只显示要用的标题与 URL);`subagent`/`subagent_fork` 说明 prompt 应含目标、报告格式、路径与边界,并要求 child 以几百字的结论、计数、路径回报,大材料写文件。`edit` 说明首次修改和 stale retry 前读取当前文件,`write` 说明只用于创建或完整替换并优先用 `edit` 做局部变更;`glob` 明确递归文件搜索不能枚举目录,`pwsh` 的 canonical native path 句子必须在 Prime 副本中严格匹配一次并移除,工具 JSDoc 只说明重引号命令先单独绑定并按内容选择 `String.raw` 或小字符串片段,多阶段工作拆成顺序调用;`todo_write` 删除逐项即时刷新的高频契约,只用于需要用户可见跟踪的长流程或真实并行工作,并把更新收敛到有意义的阶段边界;`grep` 明确字符串参数仍按正则解释,普通代码搜索优先省略标点,确需正则标点时使用无 flags literal 的 `.source`,使模型只写一层正则转义且继续向 canonical `grep.pattern: string` 传参。多个代码片段应在同一 cell 内并行运行独立的小型 `grep`,而不是拼成大型正则,并在 parse error 后显式简化或拆分。生成 SDK 和 Realm interface 都保持 DSH canonical `grep.pattern: string`,不改写 `ToolArgsMap`,也不在 Worker 中投影非 JSON 参数;DSH schema、授权、执行与日志路径不变。 MCP 同样不进入 Realm runtime。profile 显式安装 DSH Host MCP client 后,server tools 注册到统一 `ctx.tools` catalog,repl 单元自动获得对应 `tools.*` 绑定;连接、认证、重连、工具代际、子进程与清理由 Host 插件拥有。本插件不复制上游的 Python kernel-owned MCP 或 ACP MCP program。 ### 本地 `apply_patch` 组合能力 `dsh-prime-agent` 在 Agent scope 注册 `apply_patch`,因此它会进入 Realm 自动生成的 `tools.*` SDK,但外层 prompt assembly 仍只暴露 `repl`。该工具不是新的文件系统 provider:parser 与 planner 在本地纯计算层对齐 Codex patch grammar 与默认 `NormalizeToLf` 文件更新算法;支持 marker 周边空白、heredoc wrapper、Environment ID、重复路径的顺序规划、EOF 和纯追加,并按 exact、忽略行尾空白、忽略首尾空白、Unicode 标点/空格归一化的顺序选择首个匹配。parser 只额外拒绝 NUL 路径;相对路径、绝对路径及包含 `..` 的路径均原样交给当前 Agent catalog 中正式的 DSH `read` / `write`,由 owning Session 的文件能力解析并授权。模型可见 description 还要求 patch 内容含 backtick、`${...}` 或反斜杠时不要直接嵌入 template literal,而应改用 exact `edit`、拆分 patch 或安全构造字符串。nested dispatch 通过 `ctx.tools.execute(...)` 转发 owning Agent、parent token、root call id 与取消信号,DSH 继续拥有 Session cwd、sandbox、approval、observation、日志和单文件原子发布。 当前组合层支持 `*** Add File` 与 `*** Update File`,且 Add 与 Codex 一样可覆盖已有文件;Delete/Move 在 planning 阶段明确失败。所有目标先完整读取并完成 Codex 风格 hunk 定位,之后才发生第一次 write,因此 parse、路径、读取或 planning 失败没有文件副作用。正式 `read` 的 canonical 行 DTO 不携带原始换行 metadata,executor 会把快照规范化为 LF;更新后的非空文件按 Codex 默认模式补齐末尾 LF。 多文件写入是按 patch 顺序执行的多个正式 DSH `write` 调用,不是 batch transaction。后续 write 失败时工具返回 `PARTIAL_APPLY` 并列出此前成功路径,不伪装成成功,也不宣称自动回滚或 crash-atomic。当前 DSH 没有受策略保护且可组合的 delete/rename seam,因此本插件不通过 Node `fs`、shell、`git apply` 或空文件写入模拟这些操作。 `apply_patch` 在工具定义 seam 上实现标准 `DiffCallView` / `DiffResultView`。调用阶段从 patch 解析出按文件、按 hunk 排列的 `{ path, oldText, newText }` 与可打开位置;成功结果优先使用持久化 `presentationMeta`,nested dispatch 没有 result metadata 时从同一 durable patch 参数重建等价视图。Prime REPL bridge 不发明独立 UI metadata,而是与官方 `run_code` 一样,在真实开始与结算时写入 `tool/ptc-dispatch-start` / `tool/ptc-dispatch`,携带 root/parent/sub-call identity、JSON arguments、`content` 与 `isError`。官方 Web 直接把这些事件折叠成递归 `subCalls`;TUI 通过同一协议和工具 presenter 渲染。失败、旧日志或无法解析的参数仍走 generic error fallback,绝不把失败意图渲染成已应用差异。 `edit` 仍保留:它的窄 interface 对一次 literal replacement 更省 token、更容易审阅,也能保持 `old_string` 唯一匹配约束;`apply_patch` 用于相关的多 hunk 或多文件 Add/Update。`write` 只用于有意替换完整文件。模型 policy 按这三个粒度选择工具,不做 alias 或兼容 shim。 ## Realm 身份路由 `repl` 的执行路径不使用握手,也没有任何模型可见的身份工具: 1. 工具执行要求存在 owning Agent 会话(`exec.agent`);缺少可信 Agent/Session 执行上下文时直接失败。 2. Agent scope 用可信的 `exec.agent.id` 作为 session owner,从共享 `/realm-identity` 存储解析该会话稳定的不透明 Realm identity(首次访问时生成并持久保存)。 3. 插件为本次 cell 构建 per-run 租约绑定(`tools`/`agents`/`jobs`),并把 Realm id、程序、绑定与 abort signal 交给 host 侧的 `ctx.primeRealmRuntime.run(...)`。 4. host 服务按 Realm id 在跨进程 lease 下准入对应 Realm:不同 Session/Realm 可由不同进程同时运行;同一 Realm 已被其他存活进程持有时明确拒绝,不降级。 5. 无法解析出 Realm id、命名了不可用的 Realm id,或缺少可信执行上下文时 fail closed——明确报错,不路由到任何其他 runtime,也没有 one-shot fallback。 每个 Session 的随机 Realm identity 持久保存;原始 Session id 只参与 keyed path/HMAC framing,不进入文件名。 同一 Session 恢复后仍映射到同一 Realm identity,但 live namespace 只存在于当前进程的 Worker generation;host 重启后得到空 namespace。fork child 有自己的 Session 和 Realm,不继承父 Realm heap。 ## Persistent Realm ### live namespace Worker 使用长期 V8 上下文执行连续 cell。顶层声明在当前 generation 内持续存在;工具 binding global 的 Proxy 身份也跨 cell 稳定,因此已保存的函数可以在后续 cell 使用当前轮次重新租用的工具实现。 cell 以严格模式和 V8 REPL 语义执行,支持顶层 `const`、`let`、`var`、function、class、destructuring、闭包与 top-level `await`。同类声明按 REPL 语义重声明。`tools` 等注入 global 是保留名;第一次合法 Prime run 冻结 Realm 的注入 global schema,后续 run 可以更新已有 namespace 的成员,但不能增加 namespace 或改变其错误类描述符。 同一 Realm 的 cell 严格串行。不同 Realm 在 pool 配额内并行。排队 cell 的取消只移除自身,不影响正在执行的 cell。 DSH compaction 不遍历、序列化或清理 Realm heap,spill 也不会驱逐用户保留的 binding。live namespace 因此只应作为紧凑工作集:大源数据和结果放在任务文件或现有 spill artifact,Realm 长期保留路径、索引、函数和摘要。当前没有隐式的 binding 级 GC。 Prime agent-plane 组合不挂载 Plan Mode。它用 `dsh-prime-agent/context-manager` 替换默认 LLM 摘要 backend,并移除该 scope 的 tool-result pruner;`toolResultPruner` 仍隔离,防止误取 Host 实例。`HistoryWindowEngine` 只覆盖 `BasicCompactionEngine.summarize()` 这个公开 hook,返回有界历史目录,不发起 LLM 请求。DSH 原有的压力计量、配对边界、compaction 锁、取消、原子 surface 提交、持久化检查与 overflow 重试完整保留;`/compact` 指向同一 service。非 Prime preset 不变。 ### 可回取历史与任务笔记 `src/context/history.ts` 使用 owning `Agent.session` 的 `eventAt` 读取原始 append-origin 消息与 `tool/ptc-dispatch`,不从已缩减 surface 反推历史,也不扫描其他 Session 或直接打开 `~/.dsh` 日志文件。已授权的 fork 历史属于 child 的 Session 前缀,可由 child 回查;其他会话不可见。返回保留 role/source kind 的内容 JSON 投影,排除私有 reasoning 和 provider replay state;请求头、授权记录等非对话事件不可读。嵌套日志已 spill 时只回原有 locator,图片保持附件引用,不复制外部 artifact 或保证其永久存活。 `tools.history_search({ query?, before?, limit? })` 使用大小写不敏感的字面量查询,默认从最新向前扫描,单页最多扫描 1000 个事件、返回 10 条(上限 20),每条 preview 最多 240 字符。`nextBefore` 是下一页的排他事件偏移;空页不等于耗尽,只有 null 才表示到达日志起点。`tools.history_read({ seq, offset?, limit? })` 返回精确 JSON 字符切片,默认及上限 8000 字符;`nextOffset` 非 null 时继续读取,拼完后才解析。字符单位是 JavaScript UTF-16 字符串索引,seq 来自同 Session 搜索或目录。 窗口目录只包含当前日志的可搜索偏移范围、最近至多八条人类消息的地址与有界引用片段,以及回取动作;不是旧消息的语义摘要。所有原始记录仍留在 DSH 日志中,重复替换不会覆盖它们。目录仍使用 DSH 标准 checkpoint 外壳和 `compaction/*` 事件:`provider/model` 标明 `dsh-prime-agent/history-directory`,没有 `llmStreamCall` 或捏造的 provider usage。可移出片段不足以容纳目录与预留外壳空间时拒绝替换,保留原窗口;DSH 仍执行最终的实际缩减检查。目录与笔记不提高材料信任等级。 部分 TUI 组合会在挂载 Agent preset 的同时保留 base Host compactor;Host listener 注册更早时会先执行旧摘要,甚至在失败后才轮到 Prime。context-manager 因而在同一个 Agent-scope `ctx` 上额外注册 `prepend` pressure pass,并捕获刚挂载的隔离 `HistoryWindowEngine`:Prime 成功替换后,后续 Host 与引擎自带的 pressure listener 重新计量并跳过;Prime 失败时仍调用 `next()`,保留继承链作为可用性回退。作用域过滤使该顺序只覆盖 Prime Agent,非 Prime Session 继续使用 Host 行为。引擎自带的 `auto` 保持开启,因此官方 provider-overflow recovery 未被复制或关闭;本次顺序修复只覆盖 step-boundary pressure。 `tools.notes_read()` / `tools.notes_write({ revision, content })` 使用 `src/context/notes.ts` 的单份 Session 任务笔记,6000 字符硬上限。它是可替换的当前恢复快照,不是追加式 chronology;原始顺序仍由 Session history 保存。空内容用于清空;写入要求读到的 revision,以 DSH `withFileLock` 串行跨进程修改、锁内复查 revision,再由 `writeFileAtomic` 发布。Host 同时记录调用开始时的排他 Session offset 为 `updatedAtSessionOffset`;模型不能提交该字段,它只表示该 revision 的写入位置而不背书内容,也不与缓冲发布的 Session 日志形成事务;崩溃恢复后该 offset 可能暂时超过已恢复日志。旧文件缺少字段时读作 null,并在下一次 CAS 写入升级。Session id 哈希构造文件名,路径不接收模型输入;文件位于配置 `stateDirectory/context-notes`,默认 preset 与 Prime runtime 的 stateDirectory 表达式一致。笔记独立于 Realm、continual store 和工作区文件;child 默认空笔记,不继承 parent 的独立文件。取消发生在原子发布期间时仍可能已经写入,调用方须先回读;底层原子写不承诺掉电 fsync 耐久性。 `tools.new_context({})` 只登记本进程内、按 Session 隔离的待处理请求,返回 queued;下一 `agent/pre-step` 在整个 cell 及其工具结果结算后处理。若 DSH 自动处理已替换窗口,就不再重复;否则调用公开 `compactIfNeeded(..., 'context-overflow', ...)` 使用 DSH 最小安全尾部选择。没有可缩减范围或范围太小时保留当前窗口并提供当次通知,其他错误保持显式失败。排队请求不跨进程恢复,已保存的笔记可恢复;它不结束 turn、不创建新 Session、不销毁 Realm。 context-manager 配置:`stateDirectory` 必填;`thresholdRatio` 默认 0.8、`retainTokens` 默认 16000,`modelPolicies` 复用 DSH 精确路由策略的校验。`checkpointReminderTokens` 未配置时关闭,配置值至少为 512;启用后,Prime-first pressure pass 先运行,继承的 Basic/Host listener 链结算后再根据最终 surface、最近已解析的 routed request context、精确 model policy、模型容量和本 step 待进入消息计算 threshold headroom。只有提醒消息本身仍装得进该 headroom 时,才把它作为最后一条 admitted message 返回,确保它跟在触发本 step 的用户/动态上下文之后进入下一次模型请求,而不会在同一 pre-step 被压缩掉。专用 plugin source 与当前 model-visible surface 上的 Session 自有事件共同恢复“每工作窗口至多一次”状态;局部 replacement 未移除旧提醒时不会重复注入,fork 也不继承 parent 的抑制状态。提醒不延迟或绕过 DSH pressure compaction,突发大输出直接跨过阈值时仍按原流程切窗。Prime preset 配置 16384 作为保守的阈值前 lead(不是阈值后的 fallback buffer),且为 `deepseek-official/deepseek-v4-flash` 配置 thresholdRatio 0.3;正常压力下 recent-tail 预算由配对边界修正,主动切换和真正溢出按 DSH 的最小安全尾部策略处理。没有关闭自动生命周期后留着无人处理的溢出,也不额外调用学习或摘要模型。 固定 policy 把变量复用限制为输入与来源仍有效;编辑、构建、外部写入或用户纠正后,刷新受影响证据并重新判断派生索引和计划。重要进展与用户纠正时更新现有任务笔记,主动切窗前保持笔记当前;自动 checkpoint 后若笔记为空或过时,使用历史原文恢复并核对当前目标与约束。Host 不自动撰写任务笔记,目录也不替模型选择证据。新上下文工具的细节仅放在生成 SDK;policy 只保留跨工具的恢复与材料信任说明。入口示例以保留来源、关键例外和未决项为准,不把单调用或输出压缩比当作失败判据。 ### binding lease 每个 cell 开始时,Worker 安装本轮声明的 namespace members;结束前先撤销全部 lease,再结算结果。调用时再次校验 lease,因此: - 当前 cell 可以调用 DSH 工具,schema、权限、sandbox、日志、取消和结果保留仍由 host 执行。 - 旧 cell 保存的 Proxy 和 wrapper 可复用身份,但不能在没有当前 lease 时调用。 - 本轮已接受的 host 调用全部结算后,cell 才完成。 - arguments 与 resolution 必须是无损 JSON;函数和原生 handle 只能留在 Worker heap,不能穿过 host bridge。 - 模型 schema 中没有身份或引导工具;worker 仍按保留名 `prime_realm_identity` 过滤租约成员作为纵深防御,但该名称不再注册,也永不进入模型 prompt。 每个 cell 同时受 host-call 总量和并发量限制,超限调用被拒绝,不扩大权限。 ### 子模型调用 `agents.query` / `agents.queryMany` `agents` 命名空间在委派别名之外还带两个私有成员:`agents.query({ prompt, system?, maxTokens? })` 返回 `{ text, truncated }`,`agents.queryMany({ prompts, system?, maxTokens? })` 按输入顺序返回 `{ replies }`。它们与 `refine` 走同一条私有 binding 路径:不注册为 DSH tool、不进入 `tools.*`/`ToolArgsMap`、不产生 `tool/ptc-dispatch` 记录,但写进生成的 `agents` 声明及其 JSDoc(上游 Prime 明令不得发明未声明的 wrapper)。每次调用用 `ctx.llm.stream()` 对当前 Agent 的路由(最近一次请求 envelope,否则创建选项)发起无工具的一次性请求,`sessionId` 归属当前 Session,取消信号来自本次 cell;回复至 `maxTokens` 截断时返回 `truncated: true` 而不是失败。预算由插件 `llm` 配置约束:单条 prompt/system 上限 `maxPromptChars`(默认 200,000 字符)、单批 `maxBatchSize`(默认 20)、批内并发 `maxConcurrency`(默认 8)、`maxTokens` 默认且上限 4096;越界在调用模型前拒绝,批内单条失败带下标整批拒绝。这是 RLM 范式里「对 N 个 chunk 各做一次无状态子调用并把结果收进变量」的原语;与 `agents.spawn` 的分工由声明的 JSDoc 说明:需要工具或多步推理的子任务用 `spawn`,对已在变量里的文本做语义归约用 `query`。它不经过 approval(不是工具调用),计费归当前 Session;UI 当前看不到这些子调用,是已知缺口。bridge 只允许把这类额外成员加到 `agents` 上,且不得与委派别名同名。 `queryMany` 保持整批成功或失败的契约。每批持有独立取消控制器:首个失败停止队列领取,取消同批在途请求,等待所有已启动请求完成清理后才报告原始失败下标;父 cell 取消会转发并在收尾后报告取消。结束时移除父取消监听,批间不会相互取消,失败后可在同一 cell 捕获错误并发起修正后的调用。已完成项不作为部分结果返回。取消是协作式的,底层 adapter 若忽略 signal,收尾仍需等它自然结束,已产生的 provider 用量也不会撤销。 `maxConcurrency` 是单批上限,不是 cell 或 Session 的子模型并发总额;并行调用多个批次会叠加。Realm 的 host-call 配额计数的是 binding 调用,一次 `queryMany` 在内部展开的请求不能解释成逐条占用该配额。这里不新增费用预算或部分结果 API。评测通过公开 `llm/stream` 边界记录所有可观察请求的 usage,区分失败、取消和缺失计量;token 与缓存用量不等于实际账单,详见 [任务级评测与成本归因](eval-context-engineering.zh.md)。 ### Worker 销毁与注入全局数 这两个成员没有做成独立的 `llm` 全局,是因为实测第五个注入全局会让 Worker 销毁崩溃:在同一进程里创建两个 Realm、各跑一个 cell、然后一起 `dispose()`,Realm 注入 `tools`/`agents`/`jobs`/`refine` 四个全局时 15/15 次正常,再注入任意名字的第五个 Proxy 全局时约 3–5/5 次触发 V8 fatal `Invoke in DisallowJavascriptExecutionScope`(栈在 isolate 销毁与 inspector 之间),整个宿主进程随之退出;单个 Realm 无论几个全局都不崩,把 `terminate()` 串行化或把 host 端 `port.close()` 挪到 `terminate()` 之后都不改变结果(Node v24.18.0,Windows)。一个只含 inspector 会话与 Proxy 全局的最小 Worker 复现不出,说明触发条件还依赖 Realm Worker 的其它组成;根因未定。在弄清之前,新的私有能力一律挂到现有命名空间,不再增加注入全局。 ### 完成值、日志与大输出 Worker 通过 Inspector 取得 cell 的末尾表达式并执行一次有界分类:lossless JSON 与 Map、Set、函数、BigInt、循环对象、class instance 等 opaque 值分别按各自的单槽预算判定。日志与 Realm → Host 的 completion value 共同受 `maxOutputBytes` 硬上限约束。工具 binding 在 Host 侧把 canonical value 与可选官方 content 放入仅供 Worker 解包的内部结果;程序只得到 canonical value,Worker 以私有 WeakMap 关联对象 identity 与展示文本。 成功的非 `undefined` completion 自动成为 generation-local 的最新值,并通过 `$_` 保留原始 identity;下一次非 `undefined` completion 会替换它。`undefined` 不覆盖当前值,因此需要长期复用时应优先赋给命名变量,也可在下一次产生 completion 前执行 `let saved = $_`。JSON 准入使用 `maxCompletionRetainedBytes` 与 `maxCompletionRetainedNodes`,opaque 准入使用 `maxCompletionOpaqueBytes` 与 `maxCompletionOpaqueNodes`;两类预算独立判定单个最新值,不存在 entry count、total history、FIFO 或管理操作。新 completion 被预算拒绝时会清空旧单槽,且不得暗示被拒绝的新值可恢复。 `maxCompletionFullBytes`(默认 64 KiB)以内的完成值原样返回,超过后 Worker 产生固定 schema 的有界内部 envelope。Realm 验证 terminal nonce 与 envelope shape 后才附加 discriminated `ReplPresentation`;retained preview 与 opaque reference 都不携带 numeric handle,unretained preview 明确不可恢复。用户程序返回同形对象没有可信 metadata,仍按普通 JSON 显示。降级链是 full → rich projection → minimal reference → output-limit,只有连最小 envelope 都放不进剩余预算时才真正失败。 外层 `repl` canonical value 保持结构化:`logs`、可选 `result`、可选可信 `presentation` metadata 与可选 `contextTokens`/`contextWindow` 仍可由调用方程序化读取。模型 renderer 不再 `JSON.stringify({ logs, result })`:logs 和 scalar string 原样显示,full structured value 只 pretty-print 一次;retained preview 与 opaque reference 提示值仍在 `$_`,要求在下一个产生 completion 的 cell 前赋给命名变量,retained preview 还要求从该变量继续切片、过滤或计数而不再整体显示;unretained preview 只说明重新计算或从持久文件载入。renderer 不调用用户 hook,无 logs 且无 completion 时返回空文本,也不添加普通结果类型标题或 Markdown fence。preview 是观察文本而非可解析数据,后续计算必须回到命名变量或 `$_`。 当 host `tokenMeter` 与路由模型宣告的 contextWindow 可用时,`repl` 在 cell 开始前测量当前 Session 的上下文用量,并在结果末尾追加一行 `Context: <已用> / <窗口> tokens`(窗口未知时只有已用数)。任何一步不可用或抛错都只是省略这一行,永不让 cell 失败;这一行纯追加,避免因改写旧消息而使前缀缓存失效;不代表新增输入、缓存容量或账单成本为零。它对应研究文档所说的 continuous visibility:模型对自己烧了多少上下文不再是盲的。 工具调用的 canonical value、官方 content、日志与 spill locator 仍由 DSH 工具层管理。Prime binding 始终把 canonical value 返回给程序;非空官方 content 只与对象 identity 关联,并仅在该对象直接成为 completion 时替代其模型展示。提取字段、spread 或其他转换产生的新值继续走普通 completion 路径;primitive canonical value 保持原值。Prime preset 为模型可见的工具结果配置 12KB best-effort spill 阈值,超过预算时模型先继续使用已赋值的 canonical 变量;只有确需遗漏的格式化文本时才 read/grep notice 中的 locator,并在 TypeScript 字符串中规范化 Windows 反斜杠。Spill backend 不可用时保留 inline 成功结果并告警。 控制面 policy 只在任一失败会让全部成功结果都失去用途时建议 `Promise.all`;相互独立的读取、搜索和探测即使希望拿到全部答案,也使用 `Promise.allSettled` 或逐项捕获 `ToolCallError`,保留成功结果、检查失败并只重试失败项。这些是模型侧编排约定,不改变 DSH 工具失败或 Realm partial-commit 语义。 ### 失败与换代 | 情形 | 当前结果 | | --- | --- | | 语法错误 | cell 不执行,namespace 不变;从 Node parser stack 只提取映射回模型程序的 cell 行列并保留 parser message,指向合成 suffix 的位置折回 cell EOF,不回显源码片段。 | | 普通程序异常或被程序捕获的工具失败 | 当前 cell 失败或由程序处理;Worker generation 保留。未捕获异常只投影简洁 message,不暴露 Worker/V8 内部调用栈。异常前已经完成的声明、赋值和外部副作用可能保留,遵循 REPL partial-commit 语义。 | | completion 序列化失败 | cell 已执行且 namespace 保留,当前结果以 invalid output 失败;判定范围以有界走查为界,超出捕获天花板的部分不校验。 | | completion 过大 | cell 成功;外层 canonical value 保留有界内部 envelope 与可信 presentation metadata,模型只见 notebook preview。原值按对应单槽预算保留;未通过准入时不暗示新值可从 `$_` 恢复。 | | 排队 cell 在 dispatch 前取消 | 只取消该 cell。 | | active abort、compute/wall timeout、输出失控、Worker exit、OOM 或控制协议违规 | hard-kill 当前 Worker;后续 cell 创建新 generation。 | | hard kill 后第一次真正 dispatch | 返回 namespace restart notice,明确上一 generation 的 bindings 与保留结果已丢失。 | | active Realm 数达到上限 | 先回收最久未使用的 idle Realm;没有 idle 候选时拒绝新 Realm admission。 | | runtime dispose | 停止 admission,取消队列,终止并等待全部 Worker 与 host 调用。 | idle Worker 不阻止 host 退出,并在 `maxIdleMs` 后回收。一次 hard kill 只计一次 generation 变化;尚未真正进入 Worker 的调用不会错误消费 namespace-loss notice。 ## Agent 编排与文件交接 Prime preset 复用 DSH 的 continuable Subagent 架构: - `subagent` 与 `subagent_fork` 配置为 `backgroundMode: continuable`。省略 `run_in_background` 或传 `true` 时,在 child inbox 接受初始消息后立即返回持久 child id,不等待该轮完成。 - `list_agents` 列出可继续的直接 child 或后代;`ready` 表示只存在于持久存储、可恢复,不表示有结果待收集。 - `send_message` 给直接 child 排入一个后续 FIFO turn;child 不驻留时由 DSH 从 Session persistence 冷恢复。 - `interrupt_agent` 只中断目标当前 turn,保留排队消息、child 身份和已发布后代。 - child 的 `report` 只投递给直接 parent。官方 `next-step` 调度让运行中的 parent 在最近 step 消费,并唤醒空闲 parent;report 不结束 child turn。 - DSH 自有的 settled notice 独立于 child 是否主动 report,负责说明一次 Activation 如何结束。 continuable child 没有 Job result promise:其详细过程保存在 child Session,选定结论通过 report 返回。当前没有 child delete,也没有把 child transcript 作为单个结果 collect 的工具。 Jobs 是另一条生命周期:后台 shell 和使用 one-shot background provider 的工作返回 Job id,并由 `job_output`、`job_list`、`job_kill` 管理。Job 与 continuable child id 不可互换。 慢任务和独立任务采用非阻塞控制循环:先由 managed Job 或 continuable child 接受工作,保存 id 或输出位置,再继续不依赖结果的父任务;没有独立工作时结束当前 turn,等待 report、settlement notice 或后续调度。policy 禁止用 `sleep`/`setTimeout` 轮询或长阻塞 `await` 占住 cell/turn。直接面向用户的 root 在多回合、计划型或多 child 工作中按有意义里程碑汇报结果、阻塞和下一步;subagent child 不注入这条用户进度规则,仍通过 `report` 面向 parent。 未显式覆写时,child 继承 parent Session 的工作目录,并基于 parent preset 组合自己的 Agent scope;child 级 allow/deny 仍可进一步收窄工具。跨 Agent 的大材料按 policy 走 handoff file: 1. 父把材料序列化到一个工作区文件,spawn prompt 只携带任务、路径以及每个 key 的摘要和规模。 2. child 按需读取、在程序内归约,把完整结果写入另一个文件。 3. child report 只携带可执行结论和结果路径。 4. 没有 child 再需要时删除交接文件。 handoff file 是写入时刻的快照;“写后不改”由 policy 约束,不由 runtime 强制。共享工作区没有 Capsule 级访问控制,文件路径也不是授权凭证。 ## Continual Harness `refine` 是控制面之后的次级学习层。Prime scope 向 DSH Skill Registry 注册随包、model-invocable/user-non-invocable 的 `refine` provider,并增加 scoped filesystem provider;Host 唯一的 `dsh-tool-skill` 注册负责在首个模型请求前发布合并目录并按需加载 `skills/refine/SKILL.md`。Prime preset 不注册第二个同名 tool-skill,避免 scoped tool shadow 令 Host 的 visibility-matched pre-step hook 跳过目录。Skill 说明 Realm 预加载的 leased `refine.status()` / `refine.run(instructions?, options?)` 客户端。该 namespace 经 Realm 私有 Host binding 调度,不注册 DSH tool、不进入 `tools.*` 或生成 SDK,也不写入 `tool/ptc-dispatch`。模型不会获得 store 的 inspect/apply/rollback、revision、transaction 或 edit schema。 `run` 将请求按 Agent 保存在内存中并立即返回;同一 turn 的后续调用覆盖待处理请求。`agent/turn-stopping` serial listener 取出请求,运行同一个无工具 refinement planner;成功、no-op 或失败结果都通过 `agent.steer()` 送回当前 Agent,使下一步使用重建后的 prompt。一次 refinement 恢复后的同 turn 重复调度被拒绝,Agent 进入 idle 后重新开放。插件卸载或 Session 消失时,WeakMap/WeakSet 不产生独立持久任务。 人类侧 `/refine [--local|--global] [instructions]` 仍通过 DSH `ctx.commands.register` 注册。命令注册表在 handler 开始前记录 `command/run`,settle 后以同一 `commandId` 记录 `command/done`,让 Web 与 TUI 的通用命令投影在聊天列表中先显示运行节点、再原地更新结果。handler 使用 `Agent.runMaintenance` 与普通 turn 串行,按 `session.requestHeader()?.config`、再按 `agent.options` 复用 provider/model;它只向 `ctx.llm.stream()` 发送有界、图片已投影的文本会话尾部、当前 scope 摘要和可选指令,不开放工具。输出必须是 JSON proposal;截断、取消、非 stop、JSON/shape 错误或 revision 冲突全部 fail closed。空 edits 不落盘,非空 edits 走同一个 `HarnessStore.apply`。`/refine rollback [--global]` 直接复用 `HarnessStore.rollback`。 Host 内部 store 继续以显式 inspect/apply/rollback、`expected_revision`、磁盘锁内 revision 复查和 conflict-safe rollback 实现。每笔事务保存 before/after,历史有界保留;skill/subagent entry 必须引用调用时真实可见的工具,不能把 `repl` transport 当作 SDK member。local state 以 Session id 的 SHA-256 摘要命名。`allowGlobalRefinement` 默认为 false,此时所有显式 global refinement 都拒绝;启用后使用单一 `global.json`。模型 Skill 与人类 command 都没有效果观察或 global 人工批准流程;turn interval/compaction auto-refine 仍未启用。 进入 prompt 的 entry 是有界、JSON 引用的非可信建议。它不能替代基础 system prompt,也不能覆盖当前 user 指令、权限、sandbox 或工具约束。任务数据、研究材料、执行进度和大上下文不属于 Continual Harness。 ## 状态布局 ```text / ├─ realm-identity/ │ ├─ hmac.key │ ├─ sessions/.json │ └─ leases/.lease └─ continual/ ├─ global.json └─ sessions/.json ``` Realm heap、child Session、Job 和工作区 handoff files 不存放在上述插件私有目录:前者是 Worker 内存,child/Job 由 DSH 持有,handoff files 属于任务工作区。 identity 与 continual 文件使用跨进程锁和原子替换;损坏、超限和 revision 冲突明确失败。多个 host runtime 可以共享一个 `stateDirectory`。身份解析成功后,runtime 在创建 live heap 前惰性取得该 Realm 的进程 lease:不同 Session/Realm 可由不同 TUI 同时运行;同一 Realm 已被其他存活进程持有时明确拒绝,不降级为 one-shot。idle/LRU 回收会先完整终止 Worker,再释放该 Realm 的 lease;旧 Worker 完全终止前,其他进程不能创建同一 Realm 的第二份 heap。记录的 owner pid 被明确证明不存在时,stale lease 可由下一 host 回收;PID 已复用或 liveness 不可判定时保守拒绝,避免生成第二份 heap。崩溃若遗留 `*.lease.lock`,操作者须确认对应 Session 无存活 host 后再清理。 Host runtime 在模块加载时冻结启动宿主的直接父 pid,并立即开始监控。macOS/POSIX 上父进程退出后发生的 reparent,以及 Windows 上父 shell 被强制终止但子进程继续存活,都会触发同一条根级清理路径:停止监控,dispose 整个 Cordis tree,等待 Realm Worker 终止,再释放该 host 持有的 Realm leases,随后以 0 退出;根级 dispose 未在 5 秒内结算则以非零状态强制退出。父 pid 为 init 或当前进程时不安装监控;探测结果不能证明父进程消失时保持运行,避免误杀合法宿主。 ## 配置界面 Agent-scope `dsh-prime-agent`: | 配置 | 默认值 | 作用 | | --- | --- | --- | | `stateDirectory` | 必填 | Realm identity 与 continual state 根目录。 | | `allowGlobalRefinement` | `false` | 是否允许 global harness 读取和写入。 | | `refinementMaxTokens` | `4096` | `/refine` 辅助模型请求的最大输出 token。 | | `refinementMaxConversationChars` | `80000` | `/refine` 会话尾部文本预算。 | | `requireOrchestrationTools` | `true` | 是否在 prompt assembly 时要求 Agent catalog 具备 Subagent admission(`subagent`/`subagent_fork`)与 `agents`/`jobs` 控制(`list_agents`、`send_message`、`interrupt_agent`、`job_output`、`job_list`、`job_kill`)。 | | `visibleOutputBudgetBytes` | `12000` | 固定提示告知模型的每 cell 可见输出预算;须与 preset `prime-spill-policy.maxInlineBytes` 一致,因为实际截断由 spill policy 执行。 | | `continual` | 有界默认值 | entry、evidence、transaction、状态文件和 prompt 预算。 | | `llm` | `maxPromptChars` 200000、`maxBatchSize` 20、`maxConcurrency` 8、`maxTokens` 4096 | cell 内私有 `agents.query`/`agents.queryMany` 子模型调用的预算;越界在调用模型前拒绝。 | Host-scope `dsh-prime-agent/runtime` 对显式配置的 `computeMs`、`maxWallMs`、`maxOutputBytes`、`maxOldGenerationSizeMb` 逐字透传;未配置时 `maxOldGenerationSizeMb` 使用 Prime 默认 64 MiB。Realm pool 治理项为 `maxActiveRealms`(默认 32)、`maxIdleMs`(默认 600000)、`maxHostCallsPerRun`(默认 200)、`maxParallelHostCallsPerRun`(默认 16)。Completion 保留与投影的六个上限为 `maxCompletionRetainedBytes`(默认 8 MiB)、`maxCompletionRetainedNodes`(默认 1,000,000)、`maxCompletionOpaqueBytes`(默认 8 MiB)、`maxCompletionOpaqueNodes`(默认 262,144)、`maxCompletionFullBytes`(默认 64 KiB)、`maxCompletionProjectionBytes`(默认 4 KiB)。前四项分别限制单个最新 JSON/opaque 值的准入,不是 entry-count 或累计 history 配额。 runtime row 与 Prime preset 的 `stateDirectory` 必须相同,否则身份记录与 lease 目录不一致,所有 Prime 请求都会 fail closed。 ## 当前契约边界 - Worker 隔离不是安全 sandbox;真正的文件和命令权限由 DSH host 工具与 sandbox policy 决定。 - live namespace 不跨 Worker generation 或 host restart 持久化,也不自动重放历史 cell。 - 不支持未受 DSH 管理、跨 cell 持续调用工具的 detached async work。 - Host runtime 属于启动它的直接父进程;不支持有意脱离该父进程作为 daemon 继续运行。 - cell 内原生 dynamic `import()` 没有 import callback,会明确失败;外部能力通过 DSH tools 使用。 - 不提供 IPython/Jupyter backend、在线 Realm reset、Context Capsule store、`share`/`mount` 或父子文件授权。 - 不提供 continuable child delete 或 transcript collect。 - 不提供 per-spawn child reasoning-level 参数;该能力需要 DSH Subagent 原生拥有继承、resolved-model 校验、持久化与冷恢复。 - 本插件不拥有 spill artifact 的配额、保留期或清理;当前 local backend 可随超限结果持续增长,生命周期治理必须由 DSH spill store/部署层提供。 - Continual Harness 不自动生成 proposal,不包含 review/apply 分离流程,也不自动建议或执行 rollback。 - 插件不创建 Agent registry、消息总线、Session store、Job registry、Goal driver、Workflow engine 或 Compaction 实现。 ## 验证面 仓库测试覆盖:Realm identity 稳定解析与持久化、多个 host 共享状态、同 Realm 跨进程互斥与接管、Session 隔离、跨 cell binding 连续性、调用顺序、binding lease、host-call 预算、超时/abort/Worker 换代、namespace-loss notice、completion 单槽预算、输出上限与 Unicode、工具失败恢复、approval escalation、Subagent Job 编排、官方 report 组合边界、preset 落位和 bundle patch 结构。 Prime REPL 的固定提示、completion metadata 与 notebook renderer 契约见 [Prime REPL Notebook 呈现规格](repl-notebook-presentation.zh.md);上游行为映射与同步流程见 [Prime Agent 学习笔记](prime-agent-learnings.md) 和 [上游同步与差异对照手册](upstream-sync.zh.md)。 当前 DSH 依赖基线为 `0.1.6-alpha.2`。官方 one-shot 运行时使用 `ctx.ptcRuntime`;Prime 仍使用独立 Realm 服务。DSH summarizer 输入额外前置保留的 system head,Prime 计算可移除内容时排除该项,避免将小窗口误判为值得缩减。