Repository navigation
Remove create_schema's "ADR" worked example; record the tool-surface fidelity gap and two retractions - #2188
Conversation
The corpus's cases hand-author 1-2 tools per turn; production Stage 2 sends 9
with full parameter schemas. Measured against a live capture:
dev-status-change-enum turn 1 2,679 chars, 1 tool declaration
real production stage 2 24,636 chars, 9 tool declarations
So every corpus measurement ran on 11% of the prompt production sends, and the
missing 15,373 chars were entirely tool declarations. Handing the model one tool
and asking it to call that tool does not test tool SELECTION -- no wrong answer
is available -- so the corpus measured argument formation and never selection.
Rebuilt at fidelity (production-baseline.toml: 9 tools, 3 candidates, 22,064
chars) the case still passes 5/5, and its filter is BETTER than the 1-tool arm's:
the real schema teaches the structured form where the hand-authored one produced
a loose {"status":"in_dev"}.
43 arms and their measured results are recorded in ablation/FINDINGS.toml. The
substantive results:
- Dropping ALL instruction subtrees changed nothing at fidelity, but keeping only
rank-1's failed 5/5. Partial injection is worse than none: with no procedure
the tool schema is sole authority and the model follows it; with one partial
procedure the prose competes and wins wrongly.
- The repair is one SENTENCE ("only the first candidate is shown in full"), not a
mechanism. A load_procedure tool was offered in three arms and called ZERO
times -- lazy loading was not needed.
- K=1 breaks multi-turn: scoping to one skill's whitelist removes tools later
turns need.
- Inertness does NOT generalise. It holds where every rule the subtree carries is
also carried structurally, and fails where the subtree states something no
schema can -- dev-schema-creation lost `supersedes` 3/3.
- It is content, not length: ONE TYPE cut to a single clause still works; FIELDS
expanded to two sentences still fails; an IRRELEVANT two-sentence rule produced
malformed output echoing create_schema's own description back as an argument.
The rule that emerged, applied to production's own guidance (3,900 -> 1,546
chars, 5 of 10 rules) and better than full on the substantive axis:
KEEP what no schema can state -- ontological distinctions, instructions to act
on something ABSENT from the declared surface, conventions a schema
cannot encode.
DROP what a schema already carries.
NEVER SUBSTITUTE -- a differently-worded rule in an emptied slot measured worse
than an empty slot.
Two defects found that guidance cannot fix: core#2182 (search_nodes' `in`
operator gets a comma-joined string; both channels already state the rule and
prose does not help), and create_schema's `name` description supplying "ADR" as
an illustrative example that the model then adopts as the type's actual name --
schema-channel contamination, the mirror of what #1932 guards.
Not established: the production-guidance and name-contamination results rest on
one case and one skill. The other seven production skills are untested and need
the same per-skill measurement.
Adds dump_tool_defs bin so arms are built from model_facing_tool_definitions()
rather than transcribed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B8ywPs1H92aEKhXtCKK2au
…trim claim
TWO CHANGES, ONE A FIX AND ONE A RETRACTION.
1. create_schema's schema no longer names a plausible type
`(e.g. "Ticket", "ADR")` appeared twice — on the `name` parameter's description
and in the tool's own top-level description. On a request reading "We need to
start tracking architecture decision records", which never abbreviates, the
model named the created type "ADR" 3/3. The abbreviation appears nowhere else in
the prompt.
Located by prompt dump, not by inference: genericising the *guidance*'s worked
example changed nothing, deleting it changed nothing, and a dump found the only
occurrence in the schema. Both are now replaced with what the value IS ("in the
user's own words") rather than an example naming a type.
This is the mirror of what #1932 guards. That guard keeps eval prompts from
sharing text with guidance so a pass cannot prove memorisation; here a SCHEMA
supplied an incidental detail the model adopted as the answer to a question the
example was not addressing.
Verified: "Architecture Decision Record" 3/3 in both prod-paired arms, where
before the fix trimmed arms produced "ADR" 3/3. The #2119 snapshot gate caught
the change and its golden is updated — the diff is exactly the two intended
lines.
2. The guidance-trim recommendation is retracted, and skill_rules.rs is untouched
The earlier commit's KEEP/DROP rule was derived from arms pairing production
GUIDANCE with the corpus's HAND-AUTHORED tool schemas. They differ where it
mattered: the corpus declares enum values under `allowed_values`, production
under `coreValues` — declared "REQUIRED and must be non-empty when type=enum"
with the lowercase rule inline. So an arm that dropped ENUM_FORMAT and produced
an empty enum was measuring production prose against a schema missing what
production's has, and skill_pipeline.rs's doc comment (which says the schema
carries that rule) was right all along.
prod-paired-*.toml rebuild it properly, both halves from source: guidance from
skill_rules.rs, schemas from model_facing_tool_definitions() via the new
dump_tool_defs bin. The generator ABORTS if a DROP claim cannot be verified
against the real schema.
Result: the rule does not survive.
arm rules coreValues
prod-paired-current 9 {value,label} pairs — CORRECT
prod-paired-trimmed 5 {label} only, `value` MISSING
+ UNIQUE_FIELD_FLAGS 6 still missing
+ TITLE_TEMPLATE_PLACEHOLDERS 6 CORRECT
+ EDIT_DONT_RECREATE 6 CORRECT
+ RENAME_VS_RELABEL 6 CORRECT
Three unrelated rules each independently restore `value`; none mentions enums.
The one that does not is the longest of the four, so it is not mass either.
Same signature as the earlier `supersedes` isolation, where two unrelated rules
worked and the rule most directly about completeness did not. Neither semantics
nor shape explains which rule works, so "which rules to keep" is not answerable
by reading them. No trim applied.
core#2182 was also wrong and is closed by core#2183: its evidence came from
goldens/dev-unseen-schema.toml, which declares `value` as a bare string, so the
model was COMPLYING with the schema in front of it. Production already carried
the union.
Both errors are the same one — measuring a hand-authored fixture and attributing
the result to production. FINDINGS.toml sections 5A/6A/7A record it as a method
note, the six prod-guidance-* arms are marked SUPERSEDED in place rather than
deleted, and what still holds is listed separately from what does not.
Corpus regression check after the schema edit: dev-schema-creation,
dev-instance-creation, dev-status-change-enum all unchanged at 3 reps.
cargo test -p nodespace-agent: 522 passed, 0 failed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B8ywPs1H92aEKhXtCKK2au
Pragmatic Code Review — PR #2188Verdict: APPROVE (with one follow-up worth filing, detailed below)The production change is 17 lines, correct in direction, and well-evidenced. The bulk of the diff is measurement provenance and is defensible in this repo given established precedent. The one substantive finding below is a completeness gap in the fix, not a defect in the change — it does not block merge, but it should be tracked before this is treated as closed. SummaryNet positive, clearly. This PR does three things well that are rare in prompt-engineering work:
The FindingsImprovements1. This is the one finding worth acting on. The PR removes
And in guidance ( Why this matters for the claim as stated: Two readings, and they have different consequences:
Either way, the code comment and FINDINGS overstate the search. The comment is load-bearing documentation — it exists to stop a future contributor re-adding the example — so its factual claim should match the file it sits in. Concretely, I'd suggest:
2. §5A/6A/7A are inserted before §5/6/7 in file order, with a "CORRECTION (appended after sections 5-7 were written)" banner. That works when read top to bottom, but §5's own text ends with "Treat the Schema Creation trim as ready to apply and measure" — a live recommendation that §5A revokes. A reader who greps for "trim" or lands mid-file can act on the revoked line. Nit-adjacent but cheap to fix: prefix the §5, §6, §7 headers themselves with On the four questions raised in reviewRepo hygiene / scope — acceptable, and not a CLAUDE.md violation. On the specific rules: the The one real cost is that ~58 files land as inert data — nothing enumerates Retracted material — keeping it is the right call. "A retracted measurement is still evidence about method" is correct, and the METHOD NOTE ("measuring a hand-authored fixture and attributing the result to production is the recurring error in this spike") is the single most valuable paragraph in the file precisely because it generalises from two failures. Deleting the refuted arms would delete the evidence that the method note is earned. The only change needed is §5/§6 header marking, above. The production change itself — safe, and consistent with ADR-064. Three reasons it clears the ADR-038 Finding 2 concern:
This is also deletion, not substitution in ADR-064's sense, and the
One caveat: the doc comment says "TEMPORARY investigation aid". Per CLAUDE.md's stance on Python generators reading uncommitted Verification spot-checked
I did not re-run the model arms; taking the recorded 3-5 rep results and the main agent's Nitpicks
|
malibio
left a comment
There was a problem hiding this comment.
Automated pragmatic-code-review: APPROVE (posted as a comment — GitHub blocks self-approval). Full findings in the review comment above.
… in place
The reviewer verified something I asserted without checking: `'adr'` is NOT gone
from the prompt. It survives in three other model-facing descriptions inside the
same tool surface —
create_node's `node_type` (tools.rs:811)
create_schema's `relationships.targetType` (tools.rs:1194)
update_schema's `schema_id` (tools.rs:1217)
— plus skill_rules.rs:124's RELATIONSHIP_VS_FIELD example ("ADR supersedes adr").
All three tools.rs occurrences are inside prod-paired-current.toml's tool
surface, so the arm that measured "Architecture Decision Record" 3/3 did so with
the token present at least three times.
The code comment claimed "the only occurrence here" and FINDINGS §6A claimed the
abbreviation appeared "nowhere else in the prompt". Both were wrong, and the
comment is load-bearing — it exists to stop a contributor re-adding the example,
so it must not overstate what was verified.
What the measurement actually demonstrated is narrower and is now stated as
such: removing the token FROM THIS PARAMETER fixes the name. Not that the token
is absent.
The comment also now records why the three survivors are plausibly a different
case rather than an unfixed instance of the same one: each names an id that must
ALREADY exist and says so explicitly ("do NOT invent types that don't exist
yet"), so the example illustrates a format. This parameter names the thing being
CREATED, which is what made its example answerable. That distinction is
explicitly marked untested — it predicts the survivors are harmless and a
measurement could refute it.
Also addressed:
- FINDINGS §5 ended with "ready to apply and measure" while §5A revokes it 200
lines later. The six superseded ARM FILES were marked in place; the sections
were not. §5 now carries the revocation inline.
- Dropped "TEMPORARY" from dump_tool_defs.rs's doc comment. The generators
README makes it infrastructure for building fixtures from source.
Not addressed, deliberately: removing the three surviving `'adr'` examples. They
are a different shape of description, the distinction above is untested, and
changing model-facing text on an untested theory is what this branch spent its
length arguing against. Worth a follow-up issue that MEASURES the survivors
rather than assuming either way.
Comment-only in production code — verified no non-comment line changed in
tools.rs. Snapshot gate (#2119) passes unchanged, which is the check that would
catch any model-facing text moving. quality:fix clean, no unrelated reformatting.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B8ywPs1H92aEKhXtCKK2au
Review addressed —
|
| location | text |
|---|---|
tools.rs:811 |
create_node.node_type — (e.g. 'ticket', 'adr') |
tools.rs:1194 |
create_schema.relationships.targetType — (e.g., 'task', 'ticket', 'adr') |
tools.rs:1217 |
update_schema.schema_id — (e.g. 'ticket', 'adr') |
skill_rules.rs:124 |
RELATIONSHIP_VS_FIELD — ADR supersedes adr |
All three tools.rs occurrences sit inside prod-paired-current.toml's tool surface, so the 3/3 measurement was taken with the token present at least three times. The code comment and FINDINGS §6A both overstated this, and the comment is load-bearing — it exists to stop a contributor re-adding the example.
Both now state the narrower thing actually demonstrated: removing the token from THIS parameter fixes the name. Not that the token is absent.
Your read on why the survivors differ is recorded, and marked untested: each names an id that must ALREADY exist and says so ("do NOT invent types that don't exist yet"), so the example illustrates a format. This parameter names the thing being CREATED, which is what made its example answerable. Written into the comment so it survives without the measurement — and flagged as a prediction that could be refuted.
FINDINGS §5 revocation. You were right that marking the six arm files but not the sections left §5 ending on "ready to apply and measure". It now carries the revocation inline pointing at §5A.
Dropped "TEMPORARY" from dump_tool_defs.rs.
⏭️ Not addressed, deliberately
Removing the three surviving 'adr' examples. They are a different shape of description, the distinction is untested, and changing model-facing text on an untested theory is precisely what this branch spent its length arguing against. Worth a follow-up that measures the survivors rather than assuming in either direction.
Verification
- Comment-only in production code — confirmed no non-comment line changed in
tools.rs - Snapshot gate (Prompt-assembly snapshot gate: assert production reproduces the tuned golden prompt (zero model calls) #2119) passes unchanged — the check that would catch any model-facing text moving
quality:fixclean, no unrelated reformatting- Pre-push gate passed in full
Re-review
NO RE-REVIEW NEEDED. All changes are comments and documentation; the snapshot gate confirms production output is byte-identical. No logic, no model-facing text, no new functionality.
What this changes in production
Two description strings in
create_schema(packages/agent/src/local_agent/tools.rs). That is the entire production change. No wire format, no param struct, no handler, noskill_rules.rs.…and the same example removed from the tool's own top-level description.
Why: on a request reading "We need to start tracking architecture decision records" — which never abbreviates — the model named the created type "ADR" 3/3. The abbreviation appears nowhere else in the prompt.
Located by prompt dump, not inference. Genericising the guidance's worked example changed nothing; deleting it changed nothing; a
NODESPACE_PROMPT_DUMPcapture found the only occurrence in the schema.This is the mirror of what #1932 guards. That guard keeps eval prompts from sharing text with guidance so a pass cannot prove memorisation. Here a schema supplied an incidental detail the model adopted as the answer to a question the example was not addressing.
Verified: "Architecture Decision Record" 3/3 after the fix, where trimmed arms previously produced "ADR" 3/3.
The larger part: measurement evidence, including two retractions
The fidelity gap that started this
The golden corpus hand-authors 1–2 tools per turn. Production Stage 2 sends the union of the retrieved candidates' whitelists — 9 tools with full parameter schemas — because the read tools appear in 5–6 of the 8 seeded skills.
dev-status-change-enumturn 1So the corpus measured 11% of what production sends, and the missing 15,373 chars were entirely tool declarations. Handing the model one tool and asking it to call that tool does not test tool selection — no wrong answer is available.
Rebuilt at fidelity (
production-baseline.toml, 9 tools / 3 candidates / 22,064 chars) the case still passes 5/5, and its filter is better than the 1-tool arm's: the real schema teaches the structured form where the hand-authored one produced a loose{"status":"in_dev"}.What the arms established
load_proceduretool was offered in three arms and called zero times. Lazy loading was not needed.K=1breaks multi-turn: scoping to one skill's whitelist removes tools later turns need.coreValues.value; the one that fails is the longest of the four.create_schema's own description back as an argument value. Off-topic prose is worse than an empty slot.Two retractions, recorded rather than deleted
No trim was applied to
skill_rules.rs. The KEEP/DROP rule I derived held on the corpus's own guidance but does not survive proper pairing against production's, so it does not license editing source.core#2182 was wrong and is closed by #2183. Its evidence came from a fixture declaring
valueas a bare string, so the model was complying with the schema in front of it.Both errors are the same one: measuring a hand-authored fixture and attributing the result to production.
FINDINGS.tomlsections 5A/6A/7A record it as a method note, the six superseded arms are marked in place, and what still holds is listed separately from what does not.build_prod_paired.pyaborts if a DROP claim cannot be verified against the real schema, so the class of error cannot recur silently.Verification
test:allcargo test -p nodespace-agent458d5de2Agent matrix: not used as a gate here, and deliberately. Its scenarios are equipment/albums/venues — the domain #1977 exists to retarget — so it cannot speak to a dev-domain schema-description change. A partial control run on plain main reproduced scenario 4's failure with the identical message, and given this eval's own noise band (identical code has scored 6/7/7) no regression claim is made in either direction.
For the reviewer
A real call I'd rather surface than bury: this adds ~45 ablation arms and 13 generator scripts, and two of my conclusions were retracted mid-spike. Whether that volume of negative-result material belongs in the repo — or would be better as an issue with the arms dropped — is a judgement worth making explicitly.
The
dump_tool_defsbin is small and load-bearing for building future arms from source rather than transcription.Reproduce any arm, no daemon or DB:
Related: #2122 (corpus), #2119 (snapshot gate), #1977 (eval retarget, still open), #1932 (contamination guard).