feat(instructions): a rule proposal has five answers, and three of them route (#3733, #3896)
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Failing after 10s
CI & Build / integration (push) Successful in 51s
CI & Build / TypeScript typecheck (push) Successful in 55s
CI & Build / Python tests (push) Successful in 1m31s
CI & Build / Build & push image (push) Successful in 24s

Step 6 of both milestone 385 (lessons) and 399 (preferences). #3896 asked for
the fourth and fifth answers in one pass, because two people each adding one
branch to a three-way distinction produce a list that does not read as a set.

create_rule now opens by asking what kind of thing is being held, with one
question that sorts it — what happens if someone doesn't do this? Something
breaks, a boundary is crossed: a rule. It gets done a way the operator didn't
want: a preference. They lose time rediscovering it: a lesson. The closing
question grew the two matching answers, and they are named as first-class
outcomes rather than places a proposal lands when it fails. An observation
that turns out to be a lesson has been routed, not dropped.

Stated as a practice, not a prohibition (rule 165). #3557's first cut opened
"NOT YOURS TO CALL UNPROMPTED" and cost the noticing; the wanted behaviour
here is still more proposals, and what changes is only which door they go
through.

create_note says the same from its side, so routing does not depend on having
opened create_rule first — and its existing rule test ("a mistake, not merely
uninformed") turned out to name the lesson exactly. create_lesson names the
fifth kind so the set is complete from every door. create_project_rule's
citation of the loop names five answers, since it cites rather than repeats.

The force axis has one owner (decision #4027): using-scribe states all three
strengths, the sorting question, that updating a preference mid-work is the
normal case, and that preferences shape how work is done and never what gets
recorded. _INSTRUCTIONS carries the pointer — "Rules bind; preferences guide
and you keep them current; lessons inform." It had 14 characters of headroom,
so the clause is paid for by trimming atmosphere from three other lines; 1998
of 2000 now.

Guards: the proposal-loop test learns the preference branch, the lesson
branch and the force question, on both rule surfaces; guidance-ownership gains
the force-axis topic (shared with the docstrings, for the moment a proposal is
actually written) and the preference-scope topic; a new guard pins that the
index names all three strengths and who keeps the middle one current, with its
can-fail case being the omission that actually happens — a kind added to the
product while the index still describes the corpus that came before it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01821k5B3Ysecp9fNYs92Kuy
This commit is contained in:
2026-09-19 12:12:14 -04:00
co-authored by Claude Opus 5
parent 1fcfd47ab6
commit 0ed8e86cd5
8 changed files with 171 additions and 28 deletions
+46 -14
View File
@@ -268,6 +268,23 @@ async def create_rule(
When the operator asks for a rule in so many words, that IS the yes —
write it and move on. The loop below is for the rule you thought of.
FIRST, ASK WHAT KIND OF THING YOU ARE HOLDING. Force is the axis, and one
question sorts it: *what happens if someone doesn't do this?*
* "something breaks, or a boundary is crossed" → a RULE. It must be
followed, so it is the operator's to agree to. Propose it here.
* "it gets done a way the operator didn't want" → a PREFERENCE
(create_preference). How they want work done; ignoring it costs
consistency rather than correctness. No approval loop — record it.
* "they lose time rediscovering it" → a LESSON (create_lesson). A better
way to think about a problem, or a solution that transfers, met again
at the moment it applies. It binds nobody, so it needs no yes.
Asking this raises the value of noticing rather than lowering it: the
observation is worth keeping in all three cases, and what changes is
only which door it goes through. A proposal that turns out to be a
lesson has not failed — it has been routed.
A PROPOSAL CARRIES FOUR THINGS, and the fourth is the one that decides it:
1. WHAT it would require — the statement, in the words it would carry,
@@ -289,8 +306,8 @@ async def create_rule(
shrinks only on purpose, so a question that prevents a rule is worth
more than any question that improves one's wording.
THEN CLOSE WITH A QUESTION THEY CAN ANSWER IN ONE WORD. Offer three
answers, and make the middle one the easy one:
THEN CLOSE WITH A QUESTION THEY CAN ANSWER IN ONE WORD. Offer these
answers, and make "LET'S TALK ABOUT IT" the easy one:
* "Approve it AS WRITTEN" — you create it with the statement exactly as
shown. This is what makes element 1 load-bearing: they approved TEXT,
@@ -298,14 +315,27 @@ async def create_rule(
* "LET'S TALK ABOUT IT" — the wording, the scope, whether it
wants to be a rule at all. Most good rules arrive this way, so treat
this answer as the expected one rather than a setback.
* "NO" — let it go. If the observation is still worth keeping, it is a
note (create_note): recorded, findable, and binding on nobody.
* "MAKE IT A PREFERENCE" — they want it done this way, but nothing
breaks if it isn't. create_preference records it without a loop, and
it stays yours to keep current as they correct you.
* "MAKE IT A LESSON" — worth knowing, binding nobody. create_lesson
stores it against the SITUATION it applies to, so a later session
meets it at that moment rather than having to go looking. Reach for
this whenever the answer to "what happens if someone doesn't do
this" was "they lose time".
* "NO" — let it go. If the observation is still worth keeping and none
of the above fits, it is a note (create_note): recorded, findable,
and binding on nobody.
The middle three are not consolation prizes. They are where most good
observations belong, and the reason the kind question is worth asking
out loud rather than settled silently before the proposal.
Where the interface offers structured choices, ask it that way — a
question with named options is answered in a click, while the same
question inside a paragraph is answered by scrolling past. Where it does
not, write the three options out as three options. Either way ask once
and let the answer stand; re-raising a declined proposal argues a rule
not, write the answers out as a list. Either way ask once and let the
answer stand; re-raising a declined proposal argues a rule
into existence, which is the thing this whole loop exists to prevent.
A rulebook rule is GLOBAL — it applies in every project — so it must read as
@@ -431,14 +461,16 @@ async def create_project_rule(
in get_project's project_rules and in list_rules(project_id=...).
PROPOSE, THEN WRITE ON A YES — create_rule's opening carries the whole
loop: the four things a proposal states (what it would require, its
intent, why now, and how it would be enforced) and the one-word question
that closes it (approve as written / talk about it / no). All of it
applies here unchanged. Reach for that loop MORE readily on this surface,
not less: a project rule stays out of an unfiltered list_rules(), and a
conditional one stays out of session start too, so the operator's yes is
the one moment this rule is certain to have been seen by the person it
binds.
loop: the kind question that comes first (what happens if someone doesn't
do this — a rule binds, a preference guides, a lesson informs), the four
things a proposal states (what it would require, its intent, why now, and
how it would be enforced) and the one-word question that closes it
(approve as written / talk about it / make it a preference / make it a
lesson / no). All of it applies here unchanged. Reach for that loop MORE
readily on this surface, not less: a project rule stays out of an
unfiltered list_rules(), and a conditional one stays out of session start
too, so the operator's yes is the one moment this rule is certain to have
been seen by the person it binds.
Check first whether a rule is the right shape at all — create_rule's
opening asks that question and it applies identically here. A visual