Repository navigation
Resolve whether skill instructions delivered as a tool result suppress action #1814
Description
Overview
An unresolved measurement that gates the ADR-038 routing work, and that may indicate a live defect in shipped behavior. Determine whether skill instructions delivered as a tool result suppress tool-calling, and if so, by what mechanism.
Run this staged. Stage 1 tests one variable and may close the issue outright. Stage 2 is explicitly conditional — do not run it by default.
Problem Statement
search_skills returns each matched skill's full instruction subtree as an instructions field in the tool result (packages/core/src/ops/skill_ops.rs:220, :229, flattening children at :79). This is shipped behavior.
Early measurement found action collapsing from 100% to 22% when instructions arrived this way, dose-dependent across three skills, with the model producing permission-seeking prose instead of a tool call. A later run contradicted it: a roughly 700-character instruction returned the same way still acted 100%. A size sweep (0 / 700 / 1500 / 2400 / 3200 / 4500 characters) came out non-monotonic — 2400 failed, 3200 passed, 4500 failed.
Neither "delivery channel" nor "payload size" explains that pattern. A third variable is confounded, or the harness is wrong.
This is potentially a live defect, not preparatory research. ADR-038 requires the full-subtree return and it is built — so if suppression is real, production is affected now, and the ADR provision needs retracting rather than implementing. It is currently marked Provisional pending this result.
Stage 1 — content shape at fixed length (run this first)
ADR-064 rule 4 established that the model acts on results and narrates or mis-executes plans. Skill instructions are a plan delivered as a tool result.
That predicts suppression tracks how procedural the payload is, not how large — and it predicts the non-monotonic sweep, because longer instructions are not reliably more procedural. This is falsifiable in a single pass.
Two arms at fixed length (~1500 characters, mid-range of the sweep):
- Procedural — "First search for X, then update it with…"
- Declarative — "X is at node abc-123, status pending."
Both arms must carry the same information, differing only in framing. If the declarative arm is also shorter or more informative, the test has been confounded back into a size test.
Gate: if Stage 1 reproduces the effect and accounts for the sweep's pattern, the mechanism is identified and the issue closes. Size, position, and imperative-directive arms are then irrelevant and must not be run.
Stage 2 — conditional, only on a null or partial Stage 1 result
Isolate one at a time: payload size, position in context, presence of an imperative directive. Each with the same pre-registered decision rule as Stage 1.
Where to run it
Stage 1 in nodespace-nlp-experiment (separate repo). Content shape is a model-behavior question and should be isolated from N_CTX_MINIMUM, the ReAct loop, prompt assembly, and seeded content — each of which has already produced a confound in this workstream. Fast iteration matters, since landing a clean procedural/declarative contrast may take several passes.
Confirmation in nodespace-core, before any production change. A positive Stage 1 result does not license editing skill_ops.rs. The production path adds the resident prompt, real tool schemas, and multi-turn state, any of which could mask or amplify the effect. Confirm against the real LocalAgentLoop → search_skills path first.
That ordering exists because generalizing from one channel to another is precisely the error ADR-038 line 81 made — its evidence measured prompt-injected instructions while the implementation returns them as a tool result.
Neither harness is committed. The core-side confirmation is uncommitted scratch. Because that makes it unreproducible for the second developer, record the procedure and the raw numbers in this issue thread so the result stays auditable even though the code is not kept.
Acceptance Criteria
- Harness validated against a known-good control before any arm runs — one payload known to produce 100% action and one known to produce 0%. Both must land where expected or the run is discarded. Six harness bugs during the original spike each produced a clean, plausible number and each reversed a conclusion
- The decision rule is pre-registered: arm sizes and accept/reject thresholds fixed and recorded in this thread before any arm runs. Minimum n=30 per arm. The effect is called real only if arms separate by ≥25 points — the magnitude the original 100%→22% observation implies. A smaller separation is reported as "no effect at measurable resolution," not as a small effect
- Stage 1 arms carry identical information and differ only in framing
- The 2400-fail / 3200-pass inversion is either reproduced or attributed to harness noise. An unexplained non-monotonicity means the experiment is not yet measuring one variable
- Raw model output retained for every scored generation
- Stage 2 runs only if Stage 1 is null or partial, and its scope is recorded before it starts
- Positive results confirmed against the real
LocalAgentLooppath before any production change - Procedure and numbers recorded in this thread, since the harness is not committed
- The experiment concludes with an explicit verdict on ADR-038 line 81's Provisional marker: confirmed, retracted, or still unresolved. "Still unresolved" is a valid outcome and closes this issue — it does not license running more arms without a new hypothesis
Evidence
- 100% → 22% initial measurement across three skills, dose-dependent
- 100% at ~700 characters through the same channel
- Non-monotonic size sweep (0/700/1500/2400/3200/4500)
packages/core/src/ops/skill_ops.rs:220-229— full subtree already returned- ADR-038's supporting evidence measured instructions rendered into the prompt, a different channel from the shipped one
Architecture Impact
Gates #1815 (ADR-038 Stage-1/Stage-2 gates), because Stage-2's candidate-delivery mechanism is exactly the question here.
Reference harness at ~/nodespace/spike-1802-harness/ — its README documents known defects, including a Metal memory leak that caps reliable runs at n=3 per cell in a single process. That cap conflicts with the n≥30 requirement above: run arms across separate processes, or fix the leak first.
Activity
malibio commentedon Jul 30, 2026
Stage 1 pre-registration — recorded before any arm was run
Harness:
nodespace-nlp-experiment, newinstruction-shapeexperiment (src/instruction_shape.rs). Model: Gemma 4 E4B Q4_K_M (ADR-056 locked native model), temp 0.1 / top_p 0.9 / min_p 0.05, freshLlamaContextper generation so no KV state carries between trials. Reusesprompt_arch_bench's existing render/inference/parse path, so numbers are comparable with the probe D and E measurements cited in this issue.Scenario
Status change on a node that already exists.
search_skillshas already been called; its result is the variable under test. Six request phrasings, each with its own resolved node.Schema creation — where the 100%→22% collapse was originally measured — was rejected as the Stage 1 scenario. A declarative framing has to be expressible for the contrast to exist at all: "the record is node X, its status is Y" is a fact, but "create a schema for a new type" has no resolved state to hand back, only a procedure. Probe D therefore could not have separated shape from size, whatever it measured.
Conditions (fixed payload length, ~1500 chars, mid-range of the non-monotonic sweep)
compactinstructionsfieldproceduraldeclarativewithheldEqualisation, enforced as a hard gate before the model loads
Both arms render from one fact table (id, title, node_type, status field, current value, target value) and pad from one filler pool to the same target.
assert_arms_comparable()aborts the run unless, for every case:Padding appends whole sentences and never truncates mid-word. This is deliberate: the original sweep's
instruction_of_sizerepeated one filler paragraph and thentruncated mid-sentence, so its 2400 and 3200 points differed in repetition count and cut position as well as length. That is a candidate explanation for the non-monotonicity independent of any model behaviour.Six unit tests cover the gate.
NODESPACE_ARMS_ONLY=1prints the built payloads; they were read before running, not just asserted on.Scoring
Scored on the narrowest observable step the variable affects: did the generation emit a tool call that advances the turn (
update_node, or asearch_nodesthat legitimately precedes it)? Not a multi-step chain that can fail upstream.pcounts generations emitting no tool call at all — production's failure shape. Degenerate empty generations are excluded from the denominator and reported separately ase; E4B emits them intermittently and production documents the same behaviour.Raw model output is retained for every scored generation via
NODESPACE_BENCH_TRACE.Decision rule — fixed before any arm ran
--seed-basevalues keep separate processes from re-running the same trajectories.compact≥ 90% andwithheld≤ 25%. If either misses, the run is discarded and the harness fixed before any arm runs — a probe whose positive control fails cannot detect action, and one whose negative control acts anyway cannot detect suppression.proceduralanddeclarativeseparate by ≥ 25 points, the magnitude the original 100%→22% observation implies.procedural<declarative. A ≥25-point separation in the opposite direction falsifies rule 4 as the mechanism here and is reported as such, not as a partial confirmation.Gate on Stage 2
Per this issue: if Stage 1 reproduces the effect and accounts for the sweep's pattern, the mechanism is identified and the issue closes. Size, position, and imperative-directive arms are then not run.
malibio commentedon Jul 30, 2026
Stage 1 results
Harness:
nodespace-nlp-experiment, newinstruction-shapeexperiment (src/instruction_shape.rs, uncommitted per this issue's instructions — procedure and numbers recorded here instead). Model: Gemma 4 E4B Q4_K_M, temp 0.1/top_p 0.9/min_p 0.05, freshLlamaContextper generation. Scenario, conditions, equalisation gate, and decision rule as pre-registered in the prior comment.Harness validation (required before any arm)
A harness bug surfaced immediately: three of the six original request phrasings were bare statements of fact ("The Kessler order shipped", "The Voss renewal came back signed"). These produced an empty generation on 100% of trials, deterministically per case — not intermittency, a dead prompt shape. E4B ends the turn immediately on input it reads as not requiring a response. That would have silently halved the sample in a case-correlated way. Fixed by rephrasing all six as imperatives ("Mark the Kessler order as shipped"), same resolved nodes and facts, nothing about the variable under test changed. Empty rate went 46% → 3%.
Control gate, n=36 each, separate process:
Both landed exactly where predicted. The probe can detect action and can detect suppression.
Registered arms, n=36 each, separate processes
Separation: 0 points against a pre-registered ≥25 threshold. Null result — content shape does not suppress action in this design.
Not a harness artifact: the two arms produced materially different behaviour despite identical action rates. Procedural induced a redundant re-resolution step (
search_nodesbeforeupdate_node, re-deriving an id the payload already stated) in 36% of trials (13/36), deterministically per case (6/6 on two cases, 0/6 on three, 1/6 on one). Declarative went straight toupdate_nodein 100% of trials. Everyupdate_nodecall across both arms used the correct id (59/59). So shape didn't move whether the model acted, only whether it took an unnecessary extra step first.Accounting for the 2400-fail/3200-pass non-monotonicity (required by AC)
Mechanically:
instruction_of_size(reference harness) builds every point by repeating one filler paragraph and thenString::truncate-ing mid-sentence. Reproducing it, 700/1500/2400/3200/4500 chars correspond to 1.26/3.13/5.23/7.10/10.14 repetitions, each ending at a different word. The sweep was never a one-variable size test — length, repetition count, and cut position all varied together.Isolating each factor at n=36:
Two things resolve here:
create_nodeguidance for a new instance, returned to a request to update an existing record. Raw output from the collapsing cells: "I need more information to mark the Kessler order as shipped. Could you please provide the order ID?" — the model is reading guidance for the wrong operation and asking for information it already has in the conversation, because the payload told it a different (wrong) procedure for obtaining it. Mismatched-topic guidance alone gets to 81%; the full replica (mismatch + repetition + mid-word cut) gets to 3%, so the effects compound rather than any single one dominating.Verdict
search_skillsreturning a skill whose instructions describe the wrong operation drives the model into asking for information it already has, or into following the wrong procedure. This is a retrieval-precision problem (ADR-064 rule 3 territory — skill descriptions as the retrieval index), not a channel problem (rule 4).search_skillsresult, not moving instructions to a different channel.LocalAgentLoop→search_skillspath innodespace-core, since a positive result there is what actually gates any change toskill_ops.rsor the retrieval/confidence logic per this issue's "Where to run it" section. That confirmation has not been run yet.Raw model output retained for every scored generation across all 9 conditions (controls, both arms, and 6 diagnostics) via
NODESPACE_BENCH_TRACE.malibio commentedon Jul 30, 2026
Core-side confirmation — pre-registered before any run
Per this issue's "Where to run it" section: Stage 1 identified a mechanism (topical mismatch between a returned skill and the request) rather than confirming the original hypothesis (payload shape). This run confirms that mechanism against the real
LocalAgentLoop→search_skillspath — the thing that actually gates a change toskill_ops.rs. It is not a fresh exploration of the resident prompt, context block, or retrieval rate; those are #1813/#1817. Scope is bounded to one claim: does a genuinely wrong skill match degrade action in the real loop the way it did in the Stage 1 harness.What's real, what's substituted
Harness pattern follows the reference
e2e_prompting_stack.rsdesign (uncommitted per this issue's instructions, same as before): the actualnodespace_agent::local_agent::agent_loop::LocalAgentLoop::run_turndrives the turn — duplicate-write guard,contains_action_claim, iteration cap, all real. Two substitutions only:AgentToolExecutor→ an in-memory stub graph, so the mutation is observable without a daemon or DB.LlamaChatInferenceEngineloading Gemma 4 E4B in-process (production's real inference path, no server).search_skills's result is not fabricated text — it returns the actual seeded skill content read frompackages/agent/src/skill_pipeline.rs:descriptionandmarkdown_contentexactly asgraph_editing_guidance()produces them (skill_pipeline.rs:119-142), tool_whitelist["update_node", "update_task_status", "get_node", "search_nodes", "search_semantic"].bulk_import_guidance()(skill_pipeline.rs:185-201), description "Import documents and create node hierarchies from markdown…", tool_whitelist["create_nodes_from_markdown"]only. This is the specific skill named in the earlier retrieval-wording finding (35%→75% rank-1) as a semantic attractor that captured unrelated intents — so the mismatch under test is a real, previously-observed failure mode, not an invented one.Scenario
Same request class as Stage 1: ask the agent to change an existing node's status ("Mark the Kessler order as shipped").
search_skillshas already been called and returned one of the two skills above; its result is the input under test. The registeredupdate_nodetool schema (real, from the agent's tool registry) is offered alongside whatever the matched skill'stool_whitelistexposes — for the Bulk Import arm this meansupdate_nodeis not in the whitelist the skill claims, mirroring what a real wrong-match would hand the model.Mismatch is seeded synthetically —
search_skills's result is forced rather than waiting for real retrieval to misfire — per this issue's guidance: the mechanism is already established from Stage 1, so the open question is only "does it hold in the real loop," not "how often does it occur." Occurrence rate is explicitly out of scope here (it's what the retrieval-wording work already measures).Scoring
Outcome-scored on the stub graph, same principle as Stage 1: did
update_nodeland on the correct id withstatus → shipped? Not tool-name matching, not turn-by-turn parsing — the actual mutation. Raw transcripts retained for every trial.Decision rule — fixed before any trial runs
Explicitly out of scope for this run
No resident-prompt ablation, no context-block variation, no retrieval-rate measurement, no exploration of other skills beyond the one confirmed pairing (Graph Editing correct / Bulk Import mismatch). A result that surfaces something outside this claim becomes a new issue, not additional scope here.
malibio commentedon Jul 30, 2026
Core-side confirmation results
Harness follows the pre-registration above: real
LocalAgentLoop::run_turn(duplicate-write guard, iteration cap, all real), realLlamaChatInferenceEngineloading Gemma 4 E4B in-process, real seeded skill content (graph_editing_guidance()/bulk_import_guidance()transcribed fromskill_pipeline.rs), realupdate_nodetool schema (tools.rs::def_update_node), realsearch_skillswire shape ({"query", "matches"}, matchingexec_search_skills's rename offind_skills'sskillskey). Uncommitted per this issue's instructions.Two harness bugs surfaced building this, both before any scored run — consistent with the "six harness bugs, all clean-looking" pattern from the original spike:
AgentSession::dynamic_contextis silently ignored wheneversystem_prompt_overrideis set (agent_loop.rs:437-444— override bypasses thePromptAssemblerentirely, and only the assembler interpolatesdynamic_context). A first control run scored 0/6 for a reason unrelated to the variable under test: the model had no workspace context and correctly asked for a node id.system_prompt_overridealso bypassesagent_guidance::TOOL_STRATEGY_RULES— the real resident rule set that includes "ALWAYS search_nodes first before update_node" and the read-then-write completion rule. Without it (bare identity string only), the model — correctly, given what it was shown — had no instruction telling it a search tool existed for this purpose. ImportingTOOL_STRATEGY_RULESdirectly (it'spub) fixed this.Control gate (n=36 total, two batches)
≥90% threshold: met.
Mismatch arm (n=36 total, two batches)
What actually happened
In every trial, both arms, the model called
search_nodes→update_nodedirectly and never calledsearch_skillsat all. Confirmed via full tool-call/args/result dump on sampled trials — not an artifact of my scoring, the turn genuinely never touched skill discovery.Root cause:
agent_guidance.rs:48,TOOL_STRATEGY_RULES— "ALWAYS search_nodes first before update_node or update_task_status — even if a node ID appeared earlier in the conversation. Never skip the search step." — is resident prose that hardcodes the routing for this operation class directly, independent of and bypassingsearch_skills. CompareSCHEMA_CREATION_RULES/TOOL_STRATEGY_RULES's create_node line: "call search_skills(query) THEN immediately call create_node in the SAME turn" — create_node's routing genuinely depends onsearch_skills; update_node's does not.This means: the mismatched skill payload (Bulk Import's instructions, its
create_nodes_from_markdown-only whitelist) never reached the model's context in any trial of either arm. The 100%/100% result is not evidence that mismatch is harmless — it's evidence thatupdate_nodedoesn't route throughsearch_skillsat all, so this scenario cannot exercise the claim under test ("does a wrong skill match degrade action in the real loop").One more thing worth naming:
Graph Editing's own skill instructions already say "FIND THEN UPDATE: ... call search_semantic or search_nodes first" — the same content asTOOL_STRATEGY_RULES:48, in a second channel. For update-type operations, the same routing rule currently lives in both resident prose (line 48) and skill instructions, and the resident copy is the one the model actually acts on here. That's a rule-4/rule-5 boundary question independent of #1814's core claim — resident prose is supposed to own identity/policy only, not operation-routing detail that's also expressed in a skill.Where this leaves the pre-registered decision rule
The rule as written doesn't have a clean branch for "the harness never exercised the payload." Neither "confirmed" (≥25pt separation in the failing direction) nor the not-confirmed conditions I listed anticipated this — I didn't pre-register what to do if the variable under test never reaches the model. Flagging that gap rather than picking a reading unilaterally.
Question for you
Two ways I see to close this, and I don't want to choose without checking:
update_nodespecifically — not a refutation, not a confirmation, a scope miss caused by that operation's routing being hardcoded outside the skill system. ADR-038 line 81 retraction rests on Stage 1 alone; core-side confirmation neither supports nor contradicts it for update-type operations.create_nodescenario.create_node's routing genuinely depends onsearch_skillsperTOOL_STRATEGY_RULES("call search_skills THEN immediately call create_node") — same operation class Stage 1's original 100%→22% measurement used (schema/instance creation). Re-running the confirmation there guarantees the mismatched payload actually reaches the model, which this run did not.I'd lean toward (2) since it's what the pre-registration was trying to test and (1) leaves the core-side confirmation AC technically unmet rather than answered — but this is your call, not mine to make unilaterally given the scope discipline set for this issue.
malibio commentedon Jul 30, 2026
Guidance on closing out the confirmation run
Read the full comment above, not just the summary. Two things changed since the fork was framed, and they affect the recommendation.
The finding here is not "inconclusive" — it's a second, independent discovery
This run surfaced the same class of problem Stage 1 found, one layer up. Stage 1 found that a mismatched skill's instructions degrade action once they reach the model. This run found that
update_nodenever reaches the skill system at all —TOOL_STRATEGY_RULES:48hardcodes its routing in resident prose, bypassingsearch_skillsunconditionally. And you found the same routing rule duplicated inGraph Editing's own skill instructions, with the resident copy winning — the exactcoreValues/core_valuespattern from #1809, in a different tool.So don't write this up as "the confirmation was inconclusive." Write it up as two things: (a) the core-side confirmation for
update_nodespecifically is inconclusive, for the reason given, and (b) a new, independently valid finding — resident prose hardcodes routing for at least one operation class, duplicating content that also exists as a skill, and the duplicate is dead code the model never reaches. That second part stands on its own regardless of what a create_node run shows. Don't let it get compressed into a footnote.Proceed with (2) — the create_node run — but scope it tighter than last time
Agreed with your lean, for the reason you gave: the pre-registration was trying to test whether a mismatched skill suppresses action once reached, and
update_nodestructurally couldn't test that.create_nodecan, and it's the same operation class Stage 1's original 100%→22% measurement used, so a positive or negative result there closes the loop back to where this issue started.Before running it:
search_skillsis actually being called and the mismatched payload actually lands in context — the same class of check that caught both bugs this time. Don't wait for a suspicious 100%/100% to go looking.search_skillsmismatch to be reachable in practice, which would matter a great deal to Implement ADR-038 Stage-1 and Stage-2 gates; shipped loop is the rejected single-turn pull #1815's design.On closing #1814 itself — updated
The
update_noderouting bypass has its own issue now: #1820, "TOOL_STRATEGY_RULES conflates safety policy with tool routing." Filed with the evidence from this run already in it (the search_nodes-before-update_node hardcode, and the Graph Editing skill duplication it shadows). Cross-reference #1820 rather than writing that finding up again here.#1820 also changes the precondition for the create_node run below.
TOOL_STRATEGY_RULESlines 41/55 ("call search_skills THEN immediately call create_node") are the same category of prose-routing that turned out to bypass search_skills for update_node — a doc claim, not yet independently verified at the trial level. Given this run already found two silent-drop bugs (dynamic_context, then TOOL_STRATEGY_RULES itself) before scoring anything real, don't take "create_node's routing depends on search_skills" on faith either.Before scoring the create_node arm: verify on a handful of sampled trials that search_skills is actually being called and the mismatched payload actually lands in context — the same check that caught both bugs this time, done proactively instead of after a suspicious result.
Once the create_node run lands (confirm, refute, or "didn't reach the model" per above), close with these findings, not one bundled conclusion:
malibio commentedon Jul 30, 2026
create_node confirmation — pre-registered before any run
Per your last comment:
update_node's routing is hardcoded inTOOL_STRATEGY_RULES:48(now #1820), so that confirmation could not exercise the claim under test.create_node's routing is different peragent_guidance.rs:41— "For create_node (adding an instance): call search_skills(query) THEN immediately call create_node in the SAME turn" — genuinely gated onsearch_skills, and it's the same operation class Stage 1's original 100%→22% measurement used (schema/instance creation). This re-runs the confirmation there.New precondition, added per your instruction
Before scoring any batch: verify on sampled trials that
search_skillsis actually called and the arm's payload actually lands in the model's context (visible intool_calls_made'ssearch_skillsentry and in what the next tool call's reasoning reflects). This is the same class of check that caught both silent-drop bugs in theupdate_noderun, done proactively now rather than after a suspicious result.New decision-rule branch, added now per your instruction, applies to this and any future confirmation run under this issue: if the arm's payload is not observably present in the model's context on inspected trials, the run is invalid and discarded before scoring — same standing as a failed control gate. If this happens for
create_nodetoo, that is not a third inconclusive result to shrug at — it is itself a finding (routing may be too hardcoded in prose forsearch_skillsmismatch to be reachable in practice at all), gets a comment on #1820, and #1814 closes citing #1820 rather than attempting a fourth operation class.Scenario
Real seeded "Node Creation" skill (
skill_pipeline.rs:283-320) as the correct-match control — description: "Create new nodes, records, entries, or instances of any type... Use when user wants to add, create, or insert a new item, record, entry, or example of an existing type,"tool_whitelist: ["create_node", "search_semantic", "search_nodes", "get_node"], guidance opens with "⚡ IMMEDIATE ACTION REQUIRED: Call create_node NOW..." — an unusually forceful positive control, good for isolating whether mismatch specifically (not weak guidance generally) is the variable moving the number.Mismatch arm: real seeded "Bulk Import" skill again (
tool_whitelist: ["create_nodes_from_markdown"]) — same skill as theupdate_noderun, for consistency across this issue's confirmations, and because it's a real, narrowly-scoped, genuinely-wrong skill for an instance-creation request (a single freeform document-import tool vs. a typed single-record creation).Request: "Add a freelance gig with client Hollis, rate 900, status booked" against a seeded
freelance_gigschema (client, rate, status fields) — mirrors Stage 1's harness-validated request shape (packages/agent's reference harness used the identical phrasing for itscreate_schemacontrol), reused here for acreate_nodeinstance rather than a new schema.WORKSPACE_CONTEXTextended to listfreelance_giginRELEVANT ENTITY TYPESso the type is resolvable, matching how theupdate_noderun'spurchase_orderentry was required.Scoring
Outcome-scored on the stub graph: did
create_nodeland withnode_type="freelance_gig"and the three properties correctly set? Not tool-name matching — the actual mutation, same principle as both prior confirmations in this issue.Decision rule
search_skillsfires and the arm's real payload is visible in context.Out of scope, same as before: no resident-prompt ablation beyond what's needed to reach the model's actual behavior, no retrieval-rate measurement, no exploration of other skills.
malibio commentedon Jul 30, 2026
create_node confirmation results
Precondition check (run before any scored batch, per the pre-registration):
search_skillsreached the model 5/5 trials in both arms. Proceeded to scored batches.Control — real "Node Creation" skill (n=38, two batches)
Mismatch — real "Bulk Import" skill (n=30)
100-point separation, well past the pre-registered ≥25pt threshold, in the failing direction predicted.
The failure mode
Not empty output, not refusal, not a wrong tool. All 30 mismatch trials are identical: the model called
search_skills(received Bulk Import's payload), then calledcreate_nodewith the correctnode_type: "freelance_gig", butcontent: "Hollis freelance gig booking"andproperties: {}— every field the user supplied (client, rate, status) silently dropped. The turn then reports success: "Done — node creation completed successfully."This tracks Bulk Import's guidance shape exactly, not Node Creation's: Bulk Import says "pass the markdown content directly... default node_type to text," nothing about a properties map; Node Creation says "REQUIRED FIELDS: ... MUST be included in the properties map." The model followed the wrong skill's structure (content-only, no structured fields) applied to the right tool.
This is a different failure signature than Stage 1's ("I need more information, could you provide the ID?") and different again from the
update_noderun's non-finding. It's silent data loss reported as success — arguably a worse failure mode than either, since nothing in the transcript signals anything went wrong.Some caveats to weigh
Closing verdict for #1814
Three separate findings, not one bundled conclusion, per your instruction:
1. Stage 1's mechanism — stands, harness-level. Topical mismatch between a returned skill and the request suppresses/corrupts action. Isolated harness, n=36 per arm, controls validated, 2400/3200 non-monotonicity resolved as sampling noise at the original n=3.
2. Core-side confirmation for
create_node— CONFIRMED. RealLocalAgentLoop, real seeded skill content, real tool schemas, real wire shape. Control 38/38 (100%), mismatch 0/30 (0%), 100-point separation. Precondition verified before scoring (search_skills fires reliably for this operation, unlikeupdate_node). Failure mode: silent field-data loss reported as success, not the narration/refusal Stage 1 measured — the mechanism generalizes across the delivery channel but the symptom is operation-dependent.3.
update_nodeconfirmation — inconclusive, cross-referenced to #1820, not restated here. That operation's routing is hardcoded in resident prose (TOOL_STRATEGY_RULES:48), bypassingsearch_skillsentirely — a separate, now-filed problem.ADR-038 line 81 Provisional marker
Retracted, and now confirmed at both the harness and production-loop level for at least one real operation class (
create_node). The evidence does not show that returning skill instructions as a tool result per se suppresses action — the channel itself is not the problem (confirmed twice: Stage 1's shape arms, and this run's clean 100% control through the same channel). What's confirmed harmful is returning a mismatched skill's instructions — verified now through production's actualsearch_skills→LocalAgentLooppath, not just an isolated harness. The fix implied is retrieval precision (ADR-064 rule 3), not delivery channel (rule 4) — consistent with #1820's independent finding that resident prose currently duplicates and sometimes preempts routing that should live in skill instructions.Recorded per this issue's requirement that the procedure and numbers live in this thread since neither harness is committed. Both
confirmation_1814_skill_mismatch.rsandconfirmation_1814_create_node_mismatch.rsremain uncommitted scratch in the worktree unless you want them kept.