feat(shapes): the write-path hook suggests and no longer stamps (milestone 439 step 4)
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / TypeScript typecheck (push) Successful in 53s
CI & Build / integration (push) Successful in 54s
CI & Build / Python tests (push) Successful in 1m49s
CI & Build / Build & push image (push) Successful in 27s

The hook used to land its evidence as `instance` rows, classified_by=hook —
a permanent verdict nobody read, and the source of the weak stamps that
poisoned two ledgers (#4608). Now the agent that wrote the code judges it at
the end of the turn, and the hook's evidence is what it is shown.

- shape_ledger.suggest_write_path_instances (was stamp_write_path_instances):
  same evidence and form gate, but it writes a PROPOSAL — proposed_snippet_id,
  basis "reference" (named) or "semantic" (resembles), score — and never a
  status. A judged row is left alone; a new shape gets an unclassified
  provisional row so the suggestion reaches the end-of-turn question as
  "looks like #N". By-name uses edges stay: a fact, not a verdict.
- The prior-art line says "looks like #N … when you judge this turn's
  shapes, say whether it is", and the result key is `suggested`.
- write_time_divergence takes `suggested`; _RESEMBLE_MIN re-documented as the
  suggestion bar and the weak-stamp line.
- reusing-code skill no longer says the pull stamps an instance.
- Tests moved to the new contract, plus a guard that nothing the hook does
  writes classified_by="hook".

Plugin version minted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-01 08:42:10 -04:00
co-authored by Claude Opus 5.5
parent f1fbdf746a
commit 4cf1c6f042
7 changed files with 150 additions and 110 deletions
+6 -4
View File
@@ -270,10 +270,12 @@ async def write_path_prior_art():
css|sym. The shape ledger's write-path feed
(#2791): when the session recently PULLED a
snippet this payload references or resembles,
these land as instance rows (classified_by=hook).
Honoured only for a caller allowed to write — a
read-scoped key still gets the hint, and never
changes accounting on a GET.
these carry it as a proposal — evidence for the
agent's own end-of-turn judgment, never a
verdict (milestone 439). Honoured only for a
caller allowed to write — a read-scoped key still
gets the hint, and never changes the ledger on a
GET.
"""
path = (request.args.get("path") or "").strip()
code = request.args.get("code") or ""
+29 -26
View File
@@ -2083,16 +2083,18 @@ async def build_write_path_hint(
``stamp_shapes`` turns the same request into the ledger's write-path feed
(#2791): the (kind, name) definitions the hook saw in — or enclosing —
the payload. When the session has PULLED a snippet recently and this
payload references or resembles it, those shapes land as `instance` rows
(classified_by=hook, see shape_ledger.stamp_write_path_instances) and
the result's ``stamped`` lists them. The route passes it only for a
caller allowed to write — a read-scoped key gets the hint, never the
stamp. ``repo_key`` (the hook's remote, normalised) homes a provisional
row for a shape the ledger has not synced yet.
payload references or resembles it, those shapes carry it as a PROPOSAL
(see shape_ledger.suggest_write_path_instances) and the result's
``suggested`` lists them. Never a verdict since milestone 439: the agent
that wrote the code judges it at the end of the turn, shown this as
evidence. The route passes it only for a caller allowed to write — a
read-scoped key gets the hint, never a ledger write. ``repo_key`` (the
hook's remote, normalised) homes a provisional row for a shape the
ledger has not synced yet.
"""
cfg = await get_writepath_config(user_id)
empty = {"context": "", "note_ids": [], "sync_note_ids": [], "config": cfg,
"stamped": [], "divergence": [], "derive": [], "derive_keys": [],
"suggested": [], "divergence": [], "derive": [], "derive_keys": [],
"rule_ids": []}
path = (path or "").strip()
if not cfg["enabled"] or not path:
@@ -2356,18 +2358,18 @@ async def build_write_path_hint(
menu = (placed + scored)[:max(0, top_k - len(synced))]
# The stamp runs whether or not anything is rendered — after dedup, the
# common case is a silent hint and a pulled canon being instantiated.
stamped: list[dict] = []
# The suggestion runs whether or not anything is rendered — after dedup,
# the common case is a silent hint and a pulled canon being instantiated.
suggested: list[dict] = []
if stamp_shapes and pulled:
try:
stamped = await shape_ledger_svc.stamp_write_path_instances(
suggested = await shape_ledger_svc.suggest_write_path_instances(
user_id, project_id, path=path, shapes=stamp_shapes,
code=code or "", pulled=pulled, resembles=resembles,
repo_key=repo_key,
)
except Exception:
logger.warning("Write-path ledger stamping failed", exc_info=True)
logger.warning("Write-path ledger suggestion failed", exc_info=True)
# The in-band button-B check (#2793): the hook named the shapes being
# written; if this directory+kind is canon-dense and a named shape isn't
# (about to be) an instance of that canon, say so NOW — at the write,
@@ -2376,7 +2378,7 @@ async def build_write_path_hint(
if stamp_shapes and project_id:
try:
divergence = await shape_ledger_svc.write_time_divergence(
project_id, path, stamp_shapes, stamped, code or "",
project_id, path, stamp_shapes, suggested, code or "",
)
except Exception:
logger.warning("write-time divergence check failed", exc_info=True)
@@ -2430,7 +2432,7 @@ async def build_write_path_hint(
design_text, design_dedup = await _design_arm(
user_id, project_id, path, set(exclude_derive or []),
)
if not staleness and not synced and not menu and not stamped and not divergence and not derive:
if not staleness and not synced and not menu and not suggested and not divergence and not derive:
if design_text:
return {**empty, "context": design_text, "derive_keys": [design_dedup]}
return empty
@@ -2531,8 +2533,8 @@ async def build_write_path_hint(
if passage:
lines.append(f"> ↳ {passage}")
if stamped:
lines.append(_stamp_line(path, stamped))
if suggested:
lines.append(_suggest_line(path, suggested))
if divergence:
lines.append(_divergence_line(path, divergence))
if derive:
@@ -2694,7 +2696,7 @@ async def build_write_path_hint(
"note_ids": note_ids,
"sync_note_ids": sync_note_ids,
"config": cfg,
"stamped": stamped,
"suggested": suggested,
"divergence": divergence,
"derive": derive,
"derive_keys": [d["key"] for d in derive] + (
@@ -3017,22 +3019,23 @@ def _divergence_line(path: str, divergence: list[dict]) -> str:
)
def _stamp_line(path: str, stamped: list[dict]) -> str:
"""One line saying what the ledger just recorded, so the session can
correct a wrong stamp in the moment rather than an audit finding it."""
def _suggest_line(path: str, suggested: list[dict]) -> str:
"""One line naming the recorded shape this code looks like, as evidence
for the judgment the agent gives at the end of the turn (milestone 439)
— not a record of one already made."""
by_snippet: dict[int, list[str]] = {}
for row in stamped:
for row in suggested:
label = f".{row['symbol']}" if row["kind"] == "css" else row["symbol"]
by_snippet.setdefault(int(row["snippet_id"]), []).append(f"`{label}`")
parts = [
f"{', '.join(names)} → instance of #{sid}"
f"{', '.join(names)} → looks like #{sid}"
for sid, names in by_snippet.items()
]
return (
f"> Shape accounting: recorded at `{path}` — {'; '.join(parts)} "
"(classified_by=hook: you pulled that snippet this session and this "
"code references/resembles it). Not an instance? `classify_shapes` "
"overrides a hook stamp."
f"> Shape accounting: at `{path}` — {'; '.join(parts)} "
"(you pulled that snippet this session and this code names or resembles "
"it). When you judge this turn's shapes, say whether it is: `instance` "
"of it, `variant` with the why, or something else."
)
+45 -25
View File
@@ -908,11 +908,13 @@ async def snippet_consumers(user_id: int, note_id: int) -> dict:
# semantic arm scored it above the write-path threshold for this
# very payload. Either is evidence; the pull alone is not.
# Both hold → every shape the hook named at that path, of the snippet's kind,
# becomes instance-of-N with classified_by="hook" and the evidence as reason.
# carries N as a PROPOSAL (milestone 439). Until then it became instance-of-N
# with classified_by="hook" — a verdict nobody read, and the rows written that
# way are what `stamps_to_review` still lists.
#
# A hook row is EVIDENCE, not judgment: it only ever lands on rows nobody has
# judged (unclassified) or rows an earlier hook stamped, never on a canonical
# row or an agent/audit/import judgment. Re-judge with classify_shapes.
# A proposal is EVIDENCE, not judgment: it lands only on rows nobody has
# judged, never touches a status, and is what the end-of-turn question shows
# the agent as "looks like #N". The agent's classify_shapes is the verdict.
# "The write path actually pulled it": a working session's reach. The
# precision comes from the in-play test above, not from this window.
@@ -1354,7 +1356,7 @@ def _stamp_allowed(rank: int, mine: str, canon: str) -> bool:
return forms_agree(mine, canon)
async def stamp_write_path_instances(
async def suggest_write_path_instances(
user_id: int,
project_id: int,
*,
@@ -1365,22 +1367,35 @@ async def stamp_write_path_instances(
resembles: dict[int, float] | None = None,
repo_key: str = "",
) -> list[dict]:
"""Land hook evidence as `instance` rows for the shapes being written.
"""Offer hook evidence as a PROPOSAL on the shapes being written.
This used to land the evidence as `instance` rows, classified_by=hook —
a permanent classification written with nobody reading it. That is what
poisoned two ledgers (132 and 488 rows below today's floor, #4608), and
what milestone 439 retires: the agent that wrote the code says what it is,
at the end of the turn, and this evidence is what it is shown when it
does. So the row keeps its status; the candidate canon goes into the
proposal fields (`proposed_snippet_id`, `proposal_basis` "reference" for a
by-name hit or "semantic" for a resemblance, `proposal_score`), where the
end-of-turn question reads it as "looks like #N". A judged row is left
alone entirely.
``shapes`` is the hook's (kind, name) list for ``path``; ``pulled`` is
recent_pulls(); ``resembles`` maps snippet ids the semantic arm scored
for this payload to their score. Returns the rows stamped, each
for this payload to their score. Returns the suggestions, each
{path, symbol, kind, snippet_id, reason} — empty in the common case.
A shape the ledger has no live row for yet (it is being written right
now) gets a PROVISIONAL row under ``repo_key`` — first/last-seen empty —
so the stamp is not lost to the next sync, which either confirms the
shape (sets its seen marker) or stamps it vanished. No repo key → only
existing rows are stamped.
now) gets a PROVISIONAL unclassified row under ``repo_key`` — first/
last-seen empty — so the suggestion survives to the end of the turn and
the next sync either confirms the shape or stamps it vanished. No repo
key → only existing rows carry a suggestion.
Every pulled canon the payload NAMES is still recorded as a `uses` edge:
that is a fact about the code, not a verdict about what it is.
When more than one pulled snippet is in play for a shape, a by-name
reference beats resemblance and the most recent pull breaks ties: a row
holds one canon (the known model limit logged on #2790).
reference beats resemblance and the most recent pull breaks ties.
"""
from scribe.services import access
from scribe.services import snippets as snippets_svc
@@ -1433,8 +1448,7 @@ async def stamp_write_path_instances(
for bucket in in_play.values():
bucket.sort(key=lambda t: (t[0], t[1]), reverse=True)
now = datetime.now(timezone.utc)
stamped: list[dict] = []
suggested: list[dict] = []
async with async_session() as session:
rows = (
await session.execute(
@@ -1486,9 +1500,12 @@ async def stamp_write_path_instances(
by_key[(name, kind)] = row
elif not (row.status in _MECHANICAL_TODO or row.classified_by == "hook"):
continue # a judgment — or the canon itself — stands
await _judge(session, row, status="instance", snippet_id=sid, by="hook",
reason=why, at=now)
stamped.append({
# A proposal, never a status: the writer decides (milestone 439).
row.proposed_snippet_id = sid
row.proposal_basis = "reference" if _rank >= 2 else "semantic"
row.proposal_score = resembles.get(sid) if _rank < 2 else 1.0
row.proposal_group = None
suggested.append({
"path": path, "symbol": name, "kind": kind,
"snippet_id": sid, "reason": why,
})
@@ -1504,9 +1521,9 @@ async def stamp_write_path_instances(
[t[2] for t in bucket if t[0] == 2],
basis="hook", evidence="write path: pulled the snippet, payload names its symbol",
)
if stamped:
if suggested:
await session.commit()
return stamped
return suggested
# --- the mechanical proposer (#2792): the machine proposes, judgment classifies
@@ -2210,8 +2227,11 @@ async def confirm_proposals(
# A canon dominates a directory+kind when at least this many siblings are
# judged (canonical/instance) and this share of them answer to one snippet.
# How much a payload must resemble a canon before the hook may assert, with
# nobody watching, that a shape IS an instance of it.
# How much a payload must resemble a canon before the hook SUGGESTS it as the
# shape's canon — and, for the rows stamped before milestone 439, the bar below
# which a stored stamp is weak (`is_weak_stamp`). Until milestone 439 this was
# the bar for the hook to ASSERT, unattended, that a shape IS an instance; the
# hook no longer asserts anything, and the history below is why.
#
# WHY A FLOOR AT ALL. There was none: any score the semantic arm produced
# counted, so 0.69 asserted as confidently as 0.95, and the score went into
@@ -2364,15 +2384,15 @@ async def canon_density(
async def write_time_divergence(
project_id: int, path: str, shapes: list[tuple[str, str]], stamped: list[dict],
project_id: int, path: str, shapes: list[tuple[str, str]], suggested: list[dict],
code: str = "",
) -> list[dict]:
"""The in-band check for the shapes the hook named at ``path``: for each
kind whose directory has a dominant canon, the named shapes that are
not (already or just now) that canon's instance/canonical — new or
not that canon's instance/canonical (judged, or just suggested as it) — new or
unclassified rows only; a judged shape is not re-litigated at every
edit. Returns [{symbol, kind, canon_snippet_id, instances, judged}]."""
just_stamped = {(s["symbol"], s["kind"]): s["snippet_id"] for s in stamped}
just_stamped = {(s["symbol"], s["kind"]): s["snippet_id"] for s in suggested}
out: list[dict] = []
if not shapes:
return out