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
+56
View File
@@ -13,8 +13,57 @@ Sentinels (match the milestone/task tool conventions):
from __future__ import annotations
from scribe.mcp._context import current_user_id
from scribe.services import notes as notes_svc
from scribe.services import systems as systems_svc
# Below this, a project is young enough that the mild "which area is this
# about?" question stays proportionate; at or above it, a zero-Systems project
# has demonstrated that the question never converts (#2683 — Minstrel reached
# 282 records without a single System) and the ask escalates.
_BOOTSTRAP_MIN_RECORDS = 20
_BOOTSTRAP_TITLES = 6
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 generic zero-state question habituates: identical on every record,
maximal in scope ("invent the taxonomy"), asked at wrap-up time — so
organic sessions skip it forever and only audit-shaped sessions ever mint
(#2683). What separates the nudges that convert from the prose that
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
count and the recent titles, and asks for a concrete deliverable: propose
a starter set, confirm, create.
Self-retiring by construction: callers only reach for it while the
project has zero Systems, so the first create_system ends it everywhere.
Returns None below the record threshold or on any failure (fail-open —
a hint must never break the call it rides on).
"""
try:
recent, total = await notes_svc.list_notes(
user_id, project_id=project_id, sort="updated_at",
limit=_BOOTSTRAP_TITLES,
)
except Exception:
return None
if total < _BOOTSTRAP_MIN_RECORDS:
return None
titles = "; ".join(
'"' + " ".join((n.title or "").split())[:70] + '"' for n in recent
)
return (
f"This project has {total} records and NO Systems modelled — none of "
"them can be tagged to an area, so recurring problem-spots stay "
"invisible. Bootstrap the vocabulary now, in this session: from the "
f"areas the records themselves name (recent: {titles}), propose 3-6 "
"Systems to the operator, create_system each confirmed one with a "
"one-paragraph charter, then tag this record (system_ids=[...]). "
"This ask repeats until the first System exists; answering it once "
"retires it for every future record."
)
async def untagged_systems_hint(user_id: int, project_id: int) -> str | None:
"""The Systems question, for an untagged project record.
@@ -26,6 +75,10 @@ async def untagged_systems_hint(user_id: int, project_id: int) -> str | None:
vocabulary is not an exemption — it is the question at its most urgent
(#2562, #2569). Instruction prose alone demonstrably doesn't fire at write
time; in-band behavior (the duplicate gate) does.
In a MATURE zero-Systems project the question escalates to the bootstrap
ask instead (#2683): the mild form demonstrably never converts there, and
an evidence-carrying, deliverable-shaped ask is the form that does.
"""
# Fail-open like the dedup gate: a hint must never break the call.
try:
@@ -37,6 +90,9 @@ async def untagged_systems_hint(user_id: int, project_id: int) -> str | None:
f"#{s.id} {s.name}" for s in systems
) + "."
else:
ask = await bootstrap_systems_ask(user_id, project_id)
if ask:
return ask
vocab = "This project has no Systems yet."
return (
"This record is untagged — which area(s) of the project is it about? "