The self-improvement review fork advertises the parent's full tool schema
(deliberate — tools[] must stay byte-identical for prompt-cache parity)
but denied everything except memory/skill tools at dispatch. Models
naturally reach for read_file to inspect a SKILL.md before patching, got
denied, then attempted a blind skill_manage patch which the
read-before-write guard correctly refused. One deployment logged ~142
denials + ~204 refusals over 2 days: the self-improvement loop ran
continuously but almost never landed a skill patch.
Fix is dispatch-side ONLY — zero request-body change, cache untouched:
- Whitelist read_file + search_files on the review fork (reads are
side-effect-free). Write tools (write_file/patch/terminal) stay denied:
autonomous maintenance must go through skill_manage's validation.
- read_file now registers full reads with the review fork's
read-before-write guard (same as skill_view), so the natural
read_file -> skill_manage(patch) sequence lands. Partial reads
(offset>1 / truncated) don't count. No-op outside review forks.
- Self-correcting deny message: names skill_view/skill_manage/memory as
substitutes so one denial redirects the model instead of a storm
(the actionable half of #61521's proposal 2).
Rejects #39997's alternative (narrow the advertised schema on local
endpoints): local backends have KV/prefix caches too, and re-prefilling
a large snapshot is most expensive exactly there.
Live A/B (real dispatch path, isolated HERMES_HOME): on main,
read_file DENIED -> patch REFUSED (read-before-write); on this branch,
read_file OK -> patch LANDED. tools[] identical in both.