feat(systems): evidence-carrying bootstrap ask for mature zero-Systems projects (#2683)
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 10s
CI & Build / integration (push) Successful in 24s
CI & Build / TypeScript typecheck (push) Successful in 33s
CI & Build / Python tests (push) Successful in 1m4s
CI & Build / Build & push image (push) Successful in 39s

The generic zero-state systems_hint never converts: identical on every
record, maximal in scope, asked at wrap-up time — Minstrel reached 282
records with zero Systems while vocabularied projects grew organically.
What converts is the project's own evidence at the moment of action.

bootstrap_systems_ask (mcp/tools/systems.py) fires only in a project
with >=20 records and no Systems: it names the record count and recent
titles, and asks for a concrete deliverable — propose 3-6 Systems,
confirm with the operator, create_system the set. Self-retiring: the
first System ends it everywhere. Wired at both moments the task named:
untagged_systems_hint escalates to it at write time, and enter_project
carries it as systems_bootstrap at arrival (attached only when it
applies). Young projects keep the mild question; populated vocabularies
never pay the count query.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-17 14:53:46 -04:00
co-authored by Claude Fable 5
parent d6c9f08a59
commit 7a5e2b18d9
4 changed files with 221 additions and 6 deletions
+26 -2
View File
@@ -17,6 +17,7 @@ keeps working.
from __future__ import annotations
from scribe.mcp._context import current_user_id
from scribe.mcp.tools import systems as systems_tools
from scribe.services import coverage as coverage_svc
from scribe.services import design_systems as design_systems_svc
from scribe.services import milestones as milestones_svc
@@ -57,7 +58,8 @@ async def enter_project(project_id: int) -> dict:
Returns a dict with keys: project, milestone_summary, applicable_rules,
project_rules, subscribed_rulebooks, applicable_rules_truncated,
open_tasks, recent_notes, design_system, systems, pattern_coverage.
open_tasks, recent_notes, design_system, systems, pattern_coverage
plus systems_bootstrap, present only when it applies (see below).
`pattern_coverage` (usually null) is a one-line estimate of how much of
the bound repo's code has recorded snippets — e.g. "pattern-library
@@ -72,6 +74,12 @@ async def enter_project(project_id: int) -> dict:
create it with create_system rather than leaving the area unmodelled. Read
a subsystem's accumulated records with list_system_records.
`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
vocabulary from the areas the project's records name, confirm it with the
operator, and create_system the confirmed set. It stops appearing the
moment the first System exists.
`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
departures from it) plus a summary of the token set — treat it as binding
@@ -116,6 +124,17 @@ async def enter_project(project_id: int) -> dict:
# three days of the feature landing (#2546's audit).
systems = await systems_svc.list_systems(uid, project_id)
# 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
# task — the one moment "propose a starter vocabulary" is cheap. The
# write-moment half rides untagged-record responses (attach_systems);
# both retire the instant the first System exists.
systems_bootstrap = None
if not systems:
systems_bootstrap = await systems_tools.bootstrap_systems_ask(
uid, project_id
)
# Probably the largest surfacing by volume, and it emitted nothing — so
# the pulls it caused floated unattributed and the surfaced:pulled ratio
# ran against a denominator missing its biggest contributor (#2477). An
@@ -143,7 +162,7 @@ async def enter_project(project_id: int) -> dict:
project.user_id or uid, project_id
)
return {
out = {
"project": project.to_dict(),
"pattern_coverage": coverage_svc.coverage_line(coverage) if coverage else None,
# Trimmed to what tagging needs. The full charter is get_system's job —
@@ -179,6 +198,11 @@ async def enter_project(project_id: int) -> dict:
for n in recent_notes
],
}
# Attached only when it applies — a key that usually says null trains
# readers to skip it (#2483), and this one exists to be acted on.
if systems_bootstrap:
out["systems_bootstrap"] = systems_bootstrap
return out
async def get_project(project_id: int) -> dict: