From e43dc9da7e54bc1b126a7620a7051255eac3747c Mon Sep 17 00:00:00 2001 From: Edbert Chan Date: Fri, 25 Sep 2026 07:52:40 +0800 Subject: [PATCH 1/2] reflect: a yes covers only named actions; silent swaps get retracted; liveness answers report default-branch CI cat-mode autonomy: a newly found destructive or production-mutating step gets its evidence in the same message and one short confirmation, unless it undoes this session's own side effect. Worker-liveness answers also report the target repo's default-branch CI state and last green run date. principle-flag-your-own-corrections: a claim contradicted by later evidence is retracted in words even when the reply only moves on to the new fact. Co-Authored-By: Claude Opus 5.5 (1M context) Change-Id: Ie3a071e0166915178368f94d25bd605ced82fb85 --- corpus/skills/cat-mode/SKILL.md | 2 ++ corpus/skills/cat-mode/references/autonomy.md | 19 +++++++++++++++++ .../SKILL.md | 1 + .../tests/fires_silent_swap.md | 20 ++++++++++++++++++ tests/test_cat_mode.py | 21 +++++++++++++++++++ 5 files changed, 63 insertions(+) create mode 100644 corpus/skills/principle-flag-your-own-corrections/tests/fires_silent_swap.md diff --git a/corpus/skills/cat-mode/SKILL.md b/corpus/skills/cat-mode/SKILL.md index 11b57ffba..a2e69c864 100644 --- a/corpus/skills/cat-mode/SKILL.md +++ b/corpus/skills/cat-mode/SKILL.md @@ -69,6 +69,8 @@ Once direction is set, act — don't ask permission for each sub-step. One fully - **Do not kill/restart a live Invoker `owner-serve` as the default lever.** - **Ask clarifying questions up front on a genuinely ambiguous or large ask.** - **Answering the opening question is a stopping point.** +- **A "yes" authorizes the actions it named, not the ones found afterwards.** +- **A health question covers what the thing serves, not only whether it runs.** Each rule's full text: [references/autonomy.md](references/autonomy.md). diff --git a/corpus/skills/cat-mode/references/autonomy.md b/corpus/skills/cat-mode/references/autonomy.md index 5d8c87ff1..f36fc3ea6 100644 --- a/corpus/skills/cat-mode/references/autonomy.md +++ b/corpus/skills/cat-mode/references/autonomy.md @@ -21,6 +21,25 @@ autonomy defaults. and ask whether to continue before launching further work. Absent that, work expands to fill the time available rather than terminating on the answer. +- **A "yes" authorizes the actions it named, not the ones found afterwards.** + When work turns up a destructive or production-mutating step the approval + did not name (cancelling a live workflow, killing a process, deleting a + branch), put the evidence that makes the step right in the same message, + then ask one short confirmation before acting. Undoing a side effect this + session itself created is the exception: state the evidence and proceed. + This is least privilege applied to consent: an approval grants what was + asked for and nothing wider — Jerome H. Saltzer and Michael D. Schroeder, + "The Protection of Information in Computer Systems", 1975, + https://web.mit.edu/Saltzer/www/publications/protection/Basic.html. +- **A health question covers what the thing serves, not only whether it runs.** + For example, when the user asks whether workers, babysitters, or loops are + running, report the target repo's default-branch CI state and the date of its last + green run in the same answer, without being asked. A worker can be healthy + while the branch it feeds stays red, and a liveness answer alone leaves the + user to find that out by pasting a CI link. Keeping the mainline green and + its state visible to everyone is continuous integration's own rule — + Martin Fowler, "Continuous Integration", 2006, + https://martinfowler.com/articles/continuousIntegration.html. - **An auto-merge label is a live trigger, not an annotation.** A label such as `admin-bypass` is wired to a merge automation, so applying it to a PR whose branch is still being worked on lands that work half-finished the instant CI diff --git a/corpus/skills/principle-flag-your-own-corrections/SKILL.md b/corpus/skills/principle-flag-your-own-corrections/SKILL.md index ff4ad94f9..8fb85dd5d 100644 --- a/corpus/skills/principle-flag-your-own-corrections/SKILL.md +++ b/corpus/skills/principle-flag-your-own-corrections/SKILL.md @@ -14,6 +14,7 @@ When something you already told the user turns out to be wrong, say so explicitl - Do this even when the fix that caused the correction wasn't a careless mistake — a bug you found and fixed in your own tooling still means a number you already reported is now known to be wrong. - Do it in the next message where the number matters, not buried three turns later or only in a commit message. - Applies equally to numbers, claims of "done," and root-cause explanations — anything stated as fact that a later turn contradicts. +- A silent swap is a correction too. When later evidence contradicts a claim you made and you simply move on to the new fact — you said "nothing is running," a later read shows three agents running, and you report the three agents — the old claim still stands in the user's head. Retract it in words ("earlier I said nothing was running; that was wrong") even though you never repeated the old claim and never wrote a word like "wrong." The trigger is the contradiction, not an admission. This is dependency-directed retraction: a belief whose support is gone is withdrawn explicitly, not left beside its replacement — Jon Doyle, "A Truth Maintenance System", *Artificial Intelligence* 12(3), 1979, https://doi.org/10.1016/0004-3702(79)90008-0. ## Admitting fault is itself the trigger diff --git a/corpus/skills/principle-flag-your-own-corrections/tests/fires_silent_swap.md b/corpus/skills/principle-flag-your-own-corrections/tests/fires_silent_swap.md new file mode 100644 index 000000000..8ce0028a7 --- /dev/null +++ b/corpus/skills/principle-flag-your-own-corrections/tests/fires_silent_swap.md @@ -0,0 +1,20 @@ +The user asks whether a remote worker host is handing out work. The agent +answers "Nothing is being handed out right now." Two turns later a process +listing on the same host shows three agents running, and the agent is about +to report the three agents and move on to reading their logs. + +This skill fires even though the reply it is drafting admits nothing. The +agent never repeats "nothing is being handed out" and never writes "wrong," +so the earlier claim is contradicted without being retracted: the user still +holds it, and a reply that only reports the three agents reads as a new fact +rather than a correction. + +Once loaded, the skill asks for the retraction in words before the new fact: +"Earlier I said nothing was being handed out; that was wrong, three agents +are running." It then asks what made the first answer unchecked, here a +reading taken before the host was queried directly. + +`engine/hooks/wrong-check-reflect` and the offline scanner +`engine/skills/reflect/scripts/self_retraction_scan.py` both look for an +admission inside the reply, so a reply with no admission gives them nothing +to match. The skill is the only layer that reaches this shape. diff --git a/tests/test_cat_mode.py b/tests/test_cat_mode.py index e13770598..834cc850a 100644 --- a/tests/test_cat_mode.py +++ b/tests/test_cat_mode.py @@ -1337,6 +1337,27 @@ def test_autonomy_reference_keeps_its_rules(self): self.assertIn("stale-lock reclaim lines are successor symptoms, not crash proof", text) self.assertIn("Answering the opening question is a stopping point", text) + def test_a_yes_covers_only_the_actions_it_named(self): + skill = normalized_skill_text() + text = normalized_reference_text("autonomy.md") + headline = 'A "yes" authorizes the actions it named, not the ones found afterwards.' + self.assertIn(headline, skill) + self.assertIn(headline, text) + self.assertIn("put the evidence that makes the step right in the same message", text) + self.assertIn("ask one short confirmation before acting", text) + self.assertIn("Undoing a side effect this session itself created is the exception", text) + self.assertIn("Saltzer", text) + + def test_worker_liveness_answer_reports_default_branch_ci(self): + skill = normalized_skill_text() + text = normalized_reference_text("autonomy.md") + headline = "A health question covers what the thing serves, not only whether it runs." + self.assertIn(headline, skill) + self.assertIn(headline, text) + self.assertIn("default-branch CI state and the date of its last green run", text) + self.assertIn("without being asked", text) + self.assertIn("Fowler", text) + def test_fix_the_tool_reference_keeps_its_rules(self): text = normalized_reference_text("fix-the-tool.md") self.assertIn("check whether an existing one already covers it and consolidate", text) From 28093a31f5fc6f866ddd181a737e3f57b1052b2e Mon Sep 17 00:00:00 2001 From: Edbert Chan Date: Fri, 25 Sep 2026 10:01:06 +0800 Subject: [PATCH 2/2] Keep cat-mode at its 330-line cap Join two wrapped one-sentence rules onto one line each (no wording change) to make room for this PR's two new autonomy rules. Co-Authored-By: Claude Opus 5.5 (1M context) Change-Id: I9fe596806646cb1cf6375147973b463f88bf0787 --- corpus/skills/cat-mode/SKILL.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/corpus/skills/cat-mode/SKILL.md b/corpus/skills/cat-mode/SKILL.md index a2e69c864..4d164b173 100644 --- a/corpus/skills/cat-mode/SKILL.md +++ b/corpus/skills/cat-mode/SKILL.md @@ -146,8 +146,7 @@ isolated subagents and report back async rather than blocking on each one. - **The user's named execution shape wins over Invoker-first**; say so in one line before launching. - **A fork/subagent told to touch files must run in its own worktree, not the live checkout** — "read-only" wording is not filesystem isolation. -- **A subagent's own report is not verification that it stayed in scope.** - Grep its transcript for writes/commits before trusting the summary. +- **A subagent's own report is not verification that it stayed in scope.** Grep its transcript for writes/commits before trusting the summary. - **Past about 8 agents, state concurrency and cost first**; after a usage-limit stop, resume the original task's agents first. Each rule's full text: [references/subagents.md](references/subagents.md). @@ -215,8 +214,7 @@ bug: invoke `automate-me`, do not wait. - **Admit what was not exercised** by enumerating against the done-gate: for each named layer, say whether the real path through it ran. - **Treat absolute negatives as categorical.** -- **A blocked target is a stop, not a licence to substitute.** A number - produced on a proxy carries the proxy's name beside the number. +- **A blocked target is a stop, not a licence to substitute.** A number produced on a proxy carries the proxy's name beside the number. - **Re-resolve a target's live identity immediately before mutating it; an earlier listing is not standing authorization.** What a name resolved to when it was enumerated can differ from what it resolves to