Skip to content

D/meta prompts abc paths d5bf92 - #196

Open
OpenCnid wants to merge 18 commits into
masterfrom
d/meta-prompts-abc-paths-d5bf92
Open

D/meta prompts abc paths d5bf92#196
OpenCnid wants to merge 18 commits into
masterfrom
d/meta-prompts-abc-paths-d5bf92

Conversation

@OpenCnid

Copy link
Copy Markdown
Owner

No description provided.

OpenCnid added 18 commits July 25, 2026 02:04
…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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant