fix(#4022): instruction surfaces stop describing the always-on tier milestone 394 removed
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / integration (push) Successful in 47s
CI & Build / TypeScript typecheck (push) Successful in 58s
CI & Build / Python tests (push) Successful in 1m41s
CI & Build / Build & push image (push) Successful in 28s

Skill bodies and tool docstrings still taught the deleted model: using-scribe
said always-on rules "arrive whether or not you ask" and that SessionStart may
inject a rule index; create_project, decide_project_inception and enter_project
said an undecided project gets "every always-on rulebook"; create_rule pointed
standards at "the always-on one"; the verification sweeps and
retrieval_telemetry listed always-on paths and a live preload.

Every passage now describes the current model: every rule is retrieved, a
rulebook binds only by subscription, an undecided project inherits nothing,
and the preload survives only in telemetry rows older than 394.

Also repairs three sentences left half-replaced by the 394 edits: the static
context's "If you have not loaded the no rule has arrived", create_rule's
"an A subscribed rulebook", and create_project's doubled
subscribe_rulebooks entry. Plugin version minted.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-14 10:39:27 -04:00
co-authored by Claude Opus 5
parent 7239e3c479
commit 6c1fd28179
7 changed files with 46 additions and 49 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "scribe", "name": "scribe",
"description": "Scribe system-of-record for Claude Code: MCP tools over your notes/tasks/projects/rules, a session-start push channel that surfaces your active-project context, process-skills (writing-plans, reporting-back, systematic-debugging, verification, brainstorming, reusing-code), and your saved Scribe Processes auto-surfaced as skills (/scribe:sync). Replaces superpowers + file-memory with one app-backed plugin.", "description": "Scribe system-of-record for Claude Code: MCP tools over your notes/tasks/projects/rules, a session-start push channel that surfaces your active-project context, process-skills (writing-plans, reporting-back, systematic-debugging, verification, brainstorming, reusing-code), and your saved Scribe Processes auto-surfaced as skills (/scribe:sync). Replaces superpowers + file-memory with one app-backed plugin.",
"version": "2026.09.14.1411", "version": "2026.09.14.1438",
"author": { "author": {
"name": "Bryan Van Deusen" "name": "Bryan Van Deusen"
}, },
+3 -3
View File
@@ -17,9 +17,9 @@ for the operator's work, and as your own working memory across sessions.
commit / push, or any other hard-to-reverse or outward-facing action, the commit / push, or any other hard-to-reverse or outward-facing action, the
operator's Scribe rules decide what to do — NOT generic conventions baked operator's Scribe rules decide what to do — NOT generic conventions baked
into the harness or your defaults (e.g. "branch before committing," "open a into the harness or your defaults (e.g. "branch before committing," "open a
feature branch per task," "push to a fork"). If you have not loaded the feature branch per task," "push to a fork"). If no rule has arrived for the
no rule has arrived for the act in front of you, `search(content_type= act in front of you, `search(content_type="rule")` BEFORE acting rather than
"rule")` BEFORE acting rather than falling back on a default habit. When a falling back on a default habit. When a
retrieved rule and a default habit disagree, the rule wins; if no rule retrieved rule and a default habit disagree, the rule wins; if no rule
speaks to it, ask rather than assume. speaks to it, ask rather than assume.
- **Rules bind; preferences do not.** A record's `kind` says which. A **rule** - **Rules bind; preferences do not.** A record's `kind` says which. A **rule**
+12 -14
View File
@@ -27,10 +27,9 @@ If the working repo maps to a Scribe project (you're in a known repo, or
project plus the rules bound to the areas it works in, open tasks, and recent project plus the rules bound to the areas it works in, open tasks, and recent
notes in one shot. notes in one shot.
Do this actively. A SessionStart hook *may* also inject a rule index, but treat Do this actively. Nothing is handed to a session up front to stand in for it —
that as a bonus, not a precondition: it can be absent (e.g. when the instance is rules arrive by retrieval, when your work or the operator's message matches
unreachable, or the token didn't reach the hook), so the reliable path is this one — so asking and entering the project are the reliable path.
explicit pull. Rules loaded this way are **binding** for the session.
## Scribe holds these functions — don't keep a second copy ## Scribe holds these functions — don't keep a second copy
@@ -88,17 +87,16 @@ Two constraints on *how* that's achieved:
asks. If what you learned is that something MUST be done a certain way, asks. If what you learned is that something MUST be done a certain way,
that is a rule to propose, not a preference to harden in place. that is a rule to propose, not a preference to harden in place.
Rules come in two tiers. **Always-on** rules are delivered — they arrive Every rule is RETRIEVED: one reaches you when a command, the code you are
whether or not you ask. **Conditional** rules are RETRIEVED, and one binds writing or the operator's message resembles what it is about, and a rule
just as hard for never having been handed to you. So before a consequential binds just as hard for never having been handed to you. So before a
act, `search(content_type="rule")` on what you are about to do. An empty consequential act, `search(content_type="rule")` on what you are about to
loaded set is not evidence that no rule applies; it is only evidence that do. An empty session is not evidence that no rule applies; it is only
none was pushed, and those are different claims. evidence that nothing has matched yet, and those are different claims.
The tier split exists because delivery does not scale: every resident rule Retrieval is what lets a rulebook keep growing — a rule costs nothing in a
costs tokens in every session forever, so a rulebook that grows past a few session it has nothing to do with — but retrieval only fires if something
dozen either stops growing or stops fitting. Retrieval is what lets the asks.
rulebook keep growing — but retrieval only fires if something asks.
**Ask hardest where you feel most certain.** Rules about which TOOL to reach **Ask hardest where you feel most certain.** Rules about which TOOL to reach
for — use the forge's MCP client rather than curling its API, don't stand up for — use the forge's MCP client rather than curling its API, don't stand up
+2 -2
View File
@@ -384,8 +384,8 @@ async def notes_due_for_verification(
thing there is. 0 = no age filter. thing there is. 0 = no age filter.
project_id: narrow to one project. 0 = every project. Unlike the rules project_id: narrow to one project. 0 = every project. Unlike the rules
sweep, this filter is safe: a note belongs to at most one project sweep, this filter is safe: a note belongs to at most one project
outright, with none of the subscription and always-on paths that outright, with none of the subscription paths that would make a
would make a project filter UNDER-report a rule. project filter UNDER-report a rule.
never_only: only notes nobody has ever verified. never_only: only notes nobody has ever verified.
""" """
uid = current_user_id() uid = current_user_id()
+11 -12
View File
@@ -83,7 +83,7 @@ async def enter_project(project_id: int) -> dict:
`inception` (milestone 297) appears ONLY when the project is yours and `inception` (milestone 297) appears ONLY when the project is yours and
nobody has decided what it inherits: it carries the current defaults nobody has decided what it inherits: it carries the current defaults
(which always-on rulebooks bind, design system, Systems), what to ask the (the rulebooks it could subscribe to, design system, Systems), what to ask the
operator — once — and the decide_project_inception call that answers it; operator — once — and the decide_project_inception call that answers it;
it repeats on every enter until a decision is recorded. it repeats on every enter until a decision is recorded.
@@ -149,7 +149,7 @@ async def enter_project(project_id: int) -> dict:
) )
# The inception ask (milestone 297): a project nobody has decided on # The inception ask (milestone 297): a project nobody has decided on
# inherits its defaults silently — always-on rulebooks, no design system, # inherits nothing, silently — no rulebook subscriptions, no design system,
# no Systems. Owner-only (deciding is the owner's), and only until a # no Systems. Owner-only (deciding is the owner's), and only until a
# decision is recorded; the key is ABSENT otherwise (#2483). # decision is recorded; the key is ABSENT otherwise (#2483).
inception_ask = None inception_ask = None
@@ -288,8 +288,8 @@ async def create_project(
before calling, ask the operator the four inception questions and pass before calling, ask the operator the four inception questions and pass
the answers; a project created without any of them is UNDECIDED and the answers; a project created without any of them is UNDECIDED and
enter_project will ask until decide_project_inception records it. enter_project will ask until decide_project_inception records it.
Defaults if nobody decides: every always-on rulebook binds, nothing is Defaults if nobody decides: no rulebook subscriptions, no design system,
subscribed, no design system, no Systems. no Systems.
Args: Args:
title: Project name (required). title: Project name (required).
@@ -297,11 +297,10 @@ async def create_project(
goal: The desired outcome or definition of done for the project. goal: The desired outcome or definition of done for the project.
status: one of active (default), paused, completed, archived. status: one of active (default), paused, completed, archived.
color: Optional hex colour for the project card (e.g. "#6366f1"). color: Optional hex colour for the project card (e.g. "#6366f1").
subscribe_rulebooks: rulebook ids this project opts into. Since subscribe_rulebooks: rulebook ids this project opts into.
milestone 394 subscription is the only way a rulebook binds a Subscription is the only way a rulebook binds a project, so a
project, so there is no automatic tier left to decline. Was rulebook left out simply does not apply. list_rulebooks shows
NOT inherit ([] = inherit them all). list_rulebooks shows which are which exist.
subscribe_rulebooks: rulebook ids to subscribe (the non-always-on ones).
design_system_id: the design system this project's UI is built from design_system_id: the design system this project's UI is built from
(list_design_systems); -1 = explicitly none; 0 = not stated. (list_design_systems); -1 = explicitly none; 0 = not stated.
seed_systems: true mints the standard starter Systems (CI & Release, seed_systems: true mints the standard starter Systems (CI & Release,
@@ -351,9 +350,9 @@ async def decide_project_inception(
unsubscribe_project_from_rulebook to undo one), replaces the design unsubscribe_project_from_rulebook to undo one), replaces the design
system, and never re-seeds Systems a project already has. system, and never re-seeds Systems a project already has.
Args: as create_project's inception args. Passing nothing records an Args: as create_project's inception args. Passing nothing records a
inherit-all decision (every always-on rulebook binds, no subscriptions, decision to take nothing (no subscriptions, no design system, no seed) —
no design system, no seed) — a valid answer, stated. a valid answer, stated.
""" """
uid = current_user_id() uid = current_user_id()
choices = _inception_choices( choices = _inception_choices(
+5 -6
View File
@@ -308,13 +308,12 @@ async def create_rule(
and let the answer stand; re-raising a declined proposal argues a rule 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. into existence, which is the thing this whole loop exists to prevent.
A rulebook rule is shared by every project that gets the rulebook: an A rulebook rule is shared by every project subscribed to the rulebook, so
A subscribed rulebook binds the it must read as a general standard —
projects that opt in. So a rulebook rule must read as a general standard —
never pin it to one project's files, paths, or quirks. For a rule that never pin it to one project's files, paths, or quirks. For a rule that
applies to a single project only, use create_project_rule instead (no applies to a single project only, use create_project_rule instead (no
rulebook+topic ceremony). If it's a standard a CATEGORY of projects shares, rulebook+topic ceremony). If it's a standard a CATEGORY of projects shares,
put it in a themed subscribed rulebook, not the always-on one. put it in a rulebook for that category and subscribe those projects to it.
Write it general WITHOUT hedging for the exceptions. A project that needs Write it general WITHOUT hedging for the exceptions. A project that needs
to strengthen, narrow or replace this rule writes its own and links it to strengthen, narrow or replace this rule writes its own and links it
@@ -1048,8 +1047,8 @@ async def rules_due_for_verification(
never_only: only rules nobody has ever verified. never_only: only rules nobody has ever verified.
NOT filterable by project, deliberately: a project reaches rules through NOT filterable by project, deliberately: a project reaches rules through
project scope, subscriptions, always-on rulebooks and exclusions, and a project scope and rulebook subscriptions, and a filter that missed one of
filter that missed one of those paths would UNDER-report — which is the those paths would UNDER-report — which is the
exact failure this whole surface exists to prevent. Read the whole list. exact failure this whole surface exists to prevent. Read the whole list.
""" """
uid = current_user_id() uid = current_user_id()
+12 -11
View File
@@ -280,19 +280,20 @@ It is an UPPER BOUND per surface: a pull records the door it came
`surfaced` VS `ambient` IS THE READING THAT MATTERS HERE. `surfaced` counts `surfaced` VS `ambient` IS THE READING THAT MATTERS HERE. `surfaced` counts
rules a ranker chose — today only the write-path arm — and those are claims rules a ranker chose — today only the write-path arm — and those are claims
a pull can settle. `ambient` counts BULK DELIVERIES: the SessionStart a pull can settle. `ambient` counts BULK DELIVERIES: the `rules_payload`
preload and the `rules_payload` surfaces surfaces (`enter_project`, `get_project`, `get_milestone`,
(`enter_project`, `get_project`, `get_milestone`, `start_planning`, `start_planning`, `get_task`), which hand over the whole applicable set at
`get_task`), which hand over the whole applicable set at once with nobody once with nobody choosing anything — plus, in rows older than milestone
choosing anything. A large `ambient` says the resident set is big and 394, the SessionStart preload it removed. A large `ambient` says a bulk set
arrives often — never that it is useful, and never that it is read. is big and arrives often — never that it is useful, and never that it is
read.
`pull_through` therefore divides by `surfaced` alone. Fold the preload in `pull_through` therefore divides by `surfaced` alone. Fold the preload in
and growing the always-on set would depress the arm's measured precision and growing a bulk set would depress the arm's measured precision while
while trimming it would flatter it, for reasons having nothing to do with trimming it would flatter it, for reasons having nothing to do with the
the arm. To judge the PRELOAD instead, compare `ambient` against pulls of arm. To judge a BULK surface instead, compare `ambient` against pulls of
those same rules over time: a resident set surfaced thousands of times and those same rules over time: a set surfaced thousands of times and opened
opened never is the dead-weight signal, one tier up. never is the dead-weight signal, one level up.
Read it against `sources["write_path_rule"]`. That arm was once believed Read it against `sources["write_path_rule"]`. That arm was once believed
never to decline — the reading that scoped #3311 — but it was the arm's never to decline — the reading that scoped #3311 — but it was the arm's