Repository navigation
Conversation
A search hit, a command's output or a directory search shows file content without recording an observation, so the host read-before-edit table still refuses an `edit` on that file with "file has not been read". The model learned this by having a whole batch rejected: one error per file it never read. In one benchmark run of gate-repair-loop a single `grep` stood in for 24 reads and the following 23 edits were each refused; the same signature reproduces on both arms (21 errors after `grep`, 23-24 after a `pwsh` bulk dump), so it is an interaction between batch discovery and the host gate rather than a defect in either side alone. The line names only the readers that actually clear the gate, and is emitted whenever one of them is visible — deliberately not only beside ops_grep and ops_glob. Those two are discoverable, so a session starts with `read_files` alone while the host grep stays callable, which is exactly the shape that produced the rejected batch; gating the line on a visible ops search tool silences it in the one session that needs it. A narrow range is enough: one `read_files` call covers a whole batch, against one rejection per file that skips it. `ops_grep` and `ops_glob` keep recording nothing. A grep returns excerpts, and reporting them as a read would claim a partial scan as a complete one, which this repository forbids. The shell section names no reader, so it carries no remedy and gets no line.
Richeir
force-pushed
the
fix/policy-search-not-a-read
branch
from
October 21, 2026 16:49
7e83dcd to
adca4a2
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PR 的内容还在测试中,暂时先不合并;等测试稳定之后,再去掉 Draft;
Closes #360
What was wrong
模型用搜索通道拿到文件内容后就动手改,但宿主的 read-before-edit 表只认读取类工具登记的观测,于是每个从未读过的文件各产生一条拒绝:
最小复现(
gate-repair-loopfixture,ops 侧逐调):read gate_00.js→edit gate_00.js✅ → 一次grep拿到全部 24 个 stage 的契约与错值 →edit gate_01…23(23 个并行下发)全部被拒 →ops_read_files(23 个文件,offset:77,limit:7)→ 内容相同的 23 个edit全部成功。47 次edit里 23 次被拒,toolErrors = 23;内容一直是对的,缺的只是出处。这不是 ops 侧独有。base 侧用两次
pwsh Get-ChildItem gates\*.js | ForEach-Object {…}批量 dump 踩的是同一个门禁:grepgreppwsh批量 dumppwsh批量 dump四例的错误数都精确等于「未登记读取就编辑的文件数」(
full-sweep-16多出的 1 条是ReplaceFileW EIO (Win32 1175)的 Windows 瞬时失败,与出处无关)。所以这是批处理发现方式与宿主出处门禁的交互:base 的自然节奏「读 4 个 → 改 4 个」让账本始终同步,ops 工具让一次调用替代 24 次读取,分歧一次性放大到满量级。两侧单独看都不构成缺陷。根因是
docs/component-reference.md:52已写明的既定行为:ops_grep、ops_glob与dryRun不登记任何观测。完整证据(callId 级归因、原始 JSON、ops_read_files返回中 23 个[revision=…]标记)见 #360 的证据评论。The change
src/policy.ts:工具指引新增一行说明,只要有一个会登记观测的读取工具可见就输出,并动态点名真正可见的读取工具。会话开局(只有常驻的ops_read_files可见)渲染为:test/policy.test.mjs:断言覆盖条件矩阵与「不得点名未发布的读取工具」,并复用既有约束确保文件区段不点名 shell 工具。其中**「只有ops_read_files可见时也必须输出」是关键回归断言**,理由见下。docs/CHANGELOG.md(## [Unreleased]双语条目)、docs/troubleshooting.md、docs/常见问题.md:把边界与代价写进排障文档。为什么条件不能挂在「搜索工具也可见」上
初版把条件写成「搜索工具(
grep/glob)与读取工具同时可见」,这是错的,而且错得很隐蔽:visibleTools()取的是ctx.tools.schemas(agent),即此刻真正对模型可见的工具。原生文件面只用常驻集合启动(src/index.ts:88传入NATIVE_RESIDENT_FILE_TOOLS),而注册表里只有read_files是resident、ops_grep/ops_glob/ops_inspect_local_file都是discoverable(src/registry.ts:19,且 discovery 默认alwaysLoaded: [])。于是在 gate-repair-loop 那种会话里(模型从未调用
ops_discover_tools),开局可见集合只有ops_read_files,条件恒为 false——这行根本不会渲染,修复会在它唯一要修的场景里失效。宿主grep反而是可见的,正是制造 23 条拒绝的那条通道。现在的条件只依赖读取工具,第 4 节的断言就是为这个坑设的(把旧条件加回去,该断言立刻变红,见 Validation)。
刻意没做:不给
ops_grep/ops_glob补发fs/observed。grep 返回的是片段,补发等于把局部扫描声称为完整读取,与本仓硬规则冲突,所以这条路明确排除。补救取向是「读窄范围」——一次ops_read_files调用即可覆盖整批,对照的是每个文件一条拒绝。刻意保留的边界:只挂 shell 组件、没有文件工具时该行不输出——那里点不出任何读取工具,给不出可执行的补救。若维护者希望 shell 侧也覆盖(改为提示用宿主
read),建议另开一条。依赖、
package.json版本与optionalDependencies均未改动。Validation
面向模型的文本变化:会话开局形态下该行 216 字节;两个读取工具都可见时点名两者,为 242 字节。条件矩阵实测:
4 个跳过均为环境前提、非失败:
linux-attributes、linux-shell(POSIX-only 概念),tui-host、tui-native(需要DSH_OPS_TUI_ROOT)。docs/CHANGELOG.md的## [Unreleased]已加双语条目troubleshooting.md+常见问题.md)package.json版本与optionalDependenciespull_request事件触发的CI requiredrun URL:Environment
T-Auto/dsh-opsmainb963fb2(分支已变基到它之上,与#306的fix(policy)改动无冲突:本 PR 只增行,未动它修改的 heading 与 prefer-bash 行)npm run build后由 bench profile 链接)bench-ops/bench-base、file policydanger-full-accessNODE_ENV=production会让settings-ui套件 8/8 失败(production 版 React 没有可用的act)。该现象在干净 main 上同样复现,清掉NODE_ENV后 8/8 通过。