D/meta prompts abc paths d5bf92 - #196
Open
OpenCnid wants to merge 18 commits into
Open
Conversation
…asking for one big turn Rule 24 names two acts that falsify what Trellis is. One of them was in the kernel prompt: ITERATION BUDGET told the worker to combine as many protocol steps as possible into a single repl block. FEATURE_LIST row 2.6 carried it as wrong shape. Those bytes now route the same scarcity premise the other way -- each turn carries a step, reads the slices that step needs, and the answer comes together across turns. The warning against spending a turn on tiny exploratory prints was sound and is kept. No numeric bound moved: row 2.7's exhaustion defect still gates widening the turn budget. Both composed-prompt sha256 pins moved together and wittingly. Each was watched failing twice before it was recomputed -- once against the rewritten prompt with the stale constant in place, once on a planted one-byte perturbation of the constant itself -- so the comparison was seen discriminating in both directions rather than merely reading its own value. rlms already reserves one description line per injected surface and accepts a tool entry carrying it; Trellis passed bare values, so all eleven rendered as "A custom <Type> value" at character 1,335 of the 2,116-character protocol prompt, ahead of every Trellis directive. trellis_contribution composes that line from a surface's own registered descriptor and its guard-derived expectations, joins pieces with the empty string so the frame contributes no prose of its own, refuses a brace, a newline or an empty line, and refuses the whole composition over a 2,000-character budget with per-surface sizes ranked largest first. The budget is the treatment HARNESS_SELF_MODEL section 5 asked for and had never been given: a bound that raises rather than one held by authorial discipline. Descriptor coverage goes from 1 of 9 injected surfaces to 8. The ninth, UPSUM_BUDGET, is a bare int rather than a surface and is declined on purpose. Two bounds the engine enforces and no prompt taught now reach the model: the per-run retrieval budget, whose number is read from the attribute the refusal compares against, and the closure that a run may cite only addresses whose bytes it retrieved this run. In repl_sandbox the grant list becomes a read. BROKER_CAPABILITIES is the registry rather than an authored roster, ten capabilities bind their descriptors beside the guards that refuse, and eight sentences that had been hand-authored at a distance from their predicates are retired -- one of them had four copies. The kinds sentence is composed from the kind table, each signature's required set is read from the argument table, and the retry account is keyed off the error classes' own attributes, so a model can now tell a retryable refusal from one that ends the session. The LM pair keeps empty expectations rather than gaining phrases authored away from lm_handler's guards, because capabilities ships inside the guest image and importing the handler would carry the DLP patterns in with it. Nothing describes dlp, and a test asserts that nothing does. Reachability is unchanged and unclaimed. repl_sandbox still has no non-test caller; FEATURE_LIST row 2.4 stands. Whether a model behaves differently for reading any of this is a separate paid question that nothing here measures. Three records that this commit falsified are repaired in it: RESPONSE_ARTIFACT section 3.1's second bullet, FEATURE_LIST row 2.6, and the digests quoted in RLM_HARNESS_SCAFFOLDING section 8.4, which are marked as history rather than live values. The coverage diagnostic and its drill both derived their roster from the injection seam but registered descriptors from a hand-kept import list, so both under-reported. The list is widened and named as the one list here that is not derived. test_surfaces' honest-scope pin asserted that most surfaces are undescribed; that claim is now false, and it is retired rather than patched, replaced by the ladder it became -- a name is registered, a registered descriptor may carry a contribution, and a contribution reaches a model only once it is wired. Verified: 1,424 vitest tests, 1,151 repl_sandbox tests, and test:modules, test:surfaces, test:textedit, test:answer-channel, test:rlm-workspace, test:rlm-mcp and check:repo-surface each run green through their npm wrappers. Every new check was watched failing against planted breakage and restored.
The description slot rlms reserves is one line per surface, and the contribution frame's own header says what that means: an orienting line, not an account. The first two surfaces wired into it were written as accounts -- trellis_postgres at 620 characters and trellis_neo4j at 709 -- which put the five composed contributions at 1,942 of the 2,000 character budget. At 97 percent the next surface would have been refused, and the refusal would have been correct: the budget was not the problem. What came out is elaboration a refusal message already delivers at the moment it matters. The repeat-fetch phrase enumerated which of the three retrieval surfaces refuses and on what; the refusal itself names that. The citation-closure phrase spelled out the read-then-confirm-then-cite sequence; the reference-versus-bytes distinction is the part that changes what the model does and it stays. Existence and entailment lost their restatements of what "refuses" means. What stays is every bound that makes a refusal predictable before it happens: the run's retrieval budget with its number still read from the attribute the predicate compares against, the closure that a run may cite only addresses whose bytes it retrieved this run, the hex shape of a sourceNodeIds element, and batch atomicity. No guard-owned phrase was moved to a different owner and no editorial field absorbed one. trellis_postgres is 342 characters and trellis_neo4j 461. The five composed contributions measure 1,435 of 2,000, leaving room the next surface can be wired into rather than argued about. Verified: 1,424 vitest tests, and test:modules, test:surfaces, test:textedit, test:answer-channel, test:rlm-workspace, test:rlm-mcp and test:repl-sandbox each green through their npm wrappers. The composed prompt digests did not move; these bytes are run-composed and sit outside both pins.
…he raise sites `_LM_ERROR_CODES` shipped in the previous commit without `frame`, while `lm_handler` raises `FrameError` at sites a caller reaches with its own arguments -- `llm_query_batched(prompts=[])` is one. `frame` is the only consequence in the taxonomy that drops the connection, so the rendered on-error account omitted the harshest outcome a caller could trigger while listing six milder ones. It now renders "frame is not retryable and drops the connection". The omission is the finding; the hand-kept list is the defect. `_validate_expects` checked membership in `ERROR_CODES` and never agreement with the module the list names, which is the second-encoding shape this layer closes everywhere else -- a list that agrees with its source on the day it is written and is bound to nothing afterwards. So the list is now bound. `test_the_declared_lm_codes_are_the_ones_lm_handler_raises` reads the raise sites out of `lm_handler.py` by AST at check time and refuses any code raised there that the capabilities do not declare. By AST and never by import: `capabilities.py` ships inside the guest image, and importing `lm_handler` to enumerate its raises would carry `repl_sandbox.dlp` -- the detection patterns -- in with it, which is the refusal `guest_rpc.py` already records for itself. Reading the seam rather than keeping a list is the move `derive_injected_names` makes on the rlm side. The converse is checked weakly and deliberately: a code may reach a caller through a helper rather than a literal raise here, and `cap_spend` is exactly that, arriving through `SpendLedger.charge`. The second check holds every declared code to a route in either module, so a declaration nothing can produce is refused while a legitimate indirect one passes. The dlp scan is widened in the same pass. It iterated the registry, which holds the broker and algebra descriptors and not the LM pair -- those are constructed in `capabilities.py` and reach a prompt through `PRE_REGISTERED`. So the two descriptors nearest `lm_handler` and `dlp`, the two the claim is most about, sat outside the guard the claim rested on. The scan now covers both and asserts that it does, so a later move of `PRE_REGISTERED` cannot narrow it back to the registry silently. Verified: 1,153 repl_sandbox tests pass, up 2. Each new check was watched failing and restored -- dropping `frame` reproduced the original defect and the first check named it; declaring `timeout`, which nothing raises, fired the second; planting the word "scanner" into an LM descriptor's doc fired the widened dlp scan on `llm_query`, which the pre-widening scan could not have reached.
…its rule `trellis_contribution.py` shipped with a docstring asserting a drill re-measured the rlms prompt and held the budget under it. No such drill existed -- the session that wrote the module ended before writing it -- so every refusal in the module was unexercised: the brace refusal, the newline refusal, the empty-line and boundary-whitespace refusals, the budget refusal, and `attach_contributions`' byte-identity-when-absent guarantee. `measure_contributions` had no caller anywhere, including tests. Those refusals are the safety property rather than housekeeping. A brace reaching rlms fails as a KeyError inside `.format()`; a newline breaks the one-line-per-surface listing into an unindented line the model reads as a directive of its own. Both fail at runtime, in a paid run, at the point they matter and not before. `npm run test:contribution` now exercises all of it: 44 checks over what the slot takes, what it refuses, what happens when a slot points at nothing, the budget's refusal and its ranked per-surface sizes, absence costing zero bytes, and the two shipped surfaces composing through the frame. Section 6 makes the module's own docstring true -- it re-measures `RLM_SYSTEM_PROMPT`, confirms the slot is present, and holds CONTRIBUTION_BUDGET under the protocol prompt it splices into, so the bound is checked against a measured number rather than a remembered one. `--negative-control` plants nine conditions and exits 3 when all nine are detected. Two existing drills were checking their data against a COPY of the composition rule. `test_answer_channel.py` and `test_scaffold_unit.py` each carried a local `contributed_line` that joined with "" and never called `_guard_line`, so a change to the real join would have kept both green while the shipped line moved. Both now call `render_contribution`. Verified by planting exactly that change: with the real join altered, the contribution drill goes red, where previously nothing in the tree would have. The line contract in `trellis_tools.py` is deleted rather than corrected. It restated the frame's field name and join in a surface file -- one rule in two places, which is the failure this whole layer exists to close -- and it was already wrong, naming a field `line` and a join of one space against a frame that uses `contributes` and joins with "". What the file keeps is its own business: why each guard phrase carries its own leading space, so an unwired guard costs nothing instead of leaving a gap. Verified: 44 contribution checks pass, negative control detects 9/9 and exits 3; 1,424 vitest tests; test:surfaces, test:modules, test:textedit, test:answer-channel, test:rlm-workspace, test:rlm-mcp and test:repl-sandbox all green through their npm wrappers.
…ot reproduce Rule 12 wants a durable decision in the record that owns its topic. The decisions this branch made -- the rlms description slot as a delivery route, the contribution frame and its budget, the contributes grammar, the mirrored repl_sandbox registry -- rested only in a pull request body, which is a fourth resting place rather than one of the three. They now rest in SELF_DESCRIBING_SURFACES.md section 13, with a pointer from LLM_HELP_SPEC.md, which is the spec that defines the surface this work reached around. Two claims in that record were falsified by this branch and are marked in place rather than rewritten. Section 12's closing count -- eight surfaces still carry no descriptor -- is superseded: the diagnostic reports 8 of 9 described. Section 9.2's first bullet -- no shipped surface derives its self-description from its guard predicates today -- was already false when it was written, because composeJudgePrompt renders the same taxonomy object parseJudgeVerdict refuses against, in a different subsystem and a different language, and it predates increment 1. The entry states the ladder, because a number here is easy to cite at the wrong rung: 8 surfaces registered, 5 carrying a contribution, 2 wired into a run. check:surfaces reports the first, and this branch's own headline cited it as though it reported the third. It also states LLM_HELP_SPEC.md section 6's self-play gate as OUTSTANDING rather than leaving it declined in silence. attach_contributions computes the registry-by-injected-surface intersection that section 12 names as the alive catalog's own work, and hands it to a production model, which is the moment section 6 binds. The judgment made instead is written down so a later session can overturn it rather than inherit it unstated: two wired surfaces carrying guard-derived bounds and no whenToUse do not engage the discrimination or selected-on-a-lie questions, so the trigger is stated rather than the gate waived -- section 6 binds before whenToUse reaches a composed line, and before any queryable catalog surface lands. Two figures published on this branch did not reproduce and are corrected in the pull request description, which also names the commits that carry them. "Eleven surfaces" is nine: the custom_tools seam has nine non-callable entries, and the other five are callables rlms renders as "A custom function". "342 characters" is 361, measured before a dangling-fragment fix and not re-measured after; the total in the same sentence, 1,435, was re-measured and confirms it. A third claim, that no two of the nine instances knew about each other, is overstated -- three cite each other by name in their own docstrings. The commit messages carrying those figures are left as written. The house pattern for a falsified claim is a dated correction preserving what it replaced, which is what this branch does to RLM_HARNESS_SCAFFOLDING.md section 8.4; amending would hold the author's own error to a weaker standard than the record's. The density-trellis digests are repaired here too, in both the map and its render. The pin-recompute pass demoted one of four live citations of the superseded values and characterised the rest as historical; two of them stated the old pair as current receipts, which rule 22(a) makes a repository-wide repair rather than a per-file one. Verified: check:repo-surface passes with 0 issues, and the density-trellis contract passes with 715 paths routed.
… of a list The previous pass described eight surfaces and wired two. Eleven of thirteen entries still rendered as "A custom <Type> value" in the highest -primacy text a run sees about its own tools, and three surfaces had a finished line sitting unwired for no reason beyond where the session stopped. The headline it reported -- 1 of 9 to 8 of 9 -- was the registration rung, not the wired one. Every surface a run injects now composes a line: the two database surfaces, the answer channel, the task holder, the running-state gate, the workspace, the MCP client, the editing toolkit, and the five staged helpers that until now rendered as "A custom function". Thirteen lines, 1,669 of the 2,000-character budget. The wiring iterates custom_tools itself. Naming surfaces by hand at the seam is what produced two-of-thirteen -- a hand-kept list drifting from the seam beside it, which is the failure this layer exists to close, committed inside the layer that closes it. A surface added to the seam is now described the moment it registers a contribution, and one with no contribution keeps its bare value. Compression paid for the eight new lines. The five that existed measured 1,435 characters; they measure 879 now. What came out is elaboration a refusal already delivers at the point it fires -- the three retrieval surfaces each name themselves and their offending argument when they refuse, so enumerating them in the listing spent primacy bytes on something already delivered. What stayed is every bound that makes a refusal predictable before it happens. trellis_neo4j is 251 rather than the 150 fair share because reaching 150 meant dropping one, and the honest number is the one that keeps it. trellis_textedit gains a line, reversing half of a decision from the previous pass. That decision conflated two claims: its guard-backed expectations do belong in the addendum, and the surface still has to say what it IS, because declining the slot did not leave it empty -- rlms filled it with a type name. The line points at the addendum by pulling the same category field the addendum banner renders, so pointer and target cannot drift. The pin asserting the absence is retired for one holding the property that matters: no guard-owned phrase reaches the slot. The ladder pin is retired one pass after it was written. It held "fewer contribute than are described", which was honest scope while the work was half-done and false the moment it was finished. A pin that can only say "not yet" cannot notice completion, so its successor holds the opposite claim -- the ladder is flat at the first two rungs -- and goes red on the regression rather than on the progress. Verified: 1,424 vitest tests; test:surfaces, test:contribution, test:modules, test:textedit, test:answer-channel, test:rlm-workspace, test:rlm-mcp, test:repl-sandbox and check:repo-surface green through their npm wrappers; both negative controls exit 3. Every new check was watched failing against planted breakage and restored, including the frame refusing a trailing-whitespace piece that a local reimplementation of the composition rule would have swallowed. This trips a gate this branch wrote. SELF_DESCRIBING_SURFACES.md section 13 states that LLM_HELP_SPEC.md section 6's self-play validation gate binds before whenToUse reaches any composed line. It now does, on trellis_answer, trellis_task and trellis_upsum. The gate has not run, and the reasoning that deferred it -- two wired surfaces cannot pose a selection problem -- no longer holds at thirteen.
Every modules/*/module.json carries a purpose string. ModuleManifestSchema validates it, the Python twin validates it on the same bound, load_module carries it into the loaded dict, and both loaders agree -- and no code path spliced it into any prompt. A field checked by two independent loaders reached no model. A default run has spatial-flywheel active and was never told so, while its directives were already in the prompt with nothing naming where they came from. build_active_modules_addendum composes one line per active module: the name, and the purpose its own registration records, verbatim. Nothing is re-authored -- a drill asserts the manifest bytes appear unchanged, and another refuses a purpose that is empty, multi-line, brace-bearing, or carrying trailing slop. The segment attaches after SYSTEM_PROMPT rather than inside TRELLIS_ADDENDUM, in the shape build_mcp_addendum already uses, so neither composed-prompt pin moves. A check asserts the segment is absent from both SYSTEM_PROMPT and TRELLIS_ADDENDUM, so a later move into the pinned region cannot happen quietly; planting exactly that move turns both arms red at their recorded digests, which is the observation behind the claim that neither moved. It is a segment rather than an entry in the rlms per-tool listing because a module injects no object. A line there would name a callable REPL surface the run does not have, at the highest-primacy position in the prompt. An empty selection composes the empty string, so a run with TRELLIS_MODULES='[]' stays byte-identical. Retired and contested modules contribute nothing by the predicate that already keeps their addenda out: load_module raises before a module dict exists, so an inert module has no object to reach the composer. Verified: test:modules 75 checks pass, including that the run seam calls the composer rather than the composer merely being correct. Three plants seen red and restored -- the segment moved into the pinned region, the seam call deleted, and a byte of purpose re-authored. 1,424 vitest tests and every other drill green.
Section 13 stated a trigger: the gate binds before whenToUse reaches any composed line. Three surfaces now pull whenToUse, so the trigger fired -- and the collaborator ruled that the test it points at is not a legitimate target. The ruling, recorded in his terms rather than paraphrased into agreement. LLM_HELP_SPEC section 6's second test asks whether a lying descriptor can mislead the model, with selected-on-a-lie as the pre-committed falsifier. Its adversary is whoever writes a descriptor, and descriptors are repository code authored under rule 16 and reviewed -- so the adversary is a reviewer, the threat crosses no boundary, and the experiment reduces to whether a model believes a lie it was told. That answer is entailed by what a language model is. Matt: if someone wants to reverse-engineer Trellis to lie to the interior model, there is nothing that will stop that, not ever; and we know what happens when you give a language model the wrong context. That is rule 20's own failure -- an outcome fixed by construction, reached for because the comparison was closer to hand than a target. The measurement would have reported the design. Struck: the drift half, and with it the whenToUse trigger, which was scoped to it. Standing: section 6's discrimination half, on its own merits, as a paid question under rule 7 that needs a queryable catalog to select within -- so it belongs with llm_help rather than with a per-tool listing that is always present entire. Untouched: the guard-derivation of section 3.3, which was never a test. It is the structural reason an expects line cannot drift from the predicate that refuses, and it holds whether or not anyone probes it. The drills hold the same property by construction: a guard-owned phrase restated in an editorial field is refused by a check, not by a study. Verified: check:repo-surface passes with 0 issues.
The control referenced a coverage_report key the report never returns, so it raised a KeyError before planting anything. A control that throws detects nothing, and this one reported a passing drill while proving none of its checks. It now plants against the keys the report actually carries, and separates the two rungs it had collapsed: a registration the seam never injects is registered_not_injected, while a surface that carries a line no run passes on is contributing_unwired -- the rung this drill exists to make visible. Verified: 13 of 13 planted conditions detected, exit 3.
…roduced it Section 13's rung table read 5 contributing and 2 wired. That was the state when the rung split was written, not the state it shipped in: the wiring pass landed thirteen contributions and drew the composing roster from the seam, so the true figures are 13 contributing and 8 wired plus 5 the static read cannot settle. The prose under it was falsified in both clauses -- check:surfaces reports all three rungs rather than only the first, and the assertion it named has been retired. That retirement is the point worth recording. The old check asserted the ladder NARROWS -- fewer surfaces contribute than are described -- which took an unfinished wiring and pinned it as a property. A check that holds a gap open reads as rigor while measuring nothing anyone wants. Its replacement holds the property that matters: no surface carries a line the composing call leaves out. That replacement was run against the real historical seam rather than a fixture. With trellis_agent.py from 34538be^ swapped in, it goes red naming eleven surfaces while the two rungs above it stay green -- which is exactly the failure it exists to catch, the cheap rungs reading as progress while eleven finished lines reached no model. The ladder is flat now structurally rather than by bookkeeping: the composing call draws its roster from custom_tools itself, so a surface added to the seam is wired without anyone editing the call. The five unsettled are the staged helpers, injected conditionally, which the report names rather than counts -- unestablished is not established. Verified: every drill green, 1,424 vitest tests, both negative controls at exit 3 (13/13 and 9/9), check:repo-surface 0 issues.
…the un-tool Two skills that compose sub-agents now state what a ground block carries and what it must not, because a ceremony run under both of them produced every artifact they ask for and was contaminated anyway. The case, named in each skill so the rule carries its own reason. Four judging seats were spawned over work their own composer had authored. The composer pre-registered its expected findings, held them separate from every prompt, ran the seats in clean contexts, and audited the run afterwards -- all of it correct, and none of it sufficient. It had already run a probe, recorded the result as a prediction, and then handed that same probe to the seat that would "find" it. The audit seat ruled the run's independence unestablishable and said why: the composer saw the result, wrote it down as a forecast, then supplied the instrument. Three seats carried composer-stated facts while the disclosure said three and named two. So the leak was never in the prediction bytes, which were withheld cleanly. It was in the method. Handing over the probe hands over the finding with one step of deniability attached, and it reads as method rather than as steering, which is why the existing prohibitions did not reach it: self-play discipline 1 audits the evidence corpus, judge-composition Step 5 governs task text and failure mode 2 names steering relocated into annotations. Each of those forbids something a prompt can carry. None of them says what a ground block positively carries, and none names where a held expectation goes instead. Both additions draw the line as a category difference plus one discriminating question rather than a checklist, since the failing run would have passed a checklist: does this let the agent look, or does it tell the agent what looking will turn up? A fact the agent cannot derive cold and needs in order to look is ground. What the composer believes it will find is not, and it contaminates even when true -- most of all when true, because an agent handed a true expectation returns it and no report separates that from a finding. The destination is the un-tool: end the tool call and address the collaborator. Rule 21(a) and the spark-steering section are cited from both passages rather than restated, so neither skill opens a second encoding of a rule that lives elsewhere. Placement is where the rule fires rather than where it reads well. In self-play it sits immediately after the player prompt frame, so a session composing a ground block meets it before writing one. In judge-composition it sits in Step 5 beside the sentence enumerating what a seat receives, which is the one place already governing seat input. Evidence that the discipline works, from the same session: a re-run dispatched seven reviewers whose ground blocks carried the facts and stated in as many words that no prediction was offered and none was wanted. They returned findings the composer had not anticipated, several against the composer's own work. Neither skill is byte-budgeted -- root-contract.json lists no .claude/skills path in rootFiles or documentUpsum.paths, and check:repo-surface passes with 0 issues after the edits. The user-level prompt-engineering and hypershot-protocol skills are deliberately untouched: whether this rule belongs in surfaces every project loads is a standing-configuration question put to the collaborator, not one this session settles.
The active-modules segment opened "The operator selected these protocol modules for this run." On a default run nobody selected anything: parse_module_selection returns the kernel's own DEFAULT_SELECTION when TRELLIS_MODULES is unset, and the module's docstring says so eleven lines from the code. The sentence rendered on every research run including that one, and nothing in the text distinguished the arms. The obvious fix is wrong, and this is the finding. parse_module_selection does know locally whether it read a value or substituted the default -- but that bit is not the operator bit. rlm_worker.ts forwards config.modules.selectionJson unconditionally, and config/index.ts derives that string from parseModuleSelection, which falls back to DEFAULT_MODULE_SELECTION itself. Every production spawn therefore hands the child an explicit selection on BOTH arms; the raw-is-None branch is dead past the worker. A header driven by that bit would print "the operator selected these" on exactly the default run this fixes, with plumbing behind it making the falsehood look sourced. The presence of the value is manufactured by the worker, not the value. So the header states what the code establishes and names what it cannot. It carries the standing the loader proves -- registered, active, kernel-compatible, validated before composition, purpose verbatim from the manifest -- and the scope the constant holds: fixed from the process environment at startup, unchanged for the run, so task text, tool output and completions all leave it as it is. Then it says the authorship is a fact the prompt does not carry, as a disjunction, because the disjunction is what is true: an operator naming these modules and the kernel supplying its default compose the same bytes here. The check that holds it is arm-equality: the kernel-default arm and the operator-set arm compose the same bytes. Planting the wrong fix -- a module global set by parse_module_selection and rendered by the composer -- reddens that check ALONE, which is the discrimination worth having. Two further plants: the retired sentence restored verbatim reddens four checks, and changing DEFAULT_SELECTION so the forwarded value stops equalling the substituted default reddens five. Cost: the segment is 573 -> 1154 characters, identical on both arms, which is now a pinned property rather than an accident. Neither composed-prompt pin moved -- the segment attaches at the run seam past SYSTEM_PROMPT -- and both were recomputed in a separate process to confirm it. Left standing and reported rather than changed: trellis_modules.py's "Selection is operator-owned (Guardrail 5)" docstring, whose claim is that only the operator MAY set the gate and no payload or completion can, which is true. And the spawn-contract change that would carry the real bit -- a selectionSource beside config/index.ts, forwarded in buildAgentEnv, read beside _SELECTED_MODULES -- which is a change to what the spawn contract carries rather than to a description. Verified: test:modules green with six new checks, each watched red; grep across ts, py, md and json finds the retired sentence quoted nowhere else, so rule 22(b) has no citations to follow.
…old it Each was reproduced before it was closed, so the red afterwards is against the same plant that was green before. DELIVERY. Removing the attach_contributions wrapper leaves the composed lines computed and discarded, every surface back to rlms's type-name fallback -- and test:surfaces was green, test:contribution was green, and check:surfaces still printed "8 of those are wired at this seam". The wired rung read the composing call and nothing downstream of it. derive_delivery now climbs from each compose_contributions call to its innermost enclosing statement and holds three links: the composed value is assigned back to custom_tools, RLM is handed that same name, and no seam mutation follows the attach -- attach_contributions returns a NEW mapping, so a surface added after it silently loses its line. The read is scoped to the function containing the composing call, without which the author path's own RLM(custom_tools=custom_tools) would stand in for the research seam's. COVERAGE. Injecting a surface with no descriptor was green: the drill computed undescribed and asserted nothing on it. What blocked asserting was UPSUM_BUDGET, a bare int declined on purpose, which would have reddened an honest tree -- and a check that reddens an honest tree gets switched off. coverage_report now splits undescribed into declined and gaps. That one roster is written down rather than derived, deliberately: whether a name warrants a descriptor is a human call, so no read of the code returns it, and a derivation from type or case would EXEMPT a real gap on a wrong inference. It is kept from becoming the defect one level up by being checked against the seam both ways -- a declination for a name the seam dropped, and a declination the registry contradicts, are each their own red. SUPPLIERS. Dropping one surface from the _expects roster aborts every research run at composition, and ten drills stayed green because none composes the live seam. Both sides are now derived: the roster by AST from the composing function, the requirement from the live registry -- every descriptor whose contributes carries an expects slot, with the tag imported rather than copied. It discriminates rather than merely fires: dropping trellis_mcp stays green, correctly, because that descriptor composes fine without a supplier. THE EXIT CODE, settled by making 1 mean something rather than by dropping the condition. check:surfaces exited 1 on a clean tree and had since the rung shipped, so a real gap arriving changed nothing a caller could read. It now exits 0 at baseline and 1 on exactly six undecided states, each of which affects a model: an injected surface with no descriptor and no declination, a finished line the call omits, composed lines reaching no model, a slot with no supplier, a dead exemption, an exemption the registry contradicts. The posture is unchanged -- nothing in a run consults it, no gate reads it, and it is not in CI. Negative control 13 -> 27. Six plants are applied to an in-memory copy of the shipped trellis_agent.py rather than to a fixture, and a plant whose anchor has moved reports MISSED rather than skipping, because a plant that cannot be applied has detected nothing. Three of the six are the three misses verbatim. Not held, and named rather than left: delivery for the five dynamically injected staged helpers, which a static read cannot enumerate; a seam mutation through an alias; and whether a wrong declination added deliberately would be honored, which is the residue of the property being a human call. Verified: check:surfaces exit 0, negative control 27/27 exit 3, 1,424 vitest tests, and every drill green. trellis_agent.py was planted and reverted only -- byte-identical to HEAD by sha256, by git hash-object against the HEAD blob, and by an empty git diff.
CONTRIBUTION_BUDGET // 13 was hard-coded in five drills as a per-surface ceiling. Thirteen is a census of what happens to exist; a fourteenth surface loosened all five at once. That is rule 17's plural test answered backwards -- an instance reaching a check and becoming law -- and it reached five. The replacement is a property of one line rather than a share of a budget: ORIENTING_LINE_MAX = 160, because the slot rlms reserves takes an orienting line, plus the whole-composition budget the engine already refuses past. Confirmed by running rather than argued: a synthetic fourteenth surface with a 400-character line was picked up by the roster with no edit to the drill and reddened on the ceiling, while the same plant against the drill as it stood left every check green. Rejected, with reasons. A divisor derived from the live registry is the same equal-split model auto-counted, and it fails OPEN in the four satellite drills, which import only their own surface module -- trellis_mcp alone yields a registry of one and a ceiling of 2000, so the check would assert nothing. A per-line cap with no whole-composition check misses every line staying small while the sum crosses. Keeping 153 under a new name is laundering, since 153 is 2000//13 and carries the census forward. The budget is now exercised over what ships: 2 surfaces and 276 characters became 13 and 1,502 of 2,000. Three guards sit on the roster -- it must equal the derived contributing set; an anti-trivial floor from an independent source reddens on an empty or half-imported registry rather than reporting a comfortable zero; and the expects mapping fails closed, so a surface needing a derivation and missing from it reddens naming that surface. THE TAUTOLOGY WAS SHELTERING A FALSE SENTENCE, which is the finding worth keeping. slot_at < base_len is entailed by the slot_at > 0 check three lines above it, so it could never fail -- and its name claimed the slot "precedes the prompt's own midpoint". The slot is at 1,335 of 2,116, which is past the midpoint. The check could not fire, so the false claim in its name was never contradicted. Primacy now rests on the premise that is true and that rule 6 already requires: RLM_SYSTEM_PROMPT is a strict prefix of the composed prompt, so a slot inside the base precedes every Trellis directive. The slot offset and the prompt length -- cited in two source files and a design record and held by no drill -- are now asserted, and moving either reddens with the repair sites named. Two figures moved and are named rather than buried: the per-line ceiling 153 -> 160, against a longest shipped line of 150; and the negative control 9 -> 11, whose two new plants are the two properties this pass repaired. The 9 was asserted in trellis_contribution.py and in SELF_DESCRIBING_SURFACES.md, and both are repaired here -- rule 22(a) ranges over the sentences stating a count, not the file that owns it. No prompt bytes moved. Every shipped line is byte-identical; the drills changed and the model reads exactly what it read before. Verified: all five drills green at HEAD before the edits and green after, 1,424 vitest tests, negative control 11/11 exit 3.
The composed neo4j line read "Cite only addresses whose bytes you retrieved this run, not ones Cypher surfaced." The guard it describes, _verify_hashes_retrieved, computes a set difference against the retrieved set and reads nothing about how an address was seen. A hash that Cypher surfaced and that get_ast_texts later returned is in that set and is citable -- and the line told the model it was not. Prompt bytes that over-refuse are drift in the same way bytes that under-refuse are. The clause now states the closure and gives the cure: retrieve a Cypher-named address first. That keeps the actionable half -- run_cypher never feeds the retrieved set, so a Cypher-named address is not yet citable -- without asserting a permanent disqualification the engine does not impose. 159 of the 160-character ceiling. The check generalises past this instance. It splits the shipped clause on punctuation and requires any fragment carrying an exclusion marker to also carry a retrieval term, so a future clause fencing off a class the predicate does not hold reddens whatever words it uses. Six checks beside it run the shipped guard as an oracle on both sides of the membership line, with an anti-trivial floor and the unwired-holder blank asserted separately so blanking stays correct rather than passing vacuously. Watched red twice: the historical clause, and a differently-worded future disqualifier. Every shape check stayed green under both, which is the point -- shape checks structurally cannot see this defect. The comment block defending the old clause is repaired in the same edit; it previously argued the removed wording named the same set. Verified: test:contribution 42 checks green, negative control 11/11 exit 3, composed 13 surfaces at 1,511 of 2,000.
…e name trellis_agent.py has two run modes. The research path composed one description line per injected surface; the author path built its own custom_tools and never called attach_contributions, so every modules:author run handed its model "A custom TrellisWorkspace value" -- while WORKSPACE_DESCRIPTOR carried a finished line that path never composed. The line is the same bytes as the research one, and that is a decision rather than a default. WORKSPACE_ADDENDUM is spliced into the author prompt already and opens by calling the surface the model's working memory for plan, self-notes and captured results, so the slot names no affordance the prompt does not already name; a second characterization would put two readings of one surface into one prompt. That premise is asserted rather than assumed. The derivation was the reason this stayed invisible. _composing_scope took max() over the functions holding a composing call and returned ONE -- harmless only while the second mode composed nothing, and wrong the moment it did. It now derives every scope, answers delivery per seam with the conjunction, and adds rendering_without_composing: the one condition that names a whole run mode, which no per-surface rung can see, because such a mode's surfaces arrive through a factory and it owns no composing call to read. check:surfaces joins that condition to its exit code, since reporting an undescribed run mode at status 0 is the spent-exit-code failure that file's own header documents. Neither research composed-prompt pin moved: descriptions ride on custom_tools rather than on prompt text, so build_author_system_prompt's return is byte-unchanged -- now pinned, so a later pass cannot move the line into the prompt silently. The author prompt itself is held by no sha256 anywhere, which is worth knowing and is not repaired here. Watched red by restoring the pre-change agent: test:surfaces names run_author_mode as a mode whose surfaces reach their model as type names, test:modules reddens four checks, check:surfaces exits 1. The negative control also reddened on its own before any planting -- the edit made an anchor non-unique and two REAL SEAM plants reported MISSED rather than skipping, which is the harness catching its own staleness. Plants are now per run mode; the control is 27 -> 32. Verified: every drill green, 1,424 vitest tests, check:surfaces exit 0 reporting two composing seams, negative control 32/32 exit 3.
…uses spatial-flywheel is DEFAULT_MODULE_SELECTION, so it composes into every research run. Its step 1 loaded sourceNodeIds through run_cypher and its mandatory write steps cited those same values as provenance, with nothing in between reading the bytes at those addresses. _verify_hashes_retrieved admits only hashes in the run's retrieved set, and that set is fed exclusively by the read and search buckets of _audit_add. run_cypher feeds neither, deliberately: a sourceNodeIds property in a query result is a reference to bytes rather than the bytes. So the default module's mandatory step prescribed exactly the batch the default guard refuses whole, and no record connected them. Read-then-cite is the legitimate act here rather than a workaround -- the closure's own docstring says it closes the transcription and second-hand-citation class and leaves laundering to a separate entailment tier, so what it requires is that the run actually held the bytes. A new step 2 retrieves the deduped union of every sourceNodeIds the catalog carries in one get_ast_texts call, keeps it unprinted, and each write is filtered to the addresses that came back rather than to the full property value -- a question left with none is skipped rather than refused. It costs ONE fetch at any catalog size, because get_ast_texts increments the fetch counter per call rather than per hash, leaving 63 of the 64 budget. Per-question retrieval was rejected on that arithmetic: it costs one fetch each and exhausts the budget past 64 questions. A second fact forced the bulk placement independently -- the mentions step writes for questions whose categories were cache hits, so a fetch scoped to the classification write set would leave those citations unretrieved and refuse that batch. Both composed-prompt pins moved, together, in this commit, and both were watched failing before recomputation and again under a one-hex-digit perturbation with the prompt restored. Each new value was derived twice in separate fresh processes. Default arm 15,658 -> 17,214 chars, omit arm 16,962. The check holds the class rather than the instance: an addendum naming a provenance-write surface must name a retrieval surface EARLIER in its own text, with both surface sets derived from trellis_tools.py's own call sites rather than listed by hand. Three controls seen red -- the pre-repair addendum, the repaired step relocated after the writes, and the derivation regex perturbed to match nothing, which reddened the anti-vacuity guard and made the dependent check report SKIP rather than pass silently. Honest scope: the check reads instruction text, so it establishes that a procedure ASKS for retrieval before its writes, not that a run performs one. It binds non-vacuously on two of four registered modules; the other two prescribe no provenance write at all. The class it closes is a module that never asks, which is the defect that was live. module.json's version is left at 1 -- the manifest records no content hash and a bump was not judged to be this pass's call. Verified: test:modules green after the recompute, test:module -- spatial-flywheel 8/0/0, all four modules' acceptance runs green, 1,424 vitest tests, addendum brace-free at 4,606 bytes with one substitution token.
parsePredictedPairs matched parentheses only, so a run answering FINAL_ANSWER: [["q_0013", "q_0050"], ...] -- a JSON list of lists, the notation json.dumps produces -- scored 0.0 with every pair present and none counted. Observed on a live run: the answer was correct and the report called it a reasoning failure. Both bracket forms are now accepted; the pair shape still has to match, so no new class of text is admitted, and the 15 existing scoring tests stay green. The defect is worth more than the run that surfaced it. It had nothing to do with the module under test and would have misread any future benchmark run answering in valid JSON. The measured result lands in PROVENANCE_CITATION_AB_REPORT.md rather than in a pull request body, which is rule 12's fourth resting place. One cold OOLONG-Pairs query per arm, identical graph, arms differing in one file: the spatial-flywheel addendum at 41eff03^ against 41eff03. Control refused 24 of 24 cited hashes and cached 0 insights. Treatment refused none and cached 220. Rescored with the fixed parser, F1 goes 0.800 to 1.000. The control discriminated, so this is a finding rather than noise. Two figures, per rule 7: estimate $0.25 printed before the first paid call, actual $0.4591. The overage is a blind first arm -- it returned F1 1.0 with zero sub-LLM calls, because the freshly ingested corpus carried all 220 categories and the module short-circuits when q.category is set, so nothing classified, nothing wrote, and the guard never fired. Its output was noise and the record says so. Standing is stated rather than implied: n=1 per arm establishes that the pre-repair write is refused and the repaired one is not, on the same instrument, and does not establish a distribution or a claim about answer quality, where one sample separates 0.800 from 1.000 and cannot say why.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.