feat(systems): bootstrap mints directly — the standard vocabulary replaces operator approval (#2798)
CI & Build / Python lint (push) Successful in 5s
CI & Build / Plugin hooks (push) Successful in 15s
CI & Build / TypeScript typecheck (push) Successful in 34s
CI & Build / integration (push) Successful in 40s
CI & Build / Python tests (push) Successful in 1m5s
CI & Build / Build & push image (push) Successful in 59s

Operator directive: creating Systems is the agent's call, not an approval
flow — "propose to the operator, create each confirmed one" made the
operator a permission gate. The bootstrap ask now says create_system 3-6
directly, in-session, and the consistency that approval was covering moves
to a standard cross-project vocabulary (CI & Release, Auth & Access, Data
Model & Storage, API Surface, UI & Design, Import & Export, Background
Jobs, Observability): use the standard name verbatim where the area fits,
mint freely beyond it, the duplicate gate guards sprawl. create_system and
enter_project docstrings carry the same contract.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-19 20:05:02 -04:00
co-authored by Claude Fable 5
parent d50ebbad66
commit 5265d11a6a
4 changed files with 46 additions and 20 deletions
+5 -5
View File
@@ -78,10 +78,10 @@ async def enter_project(project_id: int) -> dict:
a subsystem's accumulated records with list_system_records. a subsystem's accumulated records with list_system_records.
`systems_bootstrap` appears ONLY when the project has many records and no `systems_bootstrap` appears ONLY when the project has many records and no
Systems at all — act on it before starting other work: propose a starter Systems at all — act on it before starting other work: create_system a
vocabulary from the areas the project's records name, confirm it with the starter vocabulary from the areas the project's records name, directly
operator, and create_system the confirmed set. It stops appearing the and without asking permission, preferring the standard names the ask
moment the first System exists. lists. It stops appearing the moment the first System exists.
`design_system` is null unless the project points at one. When present it `design_system` is null unless the project points at one. When present it
carries the chain-merged guidance (the house style AND this project's carries the chain-merged guidance (the house style AND this project's
@@ -129,7 +129,7 @@ async def enter_project(project_id: int) -> dict:
# The arrival-moment half of the bootstrap ask (#2683): session start is # The arrival-moment half of the bootstrap ask (#2683): session start is
# when the agent has just read the project map and is not yet deep in a # when the agent has just read the project map and is not yet deep in a
# task — the one moment "propose a starter vocabulary" is cheap. The # task — the one moment minting a starter vocabulary is cheap. The
# write-moment half rides untagged-record responses (attach_systems); # write-moment half rides untagged-record responses (attach_systems);
# both retire the instant the first System exists. # both retire the instant the first System exists.
systems_bootstrap = None systems_bootstrap = None
+35 -13
View File
@@ -23,6 +23,18 @@ from scribe.services import systems as systems_svc
_BOOTSTRAP_MIN_RECORDS = 20 _BOOTSTRAP_MIN_RECORDS = 20
_BOOTSTRAP_TITLES = 6 _BOOTSTRAP_TITLES = 6
# The standard vocabulary (#2798): area names that recur across software
# projects, offered so "CI & Release" means the same thing in every project
# on the instance. Consistency comes from the shared names — NOT from asking
# the operator to approve each System; agents mint directly. Generic by
# design (rule #115): archetypes any codebase could have, never one
# install's subsystems. Mint freely beyond the list; the duplicate gate
# guards sprawl.
_STANDARD_SYSTEMS = (
"CI & Release", "Auth & Access", "Data Model & Storage", "API Surface",
"UI & Design", "Import & Export", "Background Jobs", "Observability",
)
async def bootstrap_systems_ask(user_id: int, project_id: int) -> str | None: async def bootstrap_systems_ask(user_id: int, project_id: int) -> str | None:
"""The escalated vocabulary-bootstrap ask for a mature zero-Systems project. """The escalated vocabulary-bootstrap ask for a mature zero-Systems project.
@@ -33,8 +45,10 @@ async def bootstrap_systems_ask(user_id: int, project_id: int) -> str | None:
(#2683). What separates the nudges that convert from the prose that (#2683). What separates the nudges that convert from the prose that
doesn't (the duplicate gate, the prior-art "already defined in 2 files") doesn't (the duplicate gate, the prior-art "already defined in 2 files")
is the project's OWN evidence in the ask — so this one carries the record is the project's OWN evidence in the ask — so this one carries the record
count and the recent titles, and asks for a concrete deliverable: propose count and the recent titles, and asks for a concrete deliverable: create
a starter set, confirm, create. a starter set directly, preferring the standard cross-project names.
Deliberately NOT an approval flow (#2798): the operator is not a
permission gate for vocabulary; the standard names carry consistency.
Self-retiring by construction: callers only reach for it while the Self-retiring by construction: callers only reach for it while the
project has zero Systems, so the first create_system ends it everywhere. project has zero Systems, so the first create_system ends it everywhere.
@@ -56,12 +70,15 @@ async def bootstrap_systems_ask(user_id: int, project_id: int) -> str | None:
return ( return (
f"This project has {total} records and NO Systems modelled — none of " f"This project has {total} records and NO Systems modelled — none of "
"them can be tagged to an area, so recurring problem-spots stay " "them can be tagged to an area, so recurring problem-spots stay "
"invisible. Bootstrap the vocabulary now, in this session: from the " "invisible. Bootstrap the vocabulary now, in this session, without "
f"areas the records themselves name (recent: {titles}), propose 3-6 " "asking permission — creating Systems is your call, not an approval "
"Systems to the operator, create_system each confirmed one with a " f"flow. From the areas the records themselves name (recent: {titles}), "
"one-paragraph charter, then tag this record (system_ids=[...]). " "create_system 3-6 Systems, each with a one-paragraph charter, then "
"This ask repeats until the first System exists; answering it once " "tag this record (system_ids=[...]). Where an area fits a standard "
"retires it for every future record." f"name, use it verbatim so it means the same thing in every project: "
f"{', '.join(_STANDARD_SYSTEMS)}. Mint freely beyond that list — the "
"duplicate gate guards sprawl. This ask repeats until the first "
"System exists; answering it once retires it for every future record."
) )
@@ -148,11 +165,16 @@ async def create_system(
Create one the moment two records would share an area that has no System Create one the moment two records would share an area that has no System
yet — the same two-or-more test snippets use. Don't wait to be asked to yet — the same two-or-more test snippets use. Don't wait to be asked to
name an area that plainly exists in the code; an unmodelled area means name an area that plainly exists in the code, and don't route the
every record about it stays untaggable. An audit or sweep that walks the creation through operator approval — minting vocabulary is the agent's
codebase is a DISCOVERY moment: mint the Systems it names as it names call (#2798); an unmodelled area means every record about it stays
them — the duplicate gate below, plus reviewing the existing list, is what untaggable. An audit or sweep that walks the codebase is a DISCOVERY
guards against sprawl, not holding back. Give each one a one-paragraph moment: mint the Systems it names as it names them — the duplicate gate
below, plus reviewing the existing list, is what guards against sprawl,
not holding back. Prefer the standard cross-project names where the area
fits one (CI & Release, Auth & Access, Data Model & Storage, API Surface,
UI & Design, Import & Export, Background Jobs, Observability) so the same
word means the same thing in every project. Give each one a one-paragraph
charter, not just a label: the description is what tells a later session charter, not just a label: the description is what tells a later session
whether a record belongs here. whether a record belongs here.
+1 -1
View File
@@ -285,7 +285,7 @@ def _enter_project_stubs(p):
async def test_enter_project_carries_the_bootstrap_ask_when_it_fires(): async def test_enter_project_carries_the_bootstrap_ask_when_it_fires():
"""The arrival-moment half of #2683: a mature zero-Systems project greets """The arrival-moment half of #2683: a mature zero-Systems project greets
the session with the concrete bootstrap ask, before it is deep in a task — the session with the concrete bootstrap ask, before it is deep in a task —
the moment "propose a starter vocabulary" is cheapest.""" the moment minting a starter vocabulary is cheapest."""
import contextlib import contextlib
ask = "This project has 282 records and NO Systems modelled — ..." ask = "This project has 282 records and NO Systems modelled — ..."
+5 -1
View File
@@ -129,7 +129,11 @@ async def test_untagged_hint_escalates_in_a_mature_zero_systems_project():
assert "282 records" in hint assert "282 records" in hint
assert "Fix scrape retry backoff" in hint # the project's own evidence assert "Fix scrape retry backoff" in hint # the project's own evidence
assert "3-6" in hint and "create_system" in hint assert "3-6" in hint and "create_system" in hint
assert "propose" in hint # NOT an approval flow (#2798): the agent mints directly, and the
# standard cross-project names carry the consistency instead.
assert "without asking permission" in hint
assert "propose" not in hint and "confirmed" not in hint
assert "CI & Release" in hint and "Auth & Access" in hint
# The generic wording is REPLACED, not appended — two questions is noise. # The generic wording is REPLACED, not appended — two questions is noise.
assert "no Systems yet" not in hint assert "no Systems yet" not in hint