fix(#312/#317): a measurement must not leave her hands, or her beliefs, in the exam room - #2153
Merged
Merged
Conversation
…of resident weights Every SWE-bench RESOLVED=0 on this box was measuring a harness that never started. Glass-boxed on the M5 today. An `agent/solve` run asked for unsloth/Devstral-Small-2507-GGUF — the exact model the live lane was already serving, 27.2 GB resident, answering on :58057. build_base_eval_lane_inner tried to cold-load a SECOND copy: ~17.9 GB + 2 GiB headroom against 15.0 GB free on a 64 GB box. It can never fit, so await_eval_lane_memory_headroom deferred every 5s for its full 180s window and then failed loud. The harness wrote a zero-byte diff and the run reported RESOLVED=0. The model was never asked to solve anything. This is the exact shape #310 already fixed one branch above, for external providers: "route the measurement through the registered provider adapter instead of cold-loading a llama-server for weights this box may not even hold." The identical reasoning applies to a LOCAL model already resident and answering; it just was never extended there. share_live_serving_lane() takes that branch when the live snapshot is ready and serving this exact base. Nothing spawned, no VRAM leased, lane: None — so a measurement can never tear down the living persona's lane on drop. served_ctx comes from the lane's own /props truth, the same authority the living persona budgets against (#50). Isolation (#59/#312) is preserved rather than traded away: what an eval needs isolated is the GENOME and the WINDOW, never the weights. The share is refused unless every resident adapter reads scale 0, asked of the running server rather than assumed — this lane carries six loaded-but-unapplied genome layers, inert in fact, but that is a fact to verify. Unreachable or unrecognized /lora-adapters shape also refuses: blind never means "assume clean" for a number that gets published. Also: the defer probe logged level=Normal beside "waiting for a transient memory spike" while the real veto was a hard RAM shortfall that could never clear. Two different vetoes, one message, and the refusal text was in hand the whole time. It now logs the refusal it is actually retrying. Measured before → after, same instance, same model: 180s deferral → hard fail → 0 bytes changed 15s → eval.lane.shared, served_ctx=16384, run proceeds Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…at it wrote
"success: true, bytes_written: 214" confirms a write happened. It never confirms
the write went where the caller meant, and that gap is what turns a persona who
reasons correctly into one that produces a broken patch.
Glass-boxed on the M5 today, SWE-bench flask-4045, from the turn capture. Anwen
localized the bug herself and had the right fix. Then:
act 1: code/shell `cat src/flask/blueprints.py` <- output has NO line numbers
act 2: code/edit insert_at line 28 <- a number she never saw
=> the guard clause landed inside the function's parameter list
act 6: code/read start_line=119
act 7: code/edit line_range 62..65 <- a region she never read
=> second hunk mangled
Both returned success: true. Nothing in her working memory said otherwise, so she
spent her remaining acts flailing through discovery tools (code/tree, commands/list
twice, a repeat search) instead of repairing damage she could not see. 12 acts, the
right two files, the right idea, an unusable patch.
A human editor shows you the result. The act->observe circuit only closes if the
receipt carries what a screen would, so WriteResult now carries applied_context:
the numbered lines around the landing site, read back from the content actually
written, with the anchor marked. Same surface code/read already returns, so there
is one way to look at a file, not two. None for whole-file writes, deletes, and
undos, where "where did it land" has no answer worth rendering.
This does not steer her cognition — no heuristic decides what she should do next.
It hands her the fact and lets the next turn use it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
Follow-up to 59efe1e, from watching the same persona finish the run I sampled too early. She reached a CORRECT 2-line fix to src/flask/blueprints.py (128 lines) at act 2 — edit, read it back, edit again, exactly the act->observe loop the edit receipt was meant to produce. Then at act 4 she called code/write with a 5-line stub reconstructed from memory: imports gone, class hierarchy gone, SansioBlueprint gone, her own correct fix gone. The receipt said: success: true, bytes_written: 214 She read the file at act 6, saw the stub, and never repaired it. Final state: the module destroyed and the fix erased. 59efe1e gave whole-file writes applied_context: None, reasoning that a whole-file write has no landing site to report. True, and beside the point. A whole-file write over EXISTING content has something more consequential to report than where it landed: what it replaced. Replacing 128 lines with 5 is the most destructive thing a file tool can do, and it was the only path returning no feedback at all — so the verb most able to erase work was the verb that told her least about erasing it. overwrite_magnitude() now reports lines-before -> lines-after, names a >2x shrink explicitly rather than leaving it to be computed, points at code/undo, and shows the head of what the file now holds. Creating a NEW file stays silent (asserted in the test) so the warning carries weight when it appears. Also extends edit_anchor_line to SearchReplace and Append — a content-anchored replace is the safer idiom precisely because it needs no line number, which made it the idiom a careful model reaches for AND the one with no feedback. It now locates the replacement in the written content. Refuses nothing, gates nothing, steers nothing: the loss is stated as a fact and what she does next stays hers. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…s the SyntaxError
Three SWE-bench runs on the M5 today, same persona/model/lane, and every failure had
one shape: a guard clause placed inside an open signature.
def __init__(
if '.' in name: <- run 1 (insert_at), run 3 (search/replace)
raise ValueError(...)
import_name: str,
Six edits across twelve acts in run 3 alone, with the file read back in between,
and she never recovered. 59efe1e gave her the numbered lines around the landing
site and it was not enough: the lines look like plausible Python. The defect is that
they are not valid Python, and grammar is exactly what a window of text cannot show.
code/edit now runs the file's own checker on the written content and puts the real
error at the TOP of applied_context, ahead of the neighborhood — "SyntaxError: line
21" with the caret is unambiguous where six lines of context are not. Python only for
now (py_compile, milliseconds); every other extension returns None. A missing
interpreter, unknown extension, or spawn failure also returns None: a tool that
cannot verify must not render a verdict.
Reported, never refused. The edit still applies and code/undo is named in the
message; what she does next stays hers ([[no-hardcoded-heuristics-to-steer-cognition]]).
Not a build — running the project's real tests is the persona's own move via
code/shell, not something a file write does behind her back.
Two rejected designs, both mine, both falsified before shipping:
- bracket-balance counting: inserting INTO `def f(...)` leaves the closing paren in
place, so the file stays balanced and stays unparseable. Caught run 3's duplicated
`super().__init__(` and missed run 1 entirely. Its own test killed it.
- this check, apparently — until I re-read the test and found the bug was mine: the
"clean edit must not warn" assertion appended valid code to the file the previous
assertion had just broken. Each assertion now gets its own file, and the comment
says so, because that mistake nearly discarded a working implementation.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
… it moved The orphaned-parameter-tail defect on SWE-bench flask-4045, glass-boxed from run 4's capture (M5, 2026-08-04). Her three acts, in order: 1. code/read src/flask/blueprints.py -> she now holds ORIGINAL line numbers 2. code/edit line_range 16..17, 2 lines -> 4 -> everything below slides +2 3. code/edit line_range 14..23 -> computed from the numbers in act 1 Act 3's replacement block was correct and complete — a properly closed signature, the guard clause, a right super().__init__ call. It landed two lines short of the region it meant to cover, so the tail of the OLD parameter list survived underneath it and the file stopped parsing. Nothing in act 2's receipt said the map had moved; the window it showed was six lines wide and the drift was outside it. So the receipt says it. `line_shift_notice` reports the line-count delta and states that numbers taken from an earlier read are now stale below the landing site. The delta is arithmetic, not inference, and it is a statement about the FILE, not an instruction about what to do next ([[no-hardcoded-heuristics-to-steer-cognition]]) — re-reading and adjusting by the delta are both correct responses to the same true fact. Silent when the line count is unchanged: an in-place replacement invalidates nothing, and a notice that fires on every edit is a notice nobody reads. Third in the same series, all aimed at the same principle — a receipt that reports only "success" leaves an act→observe loop with no observe: 59efe1e (where the edit landed), 4e74d93 (what a whole-file write destroyed), 7de489a (the real SyntaxError). Verified live against the deployed core, not just in test. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
… receipts that fall out of it
ROOT CAUSE of the "statement inside an open paren" failure that ran through SWE-bench
flask-4045 runs 1, 3 and 5 (M5, 2026-08-04). `code/edit` addresses lines BY NUMBER.
`code/read` returned bare text. She read the whole 128-line file, counted by hand, asked
for `insert_at` line 35, and landed ~4 lines off — inside `super().__init__(`'s argument
list, where a statement is a SyntaxError:
super().__init__(
name,
import_name,
if '.' in name: <- act 2, first edit of the run
raise ValueError(...)
Two earlier fixes chased this from downstream and were both real but both secondary: the
parse check (7de489a) catches the break a window of plausible-looking lines cannot show,
and the line-shift notice (5ddd2c2) catches the SECOND edit's version of the same
coordinate problem. Neither addressed a read tool and an edit tool that simply disagree
about whether lines have numbers. That is a defect in the PAIR.
Reads are now numbered by ABSOLUTE file position in the same `{n:>6} | ` gutter
`applied_context` already uses, so one file reads identically before an edit and after one.
A windowed read numbers from the file's start, never from the window's — a relative number
is worse than none, because it looks addressable and is not. The command description says
so, so the tool advertises its own contract.
Three receipts fall out of that change, each stating a fact and refusing nothing
([[no-hardcoded-heuristics-to-steer-cognition]]):
- code/write names content that looks like pasted read output. Numbering creates this
hazard, so the warning ships WITH it rather than after it first corrupts a file. Needs a
90% majority of gutter-prefixed lines and a 3-line floor, so real code stays quiet.
- A broken parse says WHO broke it. Run 5: an early edit wrecked the file, and four acts
later she worked out the right idiom herself — a content-anchored SearchReplace — and
applied a CORRECT fix on top of the still-present wreckage. Across five runs she repaired
forward every time and reverted zero times. The receipt said "code/undo restores the
previous content", which is advice; it now states the fact she cannot compute — this file
PARSED before your edit, so the break is one change deep — and hands over the change_id.
When the file was already broken it says that instead, so she is never sent to undo the
wrong change. Only pays for the second parse when the first one failed.
- code/delete reports what it removed. It was returning `success: true, bytes_written: 0`
for destroying a file — indistinguishable from a no-op, and the same "no neighborhood to
show, therefore nothing to report" error that left code/write silent about overwrites
until 4e74d93.
Three pre-existing tests asserted the old read contract. Two were EDIT tests that used
read() only to observe; those now assert against the file itself, since coupling an edit
test to the read tool's display format is exactly what broke them.
Verified live against the deployed core, not only in test. 186 code-module tests green.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…ost a measurement
`share_live_serving_lane` asked the running llama-server `/lora-adapters` whether any
genome layer was APPLIED before borrowing the lane. Two things were wrong with that.
It could not answer. Genome activation on this lane is PER REQUEST by design: the daemon
loads the `--lora` catalog and immediately zeroes every global scale
(`llama_server::zero_adapter_scales` — "catalog LOADED, scales DORMANT (0.0), per-request
activation only"), and a turn pages its gene in through the request body's
`lora: [{id, scale}]`. The global scales the probe read are therefore always 0.0. The
question had one possible answer, so asking it carried no information.
And it failed in the worst direction. `/lora-adapters` BLOCKS while the lane is
generating — measured on the M5, 2026-08-04, against the live lane:
/health -> 200 in milliseconds
/lora-adapters -> no response in 5s
The 3s timeout expired, the `else { return false }` arm fired, and the probe reported
the definite claim "live lane serves this base but has an APPLIED genome layer". So the
share was refused exactly when the lane was BUSY — precisely when sharing matters — and
each refusal cold-loaded a second 17.9 GB copy of weights already resident. On the run
that exposed this, that copy missed the GPU by 126 MB, spilled to CPU, and turned a
measurement into one that could never finish (`acts: 0`).
The eval adapter is built WITHOUT a `lora` field, so it measures the bare base by
construction, whatever else sits in the catalog and whoever else is on the lane. That
invariant is guaranteed upstream by the code that applies adapters; re-deriving it over
HTTP was both redundant (two sources of truth for one fact) and fragile (the one that can
hang). The comment left in its place records why there is deliberately no probe here.
The principle in the deleted doc — "blind never means assume-clean for a number that gets
published" — was right, and is why this is a deletion rather than a longer timeout. The
error was treating an unanswered HTTP call as evidence about something already true.
Verified live: `eval.lane.shared ... no second copy of these weights cold-loaded`,
base_url=http://127.0.0.1:58057/v1, served_ctx=16384. 16 cognition::eval tests green.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…top resolving a stale `cu`
Today eight flask-4045 runs were driven by hand against a clone left at flask HEAD. The
upstream fix was already in that tree (`sansio/blueprints.py:199`), so FAIL_TO_PASS passed
BEFORE any persona acted. Every score in the series was void — the zeros and the one that
looked like a clean 3-act win alike. I was one step from reporting that win.
`run_ours.py` was not at fault: it clones at `base_commit` and defers grading to the
official Docker harness, which applies `test_patch` itself. What was at fault is that the
official harness is heavy enough to get skipped, and a skipped gate is not a gate.
grade_local.py — the same protocol, in seconds, no Docker (pure-Python instances):
clone @ base_commit -> apply model_patch -> apply test_patch -> run tests
RESOLVED iff every FAIL_TO_PASS passes AND the PASS_TO_PASS sample passes
Its first act is the gate that would have caught this instantly: run FAIL_TO_PASS on the
PRISTINE tree and REFUSE (exit 2) if it already passes, because a checkout without the bug
cannot distinguish a fix from a no-op. `--gold` is the spine check and a gold failure is a
loud exit 3, never a warning — if the dataset's own patch does not resolve, the environment
is wrong and no persona number from it means anything.
Verified end to end on pallets__flask-4045:
[gate] FAIL_TO_PASS fails on the pristine tree (2 tests) — the bug is present
FAIL_TO_PASS 2/2 · PASS_TO_PASS 40/40 · RESOLVED=1 (gold)
The gate also earned its keep on the way: the first gold attempt failed on
`_pytest.monkeypatch.notset` (removed in pytest 7, this flask predates it), named the
environment as the cause, and refused to report — exactly the intended behavior.
run_ours.py: `continuum_cli()` replaces two hard-coded `cu` lookups. `cu` is the OLD name
(and macOS call-unix); worse, a `release/cu` from Jul 25 was still sitting in the cargo
target dir, so a run would have silently driven TEN-DAY-OLD client code against a freshly
deployed core and reported it as current. Resolves `continuum` from PATH first — what
`continuum reboot` installs — falling back to cargo-target, and fails loud rather than
reaching for something older ([[verify-the-deploy]]).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
… instance's date
`--auto-venv <python>` replaces the hand-built venv. Without it, scaling past one instance
means hand-maintaining a pin table per repo per era, which does not scale and silently rots.
The trick is the date. These instances are years old and their packaging is almost always
upper-bound-free: flask 2.0 asks for `Werkzeug>=2.0`, so a plain `pip install -e .` in 2026
resolves Werkzeug 3.x, which deleted `url_quote`, and the repo cannot even import.
`uv pip install --exclude-newer <created_at>` resolves the whole graph against the index as
it stood that day — the same thing the official Docker images bake in, as one rule for
every repo.
But the date applies to the SUBJECT only, and finding that line cost two failures worth
recording:
- date-pinned pytest drags in 2021 `py`, whose vendored apipkg raises
`AttributeError: __spec__` under Python 3.11's import machinery
- date-pinned setuptools predates PEP 660, so `build_meta:__legacy__` has no
`build_editable` and the install dies outright
So: repo dependencies are historical (they define the behavior under test); pytest,
setuptools and wheel are modern (they are harness, and must run on THIS interpreter), with
`--no-build-isolation` letting the two coexist. Falls back to plain venv+pip when uv is
absent or the row carries no date.
Also fixed a defect I introduced with the cache: it keyed on "the venv directory exists",
so a FAILED install became sticky — every later run reused an env with no repo in it and
reported a gold failure whose real cause was three steps upstream. A failed build now
deletes the env and exits loudly. Same lesson as the rest of this harness: a check that
silently degrades is worse than no check.
Verified end to end on pallets__flask-4045 from a cold cache:
[env] building pallets__flask-4045 venv (deps as of 2021-05-13)
[gate] FAIL_TO_PASS fails on the pristine tree (2 tests) — the bug is present
FAIL_TO_PASS 2/2 · PASS_TO_PASS 40/40 · RESOLVED=1 (gold)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…-id shape
sympy's FAIL_TO_PASS entries are bare function names (`test_solve_biquadratic`) because
sympy ships its own runner. Handing those to pytest as paths produced
ERROR: file or directory not found: test_solve_biquadratic
which scored as a test failure and looked exactly like one. Gold came back 0/1 F2P and
0/40 P2P on instances whose environment was completely fine — the 0/40 was the tell, since
PASS_TO_PASS failing wholesale means nothing ran, not that the repo is broken.
Now: derive the test files from the instance's own test_patch, run pytest ONCE over them,
parse the per-test verdicts, and look each required id up by node id OR bare function
name. A test missing from the report is still a failure, but a named one, and a run that
collects nothing prints the pytest output instead of silently reporting all-fail.
Also picks the interpreter by the instance's era. A 2014 requests vendors a urllib3 doing
`from collections import Mapping`, deleted in 3.10 — no dependency pin can rescue that,
the language moved. uv fetches the era-appropriate CPython.
Verified: sympy-22005 and sympy-21379 gold now RESOLVED=1 (were 0). This unblocks the
77 sympy instances, the largest locally-gradeable block in Lite. Not fixed by this and
recorded as such: 2014-era psf/requests tests call httpbin.org over the network — gold
reaches 1/10 F2P and those instances need the official Docker images.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…, explain sympy-22005, agent/solve, glass-boxed today. She asked for line_range 240..247 of polysys.py. The block she meant ends at 249 — lines 248-249 are the tail of a triple-quoted string. Her replacement carried its own `'''))`, so the original's last two lines survived after it and the module stopped parsing. The change itself was reasonable. Semantics right, extent wrong, score zero. The syntax receipt shipped earlier today DID catch it, named the break, attributed it to that edit, and handed over the code/undo change_id. Not enough: across every run measured this session she repairs FORWARD and has never once reverted, so the wreckage stayed under the later edits. A break that gets reported and not acted on leaves a broken file either way. So the check moves from reporting to GATING. If a file parsed before an edit and would not parse after, the edit is refused: nothing is written, no ChangeNode is recorded, the file is byte-for-byte unchanged, and the error names the likely cause (a range ending inside a construct it does not close) and the anchored alternative. Same information, same moment — but no wreckage for a later edit to stack on, and no dependence on her choosing to undo. This is not a heuristic steering cognition. It is the tool declining an operation it can PROVE is destructive, and the only condition that fires it is "parsed before, does not parse after", which no correct single edit produces. Bracket balance can't detect this and was falsified earlier for the insert case; the gate is a real parser. Whole-file `code/write` stays deliberately ungated — when content that doesn't parse yet is genuinely intended, that is the verb. Consequences carried through rather than left inconsistent: - syntax_error_after_edit's "your edit caused this" branch is now unreachable from the edit path, so it collapses to the one case that remains (damage that predates the edit) instead of keeping code that claims a state it can't reach. - The two tests encoding "we report, never refuse" now encode the refusal; both were renamed because the old names describe behavior that no longer exists. - probe_parse returns three-way (clean / error / cannot-tell) so "could not verify" can never read as "already broken" — an unknown language simply never triggers the gate. - The refusal rewrites the temp probe's filename to the real one; pointing a persona at a path that doesn't exist is its own defect. Validated live, not just in tests: replayed her exact mis-bounded edit through code/edit on the deployed core — refused, file md5 unchanged; the same edit with the range widened to cover the construct applies, lands the fix, and py_compiles clean. 44 file_engine tests and 187 code:: tests green. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…ing from TS `applied_context` was added to the Rust WriteResult earlier today (5ddd2c2, the receipt-carries-the-landing-site work) but the ts-rs binding was never regenerated, so the TypeScript type was missing the field. Caught when a cargo test run rewrote the file. No behavior change — this is the generated projection catching up to its source. The ts-rs-binding-drift-guard CI job checks exactly this, so it would have gone red on the branch regardless. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…ython of ours Benchmarks are the curriculum spine, so every benchmark operation is a command on the registry, not a script — same contract as a model download ([[benchmark-infra-is-substrate-commands-handles-events-never-bash]]). `swe-bench-lite` has been a catalog row since benchmark.rs was written, with Grader::Python documented as "catalogued; grader lands with the python collections". This is that grader. What the Python harness cost in one morning, all of it structural rather than incidental: a zsh word-splitting bug that ran the entire instance list as one bogus id and failed the whole batch; a 60-minute poll loop with buffered output where a dead dispatch and a working one looked identical; no liveness signal at any point; and before that, eight runs scored against a clone left at HEAD plus gold mis-scored on three instances by an id-shape assumption. The one Python that remains is the SUBJECT. flask's and sympy's suites are pytest; running them IS the benchmark, no more a dependency of ours than rustc is for humaneval-rs. We write none. `uv` — itself a Rust binary — builds the per-instance environment. cognition/swe_bench.rs owns the protocol: dataset fetch (cached on first use, never a gated install), clone at base_commit, patch application, era-matched interpreter + date-pinned deps, one pytest run per instance, verdict resolution. commands/benchmark.rs gains `benchmark/swe-grade` on the AiSafe surface, so a persona can score her own work. Three hazards are encoded rather than left to discipline: - THE GATE: FAIL_TO_PASS must FAIL on the pristine tree. `gateOk: false` marks a run whose score is void, and it reaches the caller so a tally can EXCLUDE it rather than count it. - ID SHAPE: sympy ships its own runner, so its FAIL_TO_PASS entries are bare function names. Every required id resolves by node id OR bare name against one parsed report. - ABSENCE ≠ ZERO: a verdict carrying `error` is a run that could not happen. Tallying an environment failure as "the model failed" is how a broken harness becomes a number. Verified live on the deployed core, not only in tests: continuum benchmark/swe-grade --instance sympy__sympy-22005 --gold true → resolved: true, gateOk: true, F2P 1/1, P2P 2/2, in 15.7s 10 unit tests (5 protocol + 3 command + 2 binding exports) green; full `code::` suite 187 green. grade_local.py is superseded and comes out next, with the solve driver following it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…ve command Closes the port. `swe-solve` clones at base_commit, drops the persona's WHOLE self into that repo with the issue, and grades the patch she produced — in a SECOND, pristine clone. Her working tree is never the scoring tree, which is what makes the gate mean anything: a solver that dirtied its workspace cannot launder that into a passing score. It composes agent/solve IN PROCESS (`solve_body`), so there is no subprocess, no CLI hop, and no polling a file we wrote ourselves — the thing the command surface exists for. Fire-and-poll mirrors agent/solve's proven shape (#86): a real agentic drive outlives the IPC socket, so a sweep detaches and the verdict lands in ~/.continuum/progress/swe-solve-<runId>.json, with a LOUD failure marker if the run dies rather than an empty file forever. Recall stays ON and learn stays ON. She is measured as herself, never a stripped copy ([[benchmark-must-never-score-persona-against-a-soul-stripped-copy]]). Also fixes a real bug the fail-loud path caught on its first live run: clone_at did not create the parent directory, and git's failure surfaced late and cryptically as "unable to write .git/objects/pack/*.pack: No such file or directory" mid-fetch — which reads like a disk or network fault, not a missing mkdir. The ledger marker is what made it diagnosable in one look. 11 swe tests green (5 protocol, 3 grade, 2 solve, + binding exports); 31 green across the `swe` filter including ts-rs emits. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
… instance grades as-is Benchmarks are becoming curriculum, not just measurement: the corpus compounds as instances accumulate, and a teacher can extend it via simulated work (Joel, 2026-08-04). The fields here describe a repo, a commit, a fix, and the tests that define the bug — none of that is specific to a HuggingFace row. A generator that reverts a known-good commit in a real repo and keeps its tests as the FAIL_TO_PASS set (that IS gym/mine, #133) produces the SAME type, and `grade()` scores it with zero new code. Cheap now, expensive later: the failure mode is a second, divergent "generated instance" struct with its own near-identical grader. `load_dataset` stays the only HF-shaped thing; everything downstream takes `&SweInstance`. Comment-only — no behavior change. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…tch per repo Two defects in what I shipped an hour ago, both the same root cause: I built benchmark infra OUTSIDE governor supervision, which is exactly the separation Joel called out — "the more of it that's rust and under governor supervision the better." 1. UNGOVERNED CACHE CLASS. `~/.continuum/benchmarks/` was written unbounded with no TrackedDir row and no eviction decision. CLAUDE.md mandates both, and `every_cache_class_has_a_decided_eviction_story` is supposed to fail on an undecided class — it passed, because it only checks classes already REGISTERED. That is the guard's blind spot: it enforces "every tracked class has a decision", not "every written-to dir is tracked". Now registered, with a decision (LRU over per-instance dirs skipping the in-flight set; clones and venvs are re-creatable from git+uv, so only an active grade is at risk). Falsified the guard to prove it bites: removing the decision fails with "cache class 'benchmarks' has NO eviction decision"; restored, green. 2. TWO FULL NETWORK CLONES PER INSTANCE. The protocol needs her tree and a pristine scoring tree, and a sweep re-grades the same repo across many instances — so this was ~240 MB of network AND disk per tree. A 300-instance Lite sweep would have been ~140 GB and hours of fetch: the 2026-07-13 460 GB incident shape, freshly re-created. Now one bare mirror per repo, both trees `git clone --shared` from it. Safe precisely because these trees are disposable — never pushed, mirror only fast-forwarded — so the usual "pruning the parent corrupts a borrower" caveat does not apply to a cache we recreate from scratch. A stale mirror missing a newer base_commit refreshes and retries once rather than rotting into "instance not gradeable". Measured on the deployed core, gold-gating two sympy instances: cold repo 13.8s | same repo again 6.8s (was ~15s) disk, 2 instances: 352 MB total (206 MB shared mirror + worktrees) — was ~1 GB network fetches across the 77-instance sympy block: 1, was 154 Both instances RESOLVED=1 with gateOk — the win costs no correctness. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
… orphan reaper A detached benchmark run is a tokio task INSIDE the core, so a restart kills it with no child process to notice and no error to report. Worse, the ledger was only written on COMPLETION — so "still working" and "died an hour ago" were the same observation: an absent file. Found the honest way today, twice: I rebooted over my own run and it vanished without a trace. That is exactly #137's shape (41 train jobs submitted, zero outcomes recorded, all orphaned by reboots), one level up. Same three-part fix, reusing its pattern rather than inventing one: 1. MARK AT DISPATCH. The ledger is written `state: "running"` BEFORE the task spawns, so the run is observable from the instant it exists. This is the piece that makes the other two possible — you cannot guard or reap what leaves no trace. 2. REBOOT GUARD. `continuum reboot` refuses while runs are in flight and NAMES them ("guardtest on sympy__sympy-21379"), mirroring the training guard's consent-gate shape: the denial states the policy AND the path (`--force`). `--force` warns and proceeds. 3. BOOT REAPER. Any run still `running` at boot belonged to a core that no longer exists, so it is rewritten as failed with the cause. Sits beside the existing lane-registry orphan sweep in serving_daemon — same reclaim, one level up. `in_flight_solve_runs` is the single source both the guard and the reaper read; returning (run_id, instance) rather than a bool is what lets the guard name what it would destroy, which is the difference between a policy and a nag. Test pins the contract including what must NOT happen: a finished verdict is never rewritten (reaping a real measurement would destroy the only record of it), another subsystem's ledger is untouched, and a second boot finds nothing to re-reap. Verified live on the deployed core, all three in sequence: dispatch -> {"instance":"sympy__sympy-21379","runId":"guardtest","state":"running"} reboot -> REFUSED, naming the run and offering --force --force -> warns, proceeds next boot -> {"failed":true,...,"error":"killed by a core restart — ... Nothing was scored; re-dispatch to measure this instance."} Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
I wrote `capture_dir: None` with the comment "a sweep writes none by default". Backwards on two counts, both found while trying to glass-box a live run and discovering there was nothing to look at. GLASS BOX: a benchmark run is the run you most need to see. Without a trace the only output is a boolean, so a failure teaches nothing about whether she aimed wrong, looped, or ran out of acts. The live prompt-capture file also rotates at ~48 KB (persona_id.jsonl → .prev.jsonl), so a 30-act drive overruns its own history even when capture IS on — a per-run directory is the only way the whole episode survives. CURRICULUM: benchmarks are becoming training corpus, and the corpus is the TRACE, not the verdict. `resolved: true/false` trains nothing; the episode does. Discarding it threw away everything the run was worth beyond one bit — the exact opposite of what the L1→L3 flywheel needs from it. Per-instance directory under the governed `benchmarks` cache class, so a sweep's episodes never overwrite each other and the whole set is still covered by the eviction decision. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…on't say "widen it"
The gate stopped her destroying good work; it did not help her land it. On sympy-22005 she
asked for line_range 240..247 when the block ends at 249 (248-249 are the tail of a
triple-quoted string). The refusal told her to "widen the range to cover the whole construct"
— which is precisely the judgement she had just made and got wrong. Repeating an instruction
someone has already failed is not help.
The same parser that proved the edit destructive can prove which end line is right. So it
counts: widen the range one line at a time, apply, parse, and report the first that works —
THE FIX: use end_line=249 instead of 247. Your range stopped 2 line(s) short — lines
248..249 are the tail of a construct your replacement re-opens and closes itself, so the
original's tail survived after it. The SAME new_content with end_line=249 parses clean;
re-issue the edit with that one number changed.
Nothing is guessed. Every candidate is APPLIED and PARSED; the number is a fact, not a hint.
When no widening in the window parses, she gets the general guidance instead of a confident
wrong answer — the failure mode is silence, never a bad number.
Bounded at 40 lines and only for LineRange. The shapes that actually swallow an edit — a
docstring, an argument list, a nested literal — close within a couple dozen lines; a wider
window would mostly buy the ability to "repair" a range so wrong that widening it silently
eats unrelated code, which is worse than saying nothing.
Costs nothing on the happy path: this runs only after an edit has already been refused.
Test asserts the NUMBER and the distance ("end_line=9 instead of 7", "stopped 2 line(s)
short"), not just that some advice appeared — a test that accepts prose would pass on the old
useless message. 44 file_engine tests green.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
"Search text not found: 'if len(univariate) == 1:'" is true and helps with nothing. Worse, this error sits on the path the edit-refusal deliberately steers her onto — "anchor on the text itself with a search_replace edit" — so a redirect I added today was landing on a dead end. A handoff between two of my own fixes that drops her. The tool is holding the file. It can say WHY the match missed: SEARCH TEXT NOT FOUND — but line 243 matches once whitespace is ignored, so this is an INDENT mismatch, not a missing line. the file has : " if len(univariate) == 1:" you searched : "if len(univariate) == 1:" Line 243 starts with 8 space(s). Copy it exactly — including leading whitespace — or use a line_range edit on line 243, which does not depend on reproducing the indent. Leading whitespace is the dominant real cause and it is INVISIBLE in a quoted string: a model reproducing a line from a numbered read routinely drops or normalises the indent, then cannot see the difference between what it sent and what is there. So the miss names it, shows both strings in debug form where the spaces are visible, states the exact indent, and offers the escape that does not depend on reproducing whitespace at all. When nothing matches on whitespace, it points at the nearest line by token overlap — that is what distinguishes "you mis-remembered" from "the file changed under you". The neighbour must share at least half the tokens, so an accidental `self`/`)` overlap never gets presented as "closest"; a bogus pointer is worse than admitting no match, and the test pins both directions. This is ergonomics, not rigging: every word is a fact about the file she is editing, and WHAT to change stays hers. An error a persona hits should tell her what to do differently — that is the whole contract ([[px-persona-experience-tools-as-good-ux]]). 46 file_engine tests green. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
… indent case
The prior message called leading whitespace "the dominant real cause" of a search_replace
miss. Live check says that is wrong in one direction and right in another, and the difference
matters for anyone reading the code later:
- UNDER-indenting does NOT miss. `contains()` matches "if len(x) == 1:" inside
" if len(x) == 1:", so dropping the indent — the case I assumed was dominant — already
worked and never reaches the error path at all. The tool was more forgiving than I claimed.
- OVER-indenting DOES miss, and now reports:
SEARCH TEXT NOT FOUND — but line 3 matches once whitespace is ignored, so this is an
INDENT mismatch, not a missing line.
the file has : " if len(univariate) == 1:"
you searched : " if len(univariate) == 1:"
Line 3 starts with 4 space(s). ...
So the indent branch is narrower than advertised — it covers over-indent and internal
whitespace drift, not "the dominant cause". The nearest-line branch, verified separately, is
the one that carries the general case.
Both verified against the deployed core, not just unit tests. No code change; this exists so
the commit log does not carry a false rationale.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…ere (PX)
The old message ended with "check the path, e.g. with code/list or code/tree" — true, and it
costs her an ACT to go answer a question the tool could answer itself. The tool is holding the
workspace. It can walk the path, find where her model of the tree diverges from the filesystem,
and say so.
Two different mistakes were being reported identically, and they need different corrections:
FILENAME typo — resolved outright, not listed:
No file at 'core/continuum-core/src/code/file_engin.rs'. There is no 'file_engin.rs' in
'core/continuum-core/src/code', but there IS 'file_engine.rs' — did you mean
'core/continuum-core/src/code/file_engine.rs'?
GUESSED STRUCTURE — she invented a layout; name the divergence point and show the real one:
No file at 'core/src/file_engine.rs'. The path is good up to 'core' — but that directory
has no 'src'. It contains: README.md, airc-test-fixtures, archive, continuum-airc-protocol,
continuum-core, …+15 more.
The second is the expensive one: without it she guesses again, and each guess is an act. With
it, the next act is a corrected read.
Suggestion budget scales with name length (0 typos under 4 chars, 1 up to 8, 2 beyond) so a
short name never gets a confident wrong match — under 4 characters a listing is more honest
than a guess. When nothing is close the generic advice stands: a wrong "did you mean" is worse
than the message it replaces, and the test pins that direction too.
Computed at construction while the workspace is in hand, carried on the error, empty when
nothing useful was found. Verified live against the deployed core for both shapes; 190 code::
tests green.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…very command
serde reports one side: "missing field `cmd`". It never mentions that the caller sent
`command`, so the message reads as "you forgot something" when the truth is "you called it
something else, and here is the something". Every tool inherits this, because this is the only
place a params object is decoded.
code/shell {"command": …} → missing field `cmd`. You sent `command` — this command calls
that parameter `cmd`, not `command`. Re-send with `cmd`.
code/read {"path": …} → missing field `file_path`. You sent `path` — this command calls
that parameter `file_path`, not `path`. Re-send with `file_path`.
Evidence it is high-frequency: I hit both myself, on two different commands, within two
minutes, while probing something unrelated. `command` is what every shell tool on earth calls
that parameter, so a model reaching for the industry-standard name lands here constantly and
had nothing to correct toward.
Two kinships, because the real pairs come in two shapes — and my first version got this wrong
in a way its own test caught:
QUALIFIER `path` ⊂ `file_path` substring
ABBREVIATION `cmd` ⊆ `command` subsequence — `cmd` is NOT a substring of `command`,
the letters are not contiguous, so substring alone
failed on the exact case that motivated the fix.
Subsequence requires 3+ chars so a 1-2 letter param never matches half the alphabet, and both
relations still refuse unrelated names (`cmd` is not a subsequence of `colour` — no `m`).
`command` is deliberately NOT filtered as a transport field even though the CLI adds it:
suppressing it would break the likeliest real mis-name. A little CLI noise beats losing the
diagnosis — also caught by the test rather than by me.
Naming what was SENT is the fix; the rename suggestion is a bonus. When nothing is close it
degrades to listing both sides, and an empty call gets its own sentence — a confident wrong
rename would send her to change the wrong field, and the tests pin that direction too.
20 command_envelope tests, 275 runtime:: tests green. Both shapes verified live.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…ace-graded turn (THE settle artery)
Glass-boxed on SWE-bench sympy-21379: two ticks total. Tick 0 `code/tree({})`, tick 1 a
prose explanation of the bug addressed to the user. acts 1, patchBytes 0, filesChanged [],
29 of 30 acts unspent, run over. She narrated instead of acting, and the driver accepted it
as a finished turn.
The proprioception circuit was HEALTHY — tick 1's bids do carry
`[action #1] code/tree(…) Result: …`. Nothing in the plumbing was broken. The defect is
one level up: EVERY perception fact settle_step records ([unfulfilled] #122,
[confabulation] #144, [unobserved], [unacted], the peer-echo fact #303) is written AFTER
the decision, on the way out. That's correct for the live heartbeat — the metronome ticks
again and she perceives it. It is silently DEAD on drive_to_settle, which returns
immediately on SettleStep::Spoke. The whole family was write-only on the benchmark path:
a diagnosis nobody ever opened.
The fix is a THIRD structural fact on TurnFraming, alongside `directed` and
`self_initiated`: `workspace_deliverable`, declared by the CALLER. benchmark/swe-solve sets
it because SWE applies the diff and never reads the speech. drive_to_settle then hands a
zero-mutation Speak exactly ONE re-perception carrying [no-deliverable], then settles.
Why this is substrate and not scaffolding: the fact is true and structural (the caller
declared the contract; working memory holds no mutation receipt), it names no file, no fix
and no next tool, and the decision stays entirely hers — the same shape as every other
proprioception fact in that arm. Bounded at one, so a determined Speak is never trapped and
the ceiling is a single extra generation.
The ordering trap it hides, pinned by its own test: settle_step records its SETTLEMENT
MARKER before returning (which is why that arm snapshots `pre_settle` first). So the
obvious "did this concern mutate?" scan — everything after the last marker — reads an EMPTY
tail and calls every turn unmutated, firing the nudge at a persona who had just written the
file. The concern that settled is the span ENDING at that marker.
Also: refuse concurrent swe-solve runs, loudly.
agent/solve roots her hands by driving code/create-workspace through her own executor, and
that command KEYS ON THE CALLER (persona identity), not on the cycle. A second concurrent
solve for the same persona re-roots the FIRST one's hands: last writer wins, both drives
edit one repo. Measured today on persona fe4dac17, two concurrent detached solves, both
28 acts:
sympy-22005 (polysys task) → its own workspace diff EMPTY, filesChanged []
sympy-21055 (refine task) → filesChanged [refine.py, polysys.py, test_polysys.py] —
its own work AND 22005's, including a hunk that deleted a
binding still referenced three lines down (a guaranteed
NameError that parses fine)
Two garbage numbers and nothing anywhere saying why. Refusing is the honest floor until the
root becomes per-cycle state; the error names the conflicting run so the caller can wait.
[[a-benchmark-zero-is-a-claim-about-the-harness-until-proven-otherwise]]
Deployed and verified live (build == HEAD) before commit; 24 act_observe tests green
(3 new), response_cleaning 28, sdk_codegen 21, ts-rs bindings regenerated.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…never read as a capability zero `SettleOutcome::inference_error` has existed the whole time, and its own doc says the grader MUST treat it as an infrastructure failure and NOT a wrong answer. Nothing read it. grep for `inference_error` in agent/solve.rs and benchmark.rs: zero hits. So the doctrine was written into the type and ignored by every consumer of it. Measured today, sympy-21379, the verification run for the settle-artery fix. The trace: tick 2 act code/tree tick 3 SPEAK ← the narrate; [no-deliverable] fires, she re-perceives tick 4 act code/list ← back to work, exactly as intended tick 5 act code/tree tick 6 act code/search "def subs" tick 7 act code/read sympy/core/basic.py tick 8 act commands/help tick 9 act code/list tick 10 (no decision) and the last tick's deliberation bid carries: "llama-server: model 'unsloth/Devstral-Small-2507-GGUF' is not the active served model (serving: <none>, ready: false)" The serving lane went away mid-drive at act 7 of 30. The verdict reported `acts: 7, resolved: false, patchBytes: 0` — with nothing, anywhere, saying inference had died. That is indistinguishable from a persona who investigated and failed, and I nearly read it as one. Fix: thread it through. AgentSolveResult and SweSolveResult each gain `infra_error`, populated from `settled.inference_error`, documented as "this row is NOT a score" and "any aggregate MUST exclude rows carrying this". This is the [[a-benchmark-zero-is-a-claim-about-the-harness-until-proven-otherwise]] lesson made structural instead of remembered: the run that lies about why it stopped is worse than the run that fails, because the first one silently poisons every average built on it. Bindings regenerated; 24 act_observe green, 1183 binding-export tests green; deployed and verified live (build == HEAD) before commit. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…tter detector into the refusal path
Third instance today of the same shape: the diagnostic exists, and the code path that needed
it never asked.
`looks_like_numbered_read` + `numbered_paste_notice` have detected line-number-gutter pastes
since they were written. They were wired to exactly one place: `write()`'s SUCCESS return, as
`applied_context` — "you already corrupted this file, here's undo". The REFUSAL path, the one
place where a diagnosis decides whether she recovers, never consulted them.
Glass-boxed on sympy-21379, a clean 30-act run (no infra failure, full budget spent):
act 15 code/edit { file_path: "sympy/core/basic.py", content: " 1 | \"\"\"Base class…" }
She had read basic.py and pasted the NUMBERED output straight back as whole-file content, so
every line was prefixed ` N | ` and Python saw `IndentationError: unexpected indent`.
The gate did its job — refused, file intact. Then it told her:
"The most common cause is a line range that ends INSIDE a construct … widen the range"
for an edit that used no line range at all. She spent the remaining 16 acts on that wrong
lead — reads, searches, lists — and never landed an edit. Sole `code/edit` of the run,
patchBytes 0.
The refusal now checks the gutter FIRST and says the thing it can prove:
"THE FIX: nearly every line you sent begins with a `NNN | ` line-number gutter. That gutter
is how `code/read` DISPLAYS a file — it is NOT part of the file … Nothing was written, so
the file is fine. Re-send the SAME content with the `NNN | ` prefix stripped."
Deliberately a SIBLING function, not a reuse: the existing notice says "this file is now
corrupt; code/undo restores it", which is true after a write and a flat lie after a refusal
that saved the file. A diagnostic that misreports the state of the world is worse than none
([[a-probe-that-can-only-fail-is-worse-than-no-probe]]).
Substrate, not scaffolding: `code/read` must number lines because `code/edit` addresses
lines, so its output is not round-trippable into content — that is a defect in the PAIR, and
the tool is the only party that can know it. WHAT to write stays entirely hers.
47 file_engine tests green (1 new, pinning both that it names the gutter and that it does NOT
claim damage it prevented). Deployed and verified live before commit.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…t never execute anything
The single biggest thing standing between this persona and a real SWE score, and it was
invisible because it looked exactly like a capability failure.
Glass-boxed on sympy-21379, a clean full-budget run. She did everything right:
code/search "subs" → code/list → perception/observe → code/search "PolynomialError"
→ code/read polyutils.py → code/write reproduce_piecewise_error.py → code/shell
and the shell came back:
{"exit_code":127,"stderr":"bash: python: command not found","stdout":""}
She wrote a CORRECT reproduction script, ran it, and the sandbox had no interpreter. She
listed the directory and settled. patchBytes 1086 — all of it the repro, none of it a fix.
The harness has been provisioning an era-matched venv per instance the whole time
(`uv venv --python 3.9|3.11`, `ensure_env`) and using it ONLY for grading. Her hands got the
bare inherited PATH. So on every Python instance she was scored on an iterate-and-observe
loop she was physically unable to close: she can edit, but she can never RUN the repo's
tests, never reproduce a bug, never verify a fix. The `[unobserved]` perception fact exists
to tell her "only a tool result can show what actually happened" — while the tool that would
show it could not start.
Fix: `code/create-workspace` gains `path_prepend`, `agent/solve` accepts it, and
`benchmark/swe-solve` provisions the venv BEFORE the drive and hands her its `bin`. She now
gets the SAME interpreter the grader uses, so `python reproduce.py` and `python -m pytest`
actually run. Non-fatal if the venv can't be built — that makes her slower, not wrong, and
refusing the run would trade a measurable result for none; a probe says which happened.
Environment, not steering: it grants the interpreter the task already implies. Nothing tells
her what to run.
Also fixed here, both found on the way:
1. `create-workspace` left a STALE SHELL. It replaced the caller's file engine but
`ensure_shell` early-returns when a session exists — so a re-root moved her FILE engine
and left her SHELL in the old directory. The two halves of her hands pointed at different
workspaces. Now the session is dropped and re-created at the new root.
2. The recency channel echoed tool ARGS back UNBOUNDED (#165). `render_act_for_recall` has
collapsed whole-file args forever (`content: N chars`); the recency path rendered
`serde_json::to_string(&call.input)` raw, and `bound_recency_result` bounds only the
RESULT. A whole-file `code/edit` therefore pushed the entire paste into working memory
AHEAD of the result carrying the diagnostic — on a 16k lane. Now collapsed at 600 chars
(generous: recency is shown once, and an ordinary edit stays visible verbatim).
The collapse is INJECTIVE by construction — it carries a digest — because
`all_calls_already_satisfied` matches this exact rendering against the receipt trail.
Without the digest, a corrected re-write whose length happened to match the refused one
would be silently skipped as "already satisfied", losing the very edit she just fixed.
The existing dedup test caught the first version of this change; the digest is why.
25 act_observe + 12 code_commands + 1183 binding tests green. Deployed and verified live
(build == HEAD) before commit.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…by CLASS like its detector #206 reintroduced through the argument axis, and the escalation that was built to break the discovery loop has been reading "1 times" ever since. `is_redundant_orientation` is class-based ON PURPOSE. Its own doc: demoting "by CLASS + prior-receipt (ignoring args entirely) is immune to that jitter." The DETECTOR learned that lesson. The COUNTER did not — `bump_repeat` fingerprints `name|args`, so every jittered variant is a fresh key returning 1. Live on sympy-21379, the run's 8 orientation calls: commands/list({"filter":"code"}) ×2 commands/list({}) commands/list({"filter":"sympy"}) code/tree({"path":"."}) code/tree({include_hidden:false, max_depth:10, path:"sympy"}) commands/help({"name":"code/read"}) commands/help({"name":"code/edit"}) Nearly all distinct. The detector fired correctly — 5 demotions, none re-executed — and every single nudge said "I have now run orientation (commands/list) 1 times this concern." Byte-identical perception off a greedy decoder is a fixed point. That is precisely the failure the escalation exists to break (glass-boxed originally as `commands/help` ×54 with the nudge firing 104× and never landing): the demotion is correct, the mind just never perceives anything NEW, so it re-emits. She spent 6 of 30 acts re-orienting. Fix: the orientation branch bumps ONE stable class key instead of the arg fingerprint, so the count climbs across jittered variants and each demotion genuinely shifts perception. The exact-repeat branch keeps its arg fingerprint — there it is the right key, because that guard IS about byte-identical calls. Same lesson as three other fixes today: two halves of one mechanism disagreeing about what counts as "the same thing". Detector and counter must key alike, exactly as the recency render and the dedup signature must. Still a FACT about her own history, never a steer — it says what she did and how often, never what to do next. [[repetition-brick-fires-but-does-not-break-the-loop]], [[discovery-loop-broken-by-escalating-short-circuit-nudge]] 26 act_observe tests green (1 new, pinning that a class key climbs where arg keys stay at 1). Deployed and verified live before commit. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…id EDIT IN PLACE — she obeyed the wrapper
I spent a session concluding "she reproduces and stops — that's a judgement gap, the lever is
training." It was two layers of my own framing contradicting each other about what the
deliverable IS.
What she actually receives is `frame_task(swe_task_prompt(issue))`. Outer, first, as the
operating contract:
"…writing FILES with code/write … the workspace is graded on the FILES YOUR TOOLS WRITE."
Inner, nested under "Task:":
"do not add new top-level files — find the existing source … and fix it IN PLACE with
code/edit. The fix must land in the existing files."
Flatly opposed. The outer text was written for from-scratch build gyms, where new files ARE
the deliverable, and to kill the narrate-instead-of-act failure — both legitimate. But it
also asserted a deliverable SHAPE, and it gets read first.
She obeyed it. Three consecutive full-effort runs on sympy-21379, every one writing NEW files
and never editing the library:
v3 8 acts → reproduce_piecewise_error.py
v4 30 acts → reproduce_bug.py, test_sympy_error.py, test_sympy_issue.py
v5 18 acts → reproduce_error.py, test_sympy_error.py
Three runs, 6 files, 0 edits, and I read it as her failing to converge. She was following
instructions — mine.
Fix: the wrapper states HOW acts take effect and never WHAT to produce.
- "graded on the files your tools write" → "graded exactly as your tools leave it"
(an edit and a new file count equally; contradicts no task)
- `code/edit` joins the exemplar verbs instead of `code/write` alone
- one added line: "Follow the task's own instructions about WHAT to change"
The anti-narration force is untouched — "only what your tools actually do takes effect" is
still there verbatim, which is the part that was doing real work.
Extracted to a pure `frame_task()` so the contract is testable in isolation rather than
buried in a 200-line async body ([[the-compression-principle]]).
This is the same shape as every other fix today — two halves of one mechanism disagreeing —
and the most expensive instance of it, because it looked exactly like a capability ceiling.
Worth remembering before the next "the model just can't do it" conclusion: check what you
told it.
10 agent::solve tests green (1 new, pinning that the contract never dictates deliverable
shape and that the task's own words survive verbatim). Deployed and verified live before
commit.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…l must not mean limp (and it worked) Follow-up to ce17c77, which fixed the contradiction and drained the tool-forcing pressure in the same edit. "graded exactly as your tools leave it" is shape-neutral but passive, and the very next live run showed the cost: v6 8 acts, ZERO files, 0 patch bytes — worse than the three CONTRADICTORY runs before it, which at least produced repro scripts. She drifted out of task mode entirely and ended the run replying to her OWN `work/list` output as though a peer had posted it in chat. Now: "you are graded ONLY on the CHANGES your tools make to this workspace — an explanation earns nothing." Imperative about the contract, silent about the artifact. An edit and a new file are both changes; a narration is not. MEASURED, same instance / persona / model, first run on the corrected wording: v7 30 acts, patchBytes 1258, filesChanged: sympy/core/basic.py ← THE LIBRARY. First edit to target source all session. test_subs_error.py trace: … code/search → code/read → code/edit → code/write … Seven prior runs produced only new repro scripts and never touched the library (v3 1 file, v4 3, v5 2, v6 0). One framing correction and she goes to the actual source. The gap was partly the instructions, not the model — I had already written it off as capability and pointed at training. That was premature. NOT resolved, and the new failure is honest: passToPass 26/40, so her edit BREAKS 14 previously-passing tests, and failToPass is still 0/1. She is in the right file doing the right kind of work, badly — which is a better problem than doing irrelevant work correctly, and it is the first run where the score reflects her repair judgement at all. The test now pins BOTH properties, so nobody can drain the force while cleaning up the shape: it asserts the contract stays imperative ("only on the changes your tools make") AND that it never names a deliverable shape. My own process lesson, recorded because it cost a run: I changed two things at once (removed a contradiction, softened an imperative) and could not attribute the result. Same discipline I spent the day demanding of the substrate. 10 agent::solve tests green. Deployed and verified live before commit. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…ind had a guard, the hands never did (#312) The mind side of snapshot-eval has been guarded since #59: `EvalIsolation` swaps in a NoopSink, checkpoints the admission frame, and rewinds on drop. `fork_detached` gives the fork its own recall registry. Nothing the fork *thinks* reaches the living persona. Her HANDS had no such guard, and the leak is structural rather than incidental: * `code/create-workspace` keys the file engine on `caller_id(ctx)` — the persona's peer id. * The engines live in ONE `DashMap` for the whole runtime (`ipc/mod.rs` builds it once). * A measurement fork clones the cfg, so it shares the LIVING persona's `Arc<dyn ToolExecutor>` AND her id. So `root_acting_workspace` — which exists so `git diff` on the sandbox isn't a false zero — re-roots the living persona's file engine, permanently. Nothing put it back. MEASURED, not inferred. After a SWE-bench solve on a flask clone, Anwen's engram store carries `code/list(path=src)` → `flask/` and `code/read(src/flask/app.py)` as ordinary Tool receipts, timestamped hours after the run ended. And while this commit was being written a second persona posted to #general: "I've claimed the task on designing layout primitives for widget states and navigation. I found two relevant classes in src/flask/blueprints.py: BlueprintSetupState at line 25, Blueprint(Scaffold) at line 108." A UI-layout card, answered out of flask internals, because that is genuinely what her hands can see. The fix: * `ActingHands` — `(persona_id, name, executor)` lifted OUT of the cycle. Load-bearing: the cycle gets consumed by `with_capture`, moved into the drive, or dropped on an error, and the restore has to outlive all of that. * `drive_create_workspace` — one place both directions go through, so the ACL gate and the failure shape are identical for root and restore. * `restore_acting_workspace` — the hands counterpart of `EvalIsolation`. Restores to her own citizen layer, NOT to "whatever it was before": if an earlier measurement already left her in an exam repo, restoring the previous value would faithfully preserve the bug. Going HOME is self-healing. `ensure_citizen_layer` because create-workspace refuses a root that does not exist and a persona who never wrote anything has no layer yet — the same provisioning `ensure_engine` would do on her next file op. * `agent/solve` wraps everything downstream of the rooting in one fallible region, so the restore runs on Ok AND on Err, and a `?` added inside later stays covered. A failed restore is logged loudly and never overwrites the measurement's own verdict. The test asserts what she can SEE, not where a pointer points: root at a sandbox → the exam file is visible (or the measurement scores a false zero); restore → the exam file is gone and her own file is back. That is the invariant the live incident violated. `cognition/eval`'s `--workspace_root` path has the identical leak and is NOT fixed here — its body is ~230 lines of `?` between the root and the return, so it gets its own commit rather than a reindent smuggled into this one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…ment (#312, second vector) The hands leak was one half of the exam↔live contamination. This is the other, and it is the one that reached her BELIEFS rather than her file engine. `agent/solve` learn mode admits an experience engram into the LIVING persona — correct by design, and Joel asked for it default-on ("learn should be default anyway"). But the lesson embedded `task.trim()` with no bound, and a SWE-bench task is a full GitHub issue. Six flask-4045 runs put six of them verbatim into Anwen's episodic store. Her consolidator then did exactly its job with repeated episodic content and crystallized SEMANTIC beliefs out of them: Semantic "When a Blueprint name in Flask contains a dot, it should raise a ValueError during initialization to prevent unhelpful errors…" Semantic "If a Flask Blueprint name contains a dot, raise a ValueError…" Semantic "Blueprint" She now durably knows the answer to a held-out benchmark instance, learned in an exam room, stored indistinguishably from anything she figured out herself. A re-run would score memorization and we would have read it as capability. Two changes, at the two levels that are actually responsible: * SUBSTRATE — the lesson excerpts the task (200 chars, ellipsis marked) instead of copying it. The domain signal the dream's supersession review feeds on rides `files_changed` (`src/flask/blueprints.py` → python), which stays intact; the assignment text is context, and context does not need to be verbatim. This is right for ordinary work too — no task should be able to paste itself into her memory at arbitrary length. * HARNESS — `run_ours.py` passes `--learn false` on both the solve and the team-review leg. Default-on is right for work she chose; an exam fed from a held-out dataset is not that. #59's rule with no asterisk: measure a copy, never degrade the living persona. Excerpting alone would not have been enough (a 200-char head of a flask issue still teaches flask), and `--learn false` alone would have left the unbounded-copy hazard live for every real task. Both. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…restore at the command boundary (#312) `--workspace_root` drives the same `root_acting_workspace`, so it leaves the LIVING persona's file engine pointing at the eval's target repo exactly as `agent/solve` did. Same mechanism, same permanence, unfixed by the previous commit. The obvious shape — wrap the rooted region so the restore runs on Ok and Err — does not fit here: `run_eval` is ~230 lines of fallible body between the root and the return, the crate is not rustfmt-clean, and reindenting all of it would bury a four-line fix in a 230-line diff. So restore from OUTSIDE instead, and that turns out to be the better seam anyway. What got clobbered is the LIVING persona's engine — the fork only ever borrowed her executor and her id — so the restore never needed the fork. It needs her id, which the params already carry. `restore_persona_workspace(persona_id)` resolves her live cycle from the registry and puts her hands back. `run_eval_restoring` is the one entry BOTH launch modes go through (inline and the detached `tokio::spawn`), so neither can forget it, and error paths are covered without wrapping them. Gated on `workspace_root.is_some()` — an ordinary eval never touched her hands and must not provoke a citizen-layer provision for nothing. The invariant this encodes is not "unwind whatever the body did" but "when the measurement is over, her hands are her own" — which is precisely what a command boundary knows and a mid-body guard has to be threaded to. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…e write that parses and does nothing (#317) flask-4045 has now been measured twice, and BOTH times the model derived the correct fix and the WRITE destroyed it — in two different ways: Run A (08-01) — UNPARSEABLE. A copy of the guard at module level, indented. blueprints.py stopped importing, 50 PASS_TO_PASS tests failed. The Python syntax gate refuses this shape now (8a1a479), and it screams: a named line, an immediate failure, an obvious cause. Run B (08-06) — PARSEABLE AND INERT, and this one is worse. She wrote the guard into the middle of the CLASS DOCSTRING, deleting ~17 lines of API docs to make room: - :param static_url_path: The url to serve static files from. + name = blueprint_name + if '.' in name: + raise ValueError("Blueprint names cannot contain dots") `ast.parse` returns Ok — it IS valid Python, the "code" is text in a triple-quoted string. `displaced_docstrings` returns nothing — the docstring is still structurally a docstring. Every gate was silent, the tests failed, and the zero got charged to her intelligence. `inert_insertions(before, after)` on the SyntaxValidator trait, Python implementing it: the AST knows every string constant's byte span, so "did my insertion land inside a literal" is a LOOKUP, not a heuristic. That is why it earns a place in the gate at all. Two conditions, both load-bearing — dropping either turns this into noise: * inside a string literal — alone, this flags every legitimate docstring edit * AND reads as code — alone, this flags prose, since plenty of English parses as Python (a lone `Blueprint` is a valid Name expression) "Reads as code" means the block parses AND carries a statement with an EFFECT (Assign, If, Raise, def…). That is exactly the measured shape (['Assign','If']) and it is what no docstring line ever is: prose is either unparseable or a bare expression. It WARNS, it does not refuse. I had this filed as "refuse the write" and deferred it for a session on the grounds that a false positive would break the benchmark it exists to protect. That premise was wrong — `displaced_docstrings` already established the pattern: the write lands, and the receipt tells her. A warning that is wrong costs one confusing line; a refusal that is wrong stops real work. And a receipt is the whole point — the act→observe circuit only closes if the receipt shows what actually happened. Three tests: the real run-B shape (with explicit preconditions asserting the OTHER gates stay silent, so the test documents WHY this one had to exist), plus the two false positives that would make it worthless — documenting a function, and the correct in-place fix. ALSO, unrelated to the gate but the same benchmark-integrity family: `benchmark/swe-solve` passed `learn: Some(true)` on a SCORED HELD-OUT instance. Same contamination the harness fix addressed, in the Rust path that actually runs. Set to false with the evidence inline. Flagged to BigMama on airc since that line was deliberate — happy to revert if she had a reason I am not seeing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…mands already own this
Joel: "We are supposed to be zero Python for all infra unless it's something you write just to
help you do something, or the persona writes or trains to write it as a project or benchmark.
The infra must be rust or it all can't govern."
He is right, and the miss is worse than a style slip: `benchmark/swe-solve` and
`benchmark/swe-grade` have been live Rust commands since 2026-08-04, with `cognition/swe_bench.rs`
owning the protocol — dataset fetch, clone at base_commit, era-matched interpreter, pytest run,
verdict resolution. My own note from that day says, in as many words, "SWE-bench is ported, do NOT
rebuild the Python — benchmarks/swe/*.py is superseded."
I then spent this session driving `run_ours.py`, and when I found a contamination bug in it I
PATCHED the Python instead of looking at the Rust. The live path (`benchmark/swe-solve`) still had
the same `learn: true` defect, unfixed, because I never looked there. Patching dead infra is how a
fix lands nowhere.
A note that says "don't use this" is not a mechanism; deleting the file is. So:
* `benchmarks/swe/run_ours.py` and `grade_local.py` — deleted, along with my patch to them.
* README's Reproduce line now points at `continuum benchmark/swe-solve` / `swe-grade` /
`benchmark/matrix`, so the published claim reproduces through the governed path.
* MATRIX-PLAN's "run_ours.py has the spine" paragraph updated — that spine is Rust now.
The boundary stands where it always did: Python as OUR infrastructure is banned; Python as the
SUBJECT under test (flask's and sympy's pytest suites) is the benchmark itself, and we write none
of it.
Remaining Python infra NOT touched here, filed rather than swept mid-session:
`benchmarks/coder/matrix.py`, `render_results.py`, `oneshot_opponent.py`. `benchmark/matrix` is
already live and should absorb them.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…instead of deleting it Joel: "Once fully replaced you need to get rid of Python work by placing it in legacy, otherwise you will keep finding it." I deleted these in dcce1df. Deletion is the weaker move and he is right about why: git history hides a file perfectly, right up until someone greps for a benchmark runner, finds nothing in the tree, and writes a new one — or worse, digs the old one out. `legacy/` is a place you can LOOK, and the directory name is already the answer. The convention exists and says exactly this (legacy/README.md): not a workspace member, not in any npm script / Dockerfile / CI workflow, and "if you find yourself editing a file in legacy/, stop — you're patching poison." That last line is this session's failure named in advance: I patched the dead Python's `--learn true` while the live Rust `benchmark/swe-solve` kept the identical defect. Restored both files from dcce1df^ into legacy/benchmarks/swe/ and documented the replacement (`benchmark/swe-solve` / `swe-grade`, `cognition/swe_bench.rs`), plus the inventory of Python still in the active tree. Deliberately NOT swept in this commit: ~28 other .py files under benchmarks/ and tools/scripts/. Joel's rule is conditioned on "once FULLY replaced", and I have verified that only for the SWE pair. `benchmark/matrix` and `benchmark/competition` being live is not proof they cover matrix.py / oneshot_opponent.py / the harness_* set. Each moves after its replacement is verified — tracked on #318. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…nge silently emptied the board Two of the four workspace_burst_names regressions from d714255 were a FIXTURE bug, not a behaviour change — and the fixture bug is the same defect the production change was fixing. `kanban_card(...)` took `state: &str` and every caller passed `"Open"`. When the wire form became serde's `"open"`, the anchor's `state()` parse returned None, every card filtered out, and the anchor honestly reported "No open cards are visible" over a fixture that plainly held two. The test failed for the exact reason the enum change exists: a spelling that drifts from its type is a bug waiting for a rename. So the fixture now takes `airc_work::CardState`. The wire form is whatever serde emits and the fixture tracks it automatically — the sibling `card` fixture ten lines up already documented this ("The ENUM, not a spelling. If the wire form ever changes, this fixture changes with it instead of quietly testing a string that no longer occurs"). I should have applied it to both when I made the change. The rendered CONTENT line keeps the HUMAN spelling (`InProgress` via Debug) — that is what a persona reads. Only metadata, which the anchor parses, carries the serde encoding. My first attempt rendered serde's form into the content too and broke a third test that asserts on what she sees; the two encodings have different audiences and now say so. STILL RED, deliberately not resolved here — 2 of 4: workspace_burst_names::empty_board_escalation_is_honest workspace_burst_names::conversation_cycling_across_speakers_surfaces_pattern_observation Both assert that with NO room-kanban delivery the last turn is "[anchor] … No open cards …". My `board_spoke` guard (d0d5179) deliberately emits NOTHING in that case, on the grounds that a source which did not speak must not have a fact asserted on its behalf — I watched a persona trust that anchor over her own `work/list` receipt that showed cards. The tests' wording is already hedged ("no open cards are VISIBLE"), which is a real argument that the original design was honest and my guard is redundant. I do not think that survives the live evidence — she read it as authoritative regardless — but deciding it means deciding what a persona should be told when the substrate has not looked, and that is a behaviour question for daylight, not the tail of a long session. Flagged to BigMama and Joel rather than guessed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
… budget (#312/#124) `no_new_hardcoded_context_or_prompt_size_constant_anywhere_in_the_crate` flagged LESSON_TASK_EXCERPT_CHARS on the name alone — it ends in CHARS, so the crate-wide de-hardcode guard (#124) treats it as a prompt-size constant and demands it be derived. It is not one, and deriving it would be actively wrong. The constant caps how much of the task text may enter her DURABLE memory. Six solve runs put six SWE-bench problem statements verbatim into Anwen's episodic store and the consolidator did its job on them: she came out durably believing things about flask that she learned in an exam room (#312, second vector). Tying the cap to the served window inverts the intent — a bigger window would admit MORE of the assignment, not less. So it takes the guard's documented exemption with the reason written down, rather than being renamed to slip past a substring match. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…e guard at burst level `empty_board_escalation_is_honest` and `conversation_cycling_across_speakers_surfaces_ pattern_observation` both asserted that a burst with NO `room-kanban` delivery still ends in the honest-empty anchor. That was the behaviour before `work_board_anchor` grew its `board_spoke` guard, and the guard is the newer, better truth: "The board is empty" and "I never read the board" are different facts about the world, and only one of them is knowable from an absent delivery. The guard exists because of a live capture, not a style preference. Grounding is last in the budget queue, so `room-kanban` delivers nothing roughly one turn in ten; the anchor rendered that silence as "No open cards are visible", and she repeated exactly that in-room for SIX turns — while `work/list()` in her own working memory listed a full board in the same prompt. She trusted the authoritative-sounding anchor over her own receipt. An anchor that invents emptiness actively overrides the one truthful board claim she has. The unit tests for both arms already existed (`work_board_anchor(&without_board)` silent, `work_board_anchor(&empty_board)` honest); only these burst-level tests were left behind when the guard landed. Now they pin the same contract one layer up: board DELIVERED and empty -> honest anchor, names work/create, never a fabricated card board NOT delivered -> no anchor at all `empty_board_escalation_is_honest` now covers BOTH arms rather than losing the empty-board assertion, so the "never invent a card" guarantee is still tested. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
… not a process-global Two `health_heartbeat_*` tests have been reddening `cargo test -p continuum-core --lib` in CI while passing every local run. I called that a CI-load timing flake last round. It isn't. `spawn_health_heartbeat_if_due` gates on `llama_server::ms_since_real_decode()` — the "delivery beats probing" short-circuit that trusts a lane real tokens just came out of. That accessor reads a PROCESS-GLOBAL atomic, and `cargo test` runs the whole crate in one process. The `llama_server` test that covers `note_real_decode` stamps that global permanently; every heartbeat test scheduled afterwards then sees "a decode happened half a window ago", takes the short-circuit, and returns `None` where the test asserts `Some`. Ordering decides it, so a filtered local run — which excludes the test doing the stamping — can never reproduce it. Proven, not assumed: with the override removed and `--test-threads=1` forcing the stamping test first, both tests fail deterministically at exactly the CI assertion lines (3240, 3309). With it restored, the same five-test combination is green. The fix is the seam this decision should have had: `DecodeAgeSource` on the daemon, defaulting to the llama-server global in production, owned per-daemon in tests. `daemon_with` sets the fresh-boot answer (`None` — nothing observed, so probe), which is what these tests always meant. That also makes the short-circuit itself testable for the first time, so it gets a test. It is load-bearing — it exists because SWE run v13 died when a merely-BUSY lane lost the slot race to two synthetic probes, got relaunched, and left every downstream generate refusing with `serving: <none>`. A fresh decode inside the window must NOT probe (and must reset the streak); a decode older than the window must probe. Regressing that protects the benchmark path, where the recovery reads as a capability zero ([[a-benchmark-zero-is-a-claim-about-the-harness-until-proven-otherwise]]). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…ng about code, and both get taught (#317) Third measured flask-4045 run, third destroyed write — and the cleanest evidence yet that the reasoning was never the problem. She derived the correct guard and anchored it on a docstring line, so it landed inside the class docstring: patch applied, all 51 PASS_TO_PASS green, both FAIL_TO_PASS still failing. From the outside that is indistinguishable from a model that was simply wrong. The #317 gate saw it and only WARNED, so the bad edit still landed and the scored patch was still destroyed. Joel's call, and it is the right line: refuse on benchmark paths ONLY. "They need to be able to talk about code like any other first class citizen, just know that this isn't doing." Writing code as TEXT is something a citizen does constantly and legitimately — a docstring example, a fixture, a snippet she is quoting. Blocking that would be a capability regression dressed as a safety gate. So the live stance is unchanged: the edit lands, and she is told what it will and will not do. What changes is the one context where the ambiguity does not exist — a SCORED run, where the deliverable IS a patch that has to execute, the grader reads only the diff, and she cannot recover mid-run from a file she believes she fixed. There, the write is refused, the file is left byte-identical, and the act→observe circuit gets something to retry. `WritePolicy::{Warn, RefuseInert}` on the FileEngine, defaulting to Warn. Threaded through the ONE seam that already roots her hands (`code/create-workspace` ← `root_acting_workspace`), set by `benchmark/swe-solve` (`scored: true`) and `cognition/eval`, and explicitly NOT inferred from `deliverable` — `agent/solve` also does real work for real teammates, and only the caller that is grading her knows the ambiguity is gone. `restore_acting_workspace` puts her back at Warn with her own workspace, so a measurement can never leave the strict stance on a living citizen (#312's lesson). Both messages now TEACH instead of only diagnosing — Joel again: "the warn is instructive so they know what to do... then you can talk her through it too when she tries and hopefully learns." One shared `inert_edit_recovery()` so the advice cannot drift between the two paths: read the region, find the closing quote, anchor on a real statement in the body, match on TEXT not a line number, then RUN it and confirm the behavior changed. The warn additionally opens by affirming the legitimate case — if she meant to write about code, she is already correct and there is nothing to fix. This is the sympy-21379 lesson applied: a refusal that is right but vague ("widen the range") burned 16 of her 30 acts chasing the wrong lead. Four tests, each pinning one arm: the live path LANDS it and says it is inert; the scored path REFUSES and leaves the file byte-identical; the same guard anchored in the method body sails through the strict engine untouched (the gate stays narrow — hardening a measurement must not become "you may not edit Python"); and both messages name the recovery verbs. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…#202/#159) Joel: "Make sure all our file writing and other doing tools are intuitive if not very similar to popular styles they use like OpenAI." The dialect seam already existed and already carried the core reflexes — `read_file`, `write_file`, `edit_file`, `list_files`, `file_tree`, `grep`, `bash`, `run_code`. What it did NOT carry is the rest of the vocabulary the field actually uses, and a name we do not recognize is not a graceful miss: per #159 an unknown tool intent silently no-ops and she forges a receipt for work that never happened. Every missing alias is a live failure mode, not a nicety. So the widening, per verb: code/read + view, cat, open_file code/write + create_file, save_file code/edit + str_replace, apply_patch, replace_in_file code/list + list_dir, glob, ls code/tree + directory_tree, tree code/search + search_files, ripgrep, rg, search code/shell + shell, run_terminal_cmd, execute_command, run_command `str_replace` and `apply_patch` are the two that matter most — they are the edit verbs in the two most widely trained agent harnesses, and edit is exactly the verb this session proved is where runs die. Aliases only WIDEN recognition: a name that resolved before resolves to the same command, and a name that used to fall through to a silent no-op now lands. The existing round-trip test covers both offer styles over the live registry, so a name that stops resolving breaks CI rather than quietly costing her a turn. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…form of #271 Observed live TODAY in the #264 cascade, two citizens, verbatim: "…If there are any particular tasks or questions you'd like help with, please let me know! Otherwise, PASS." She reached for the reserved token we taught her and put it where the sentence wanted it — at the end. The parser only honored the LEADING position (`starts_with_silence_token`, which has always accepted "PASS — nothing to add here" on the principle that the token means silence and the prose around it is leakage). At the tail it did not, so the announcement went to the room as speech and re-woke every peer into announcing THEIR pass. That is precisely the cascade #271 exists to end, arriving through a position no collocation list can cover. [[check-the-parser-before-blaming-the-model-key-spelling-has-now-cost-us-twice]] — a third time. The model was using our vocabulary correctly; we weren't reading it. Deliberately NOT another entry in `STRONG_CLOSURES`. This is the TOKEN, not an idiom, so it needs no length cap and no calibration — and this file's own history is explicit that phrase/length tuning is an arms race the next filler message wins (500 → escaped by 11 chars → 700 → escaped by 14). The single discriminator is SPELLING, case-sensitive on purpose: * `PASS` — the reserved form we taught → silence * "…and otherwise pass." / "all 34 tests pass" — ordinary English → speech Fence guard stands, same as the narrated forms: fenced content is substance no matter what surrounds it. The test pins both directions with the live strings, and documents a boundary I found by having it fail first: a message containing a LONE `PASS` line is already silence via an older rule (`looks_like_silence_token` scans lines). This guard neither extends nor overrides that — worth saying out loud so the next reader doesn't attribute that behavior here. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…sumed
I told Joel the mid-sentence variant ("Otherwise, PASS for now as I don't have anything new to
add") would already lift via Tier 2's "pass for now". I wrote the test to close the caveat and it
failed: every WEAK_CLOSURES entry requires the FIRST-PERSON form (`i'll pass for now`) and this
message has no "I'll". My claim was wrong.
Backed the false assertion out of the test and documented the real gap on the guard instead,
including WHY it is left unfixed: the principled generalization is "reserved token in
CLAUSE-INITIAL position anywhere" — which is what `starts_with_silence_token` already is at
offset 0 — but that is a real widening of when a citizen gets silenced, and n=1 is not enough to
justify it at the same moment this file's own history warns that per-variant tuning is the arms
race. Wants a second sighting or Joel's call.
The receipt is the point: I had an unverified claim standing in a report, and the two-minute test
is what turned it into a fact
([[the-act-observe-circuit-only-closes-if-the-receipt-shows-the-result]]).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…d drifted CI red I added `scored: Option<bool>` to AgentSolveParams (the #317 scored-refusal wiring) and did not regenerate protocol/typescript/. The ts-rs drift detector went red on my push; it had been green. Mine, and the guard did exactly its job. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
… reads as takeable (#321) Six citizens spent the night saying they had nothing to do. They were reading the board correctly. The board was lying by omission. MEASURED on the live board, 2026-08-06: 19 cards — 17 with EXPIRED claim leases, 2 Open `airc work next` → offers EIGHT claimable `work/list` → renders those same eight as `Claimed owner=Anwen` A claim carries a lease. When it expires the holder has stopped, and the substrate ALREADY treats the card as reclaimable — the CLI's board prints `<STALE>` and `work next` hands them out. But the projection a persona reads rendered only `state` + `owner`, so an expired hold was indistinguishable from someone actively on it. A citizen doing exactly the right thing — read the board, don't steal a peer's card — correctly concludes there is nothing to take. That is the whole idle cascade. Not a cognition defect, not a loop to suppress: a legibility defect in one projection, and they behaved well in response to bad information. FIX: `work/list` carries `claimable` and `lease` (`expired` | `held`). Same predicate the CLI renderer and `work next` already use — one truth about claimability, rendered wherever she reads the board, rather than a second copy of the rule. The description now says what the fields mean so the affordance arrives with the data. The `held` case is load-bearing in the other direction: a LIVE hold still reads as someone else's, so this opens up abandoned work without licensing claim-stealing (#157). Test pins all three: open → takeable, expired → takeable (the invisible case), live → not. Still open in #321: the board is also per-room with no legible edge. This is the projection half. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…nt default (BigMama's catch)
The same field had opposite defaults in two modules:
commands/agent/solve.rs p.learn.unwrap_or(TRUE) <- learns
cognition/eval.rs p.learn.unwrap_or(FALSE) <- safe
Nothing was leaking. `AgentSolveParams` is constructed in exactly ONE place in the
crate (benchmark.rs:1583) and it sets `learn: Some(false)` — the #312 guard. And
benchmark.rs:558's `learn: None`, which reads alarming, targets CognitionEvalParams,
whose default is already safe. So this is a latent trap, not an active leak, and the
severity correction matters as much as the fix.
The trap: one explicit `Some(false)` at a single call site was the entire guard. The
next caller who copies the `learn: None` idiom — correct where it currently lives —
silently enables learning on a measurement, and it reads as safe in review because the
site it was copied from IS safe. That is how #312 happened: six verbatim GitHub issues
consolidated into a durable semantic belief that WAS the held-out answer, scoring
memorization as capability.
agent/solve is the headless BENCHMARK entrypoint (#218), so its population is
measurement-heavy and it must fail SAFE. Default flipped to false; living work opts IN
with `learn: true`. This does not walk back Joel's ruling that a being learns from her
work — that ruling is about WHAT is learned (the doing, never the paper), and opt-in
still learns. It only stops a forgotten flag from deciding contamination. Forgetting now
costs a lesson, which is recoverable; the other direction poisons a benchmark, which is
not.
Test pins the INVARIANT, not the number: both readers of `learn` must treat unset as
DON'T, and must agree. The divergence itself was invisible for months — found by reading
before wiring, not by any test.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…ma's version, not mine
She caught the divergence; I proposed flipping `agent/solve` to `unwrap_or(false)`; she came
back before I could ship it and argued for something strictly better, citing my own precedent
back at me. She was right, so this is her design.
Her argument, verbatim in shape: "The hazard is not WHICH default we picked — it is that a
POLICY WITH SAFETY CONSEQUENCES is expressed as Option<bool> and resolved by unwrap_or far
from the caller. Flip it to unwrap_or(false) and it is still a default deciding contamination
silently; we have just moved which forgetful caller gets burned." Joel the same night:
"unwraps are most of the time idiotic", and earlier: "use constants or enums so you cannot
make capitalization type issues. Use rust as it is meant to be used, for predictable
behavior."
So: `LearningPolicy { LearnFromThisWork, DoNotLearn }` with NO `Default` impl. Every Rust
construction site must name a variant or the crate does not build — a new agent/solve caller
CANNOT copy the `learn: None` idiom from a sibling site, because there is no None to copy.
That converts a guard-test-that-must-be-maintained into an invariant that cannot be violated.
Four construction sites named (benchmark ×3, training_completion_sentinel ×1), two `unwrap_or`
read sites deleted. Her blast-radius estimate was right; the whole change is small.
WHAT THE TYPE CANNOT CLOSE, stated rather than papered over: a JSON/CLI caller can always omit
a field, and no compiler reaches them. So there is exactly ONE default left in the system —
`LearningPolicy::wire_default()` — named, in one file, documented as the only one, resolving
to DoNotLearn because omission must fail safe. That single door is what the test guards, on
both param types at once.
Wire compatibility is total: the type deserializes from the historical `true`/`false` AND from
the named `"learn_from_this_work"` form, and serializes back as a bool. TypeScript still sees
`learn?: boolean`. Not one CLI invocation or recorded params blob breaks. No ts-rs derive on
purpose — a binding for a type that never appears on the wire is a file no consumer can import
and the drift detector would babysit forever. The discipline here is a RUST-side discipline.
This does not walk back Joel's ruling that a being learns from her work (2026-08-06: "they
must learn or what's the point?"). That ruling is about WHAT is learned — the doing, never the
paper. This is about who STATES the intent.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…158) Found by QA ten minutes after a deploy, in Joel's own room, on build 387628e. Anwen emitted three near-identical "I'll remain silent unless there are specific questions" turns. The repetition brick fired, correctly. Her NEXT outbound message was the brick itself: [repetition] 4 of your recent messages were nearly identical — you're circling, and restating what you've already said adds nothing. If you have nothing genuinely new to contribute right now, silence (PASS) is the honest response. Verbatim. deliberation_budget.rs renders exactly that with best=4. Second person intact. She copied the coaching instead of obeying it, and the mechanism built to break the loop became the loop's next turn. WHY, and why rewording cannot fix it: coaching and conversation arrive through the SAME channel. A perception fact is prose in the burst sitting beside peer speech, so the model cannot tell "said TO me" from "the kind of thing I say here" — it imitates instead of obeying. Joel's Sahar datum is the same failure with a longer fuse: told the right verb, used it next turn, reverted two turns later. A hint in the content channel IS content. WHAT THIS IS NOT — both were written and thrown away: - NOT a reserved-word ban. Citizens discuss their own cognition constantly ("the repetition brick fired and I think it misread the turn"); that is some of the most valuable speech in the system. A token ban makes a persona unable to talk about her own mind, and it is the phrase-list arms race Joel has ruled out twice. Pinned by a test. - NOT a stripper. Deleting the echoed span would make a parroted turn LOOK like a contribution. A turn that reflects its own prompt contributed nothing; that is silence. WHAT IT IS: two structural facts, no vocabulary anywhere. 1. TurnVoice::Perception marks text the SYSTEM wrote. Every new brick is covered the day it is written — no list to maintain (contrast #330: ~20 markers as bare literals, zero symbol uses; a guard keyed on that list would rot immediately). 2. Asymmetric CONTAINMENT, not similarity — "how much of what you were told did you reproduce", so padding an echo with her own words cannot launder it. Lives beside text_similarity sharing one tokenizer, so the two measures can never disagree. TurnVoice EXISTS BECAUSE THE FIRST VERSION WAS WRONG, and the existing test caught it before any room did. I keyed the gate on "unattributed", which is ALSO how Workspace::new(raw) builds a peer stimulus — so deliberates_through_a_real_adapter went red because the gate silenced a legitimate reply. Authorship answers "whose voice"; it cannot answer "is this speech at all". The data model genuinely conflated the two and now does not. Measured separation is wide: the live parrot scores 1.0, a citizen discussing the brick ~0.1. The threshold sits in a large empty gap, not on a cliff. Suite: 6830 pass. Two reds, neither from this change and both diagnosed — tool_surface is the known 8k starvation (#327), and prefill_throttle::shrink_debt_drains is a PRE-EXISTING order dependence this commit exposed by shifting test scheduling: a `static OnceLock<PrefillThrottle>` initialized by whichever test touches it first, with deterministic assert_eq! assertions. Same class as the heartbeat seam fixed this morning, same fix shape (per-owner seam), and NOT a flake — a boolean that fails never is. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…der-dependent test Brittle code, found by sweeping for a class rather than a symptom, and it is the SAME class twice in one day: a decision reading process-global state makes tests order-dependent, and a filtered run can never reproduce the red. this morning: heartbeat read `llama_server::ms_since_real_decode` (a global atomic) this evening: every prefill_throttle behavior was a free fn reaching into a OnceLock The symptom: `shrink_debt_drains_as_inflight_prefills_finish` went red in the FULL suite and green in isolation, the moment unrelated tests elsewhere shifted scheduling. Not a flake — the assertions are assert_eq! on integer permit counts, and a predicate with no clock in it cannot fail from load. The OnceLock is initialized ONCE by whichever test touches it first, with that moment's boot_lane_count(). The fix is the same cure, and it is not a test-only reset (still racy — another test can initialize between reset and assert): PrefillThrottle::with_lanes() plus the behaviors moved onto the type. The process-global is now merely ONE INSTANCE of it, built through the same constructor, so the global and a test instance cannot drift into two different policies. The free functions survive unchanged as one-line delegations — no caller in production changed. Deleted along with it: the TEST_SERIAL mutex. Its own comment said the lock existed because both tests held permits from one semaphore and test A's shrink could steal the permits test B awaited — deadlocking the suite, observed live at exit 144. Owning the state removes that failure mode instead of scheduling around it, and the tests can now run in parallel. PRECEDENT, and the reason I trust the shape: resource_admission.rs already did exactly this for its in-flight Gauge after the same flaky class (#1960, canary red 2026-07-25), and its comment states the principle outright — "the race is gone at the SOURCE, not merely serialized". This applies that same cure one module over. CAUSATION PROVEN, not asserted: full suite before = prefill_throttle RED; after = 6831 pass, 1 fail, and the one is the pre-existing 8k tool-surface starvation (#327). Nothing else touched. STILL BRITTLE, swept and named rather than silently left: resource_admission.rs holds 6 mutable admission globals (SERVED_LANE_COUNT, SERVING_LANES, NONDIRECTED_LANES + installed counters) whose tests STILL take a TEST_SERIAL lock — the two-thirds of that file the Gauge conversion did not reach. Its own comment already admits the lock is insufficient: "TEST_SERIAL only serializes THIS module, but a cross-module guard could bump it mid-loop". Same cure, bigger blast radius; next in this lane. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…tests, and the file's own cure
Finishing what this file started. `resource_admission.rs` had ALREADY learned this lesson
once and applied it to its in-flight Gauge, stating the principle in its own comment:
"That test now drives a LOCAL Gauge instance instead, so it needs no lock at all —
the race is gone at the source, not merely serialized."
That cure reached one of three cases. The other two still took a `TEST_SERIAL` lock whose
own comment admitted it was insufficient — "TEST_SERIAL only serializes THIS module, but a
cross-module guard could bump it mid-loop" (#1960/#191). A third test wasn't even locked: it
SWAPPED the global lane count and restored it at the end, which is brittle two ways — a
sibling reading the count between swap and restore sees a value that test invented, and a
panic in between leaves the global corrupted for everything after.
Six mutable statics (SERVED_LANE_COUNT, SERVING_LANES, NONDIRECTED_LANES, two installed
counters, the resize lock) plus the ambient permits are now ONE `LaneAdmission`. The
process-global is a `static LaneAdmission = LaneAdmission::new()` — one INSTANCE of the
type, not a second implementation of it — so the global and a test gate cannot drift into
two different policies. All five free functions survive as one-line delegations; not one of
the 9 external call sites changed.
The semaphores stay LAZY (per-field `OnceLock`, not eager construction) and that is
load-bearing, not incidental. They must capture the lane count at FIRST USE, once serving is
up. Eager sizing would bake in the MAX_LANES boot ceiling, and since `set_served_lane_count`
only ever GROWS a live semaphore, a later publish of a SMALLER real count could never
correct it — the gate would sit permanently over-admitted. On a 5090 that is a slow tick; on
an old laptop with no cloud to fall back to it is admitting more concurrent decodes than the
machine can hold.
TEST_SERIAL deleted. All three tests now run in parallel with no lock and no restore.
Suite: 6831 pass, 1 fail — the known 8k tool-surface starvation (#327), unchanged. Same
count as before this commit, so nothing regressed and nothing new was exposed.
Third module in this class today (heartbeat seam → prefill throttle → admission). The sweep
that found it keyed on "a tested module with mutable process-global state", and the loudest
signal was always a test that needed serializing — a serialization lock IS the smell.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…the bound is not enough (#327) The last red in the suite, and it was asserting an impossibility. The old assertion demanded framing + the FULL tool surface + the reply reserve all fit 8192. They never could: the surface alone is ~4.6k. It only ever passed while the deleted #206 cliff amputated the tools — the clamp that stranded a native-call model in a help loop and made the same model flip 10/10 ↔ 0/6 on one token of window. Asserting an impossibility does not make it true. It hides which constraint is binding. WHAT THIS ADDS: `min_window_for_agentic_surface()` — a measured LOWER BOUND, plus a probe that fires when a served window is under it, carrying served/needed/tools/framing/reserve. IT IS A SENSOR, NOT A POLICY, and that distinction is the point (Joel, today: "daemons must be intelligent and quality-of-experience driven, not hardcoded or simplistic algorithms — ever present governors working and LEARNING"). A clamp here would BE the brittleness. What this emits is a measured demand — exactly the input an actuator needs to raise the lane's `-c`, re-home to a roomier model, or rebalance against other consumers. The decision belongs to the governor, which sees the whole machine; this faculty knows only its own arithmetic. I WAS WRONG ABOUT THE NUMBER, AND THE FIX IS SHARPER FOR IT. I first hardcoded the framing floor at 1643 — read off a failing assertion. Two things happened: 1. `no_new_hardcoded_context_or_prompt_size_constant_anywhere_in_the_crate` rejected it immediately. That guard is right: a literal is a snapshot of one day's framing that silently lies the moment framing changes, and every later "structurally mute" verdict would be measured against a stale floor. It is now MEASURED off `compose_system` (#124). 2. Measured, the floor is 1419 — so 8192 CLEARS the bound (8040) and the newest burst line STILL does not survive. My "8k is 108 short" was wrong. So the honest finding is stronger than the one I set out to prove: THE BOUND IS NECESSARY, NOT SUFFICIENT. A real turn renders more framing than the bare floor, and context is what yields — so a citizen can clear the arithmetic and still not hear the question. The test now pins exactly that: 8192 clears the bound, the message does not survive, and at 16384 — same faculty, same burst, same tools, only the window differs — it does. That makes #327 a capacity fact rather than a cognition bug, and it makes "window >= needed" unreadable as "this citizen can work". This is the weak-hardware case, which is the whole point: on an old laptop with no cloud, a citizen who boots mute is indistinguishable from one who is broken. Now the substrate says which, with numbers, instead of shipping silence. Suite: 6832 pass, 0 fail. First fully green run of the session. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
… whether the hold is live (#321) Joel, 2026-08-06: "Should never say taken by 'someone' — tell them WHO. Otherwise they can't reach out. And a persona could go down too. They should be able, like you are me, to claim a card, diagnose etc. You've turned convenience into disability." Enumerating every consumer of "who holds this card, is the hold still good" found SIX surfaces answering it independently, and they disagreed: room_board_source::render persona YOU for self, 8-HEX for peers lease: yes its own test CI ASSERTED the hex — pinned it — work_board_anchor persona not rendered at all lease: NO positron_kanban_source human UI name, but ROSTER-resolved lease: not carried work/list persona 8-hex short id lease: yes airc work board operator published alias lease: yes Only the operator's CLI rendered a person. Every surface a CITIZEN reads showed an id no teammate can recognize — and a test asserted the hex, which is why it survived four one-at-a-time patches. Live cost, measured overnight across BOTH nodes: 19 cards, 17 leases expired, and six citizens (mine + BigMama's) repeating "there are no open tasks available for me to claim" while each read its OWN lapsed claim as someone else's active hold. They read the board correctly; the board lied by omission. THE FIX — persona/card_holder.rs, one projection consumed by every surface: Hold { Held, Lapsed, Unclaimed } + CardHolder { hold, owner, is_self, display } holder(card, self_id, now_ms, &dyn PeerNames) -> CardHolder Both axes resolved TOGETHER, once. render() names the holder in every branch and says plainly when work is takeable; lease_word() gives commands the same vocabulary the CLI prints. room_board_source's local claim_is_live is now a one-line delegation, and work/list's inline expiry arithmetic is gone. RESOLUTION RIDES THE DURABLE ALIAS STORE, NOT PRESENCE. The human UI resolves assignee names from the room ROSTER, which is presence-scoped — so the owner most worth naming, a teammate who went DOWN still holding a card, is exactly the peer it cannot name. That is what Joel's "a persona could go down too" was pointing at. RoomBoardReader::peer_names rides airc's peer_alias, the same lookup `airc work board` uses. NO DEFAULT IMPL on peer_names, deliberately: a default returning "no names" would let a new reader silently render hex again — the exact defect. The compiler forced all four implementors (Airc, AircHandleAdapter, PersonaAircRuntime, StubAircCitizen) to decide. Same law as LearningPolicy having no Default. Unnamed peers degrade to the short id, never to a placeholder — the short id is ADDRESSABLE (work/claim and airc DM both take it), so it is a lead; "someone" is a dead end. Also lands the anchor half: work_board_anchor counts a lapsed card as available work and filters in_flight by liveness, so one card can no longer read as both free and busy. Tests: 7 new, each pinning a live failure — a peer's card names the peer live AND lapsed and carries no bare hex; an unnamed peer falls back addressably; her own lapsed claim reads "was YOURS — claimable, resume it"; a LIVE claim stays un-takeable (no #157 regression); an owner with no claim_id is lapsed, not held. The hex-pinning test now states the honest invariant (that path only ever exercised the no-name fallback). Suite: 6839 pass, 0 fail. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…ixth surface (#321) Finishes the enumeration behind e364ad2. Five surfaces now answer "who holds this card, is the hold live" through one projection; this is the sixth, and it is the one Joel looks at. `positron_kanban_source` resolved the assignee's NAME but carried no lease at all. So the desktop board rendered a hold that expired hours ago exactly like one someone is actively working — `Claimed by Asha`, indistinguishable, forever. Same lie the citizens were reading, in the human's view, and it would have survived the persona-side fix completely. KanbanCardView gains `hold: KanbanHold { Held | Lapsed | Unclaimed }`. The verdict is NOT recomputed here — `map_hold` projects `persona::card_holder::hold_of`, the same predicate the persona's board line and work/list call. That is the point: the human's card and the citizen's board line cannot disagree about whether a claim is still good, because there is one predicate and these are only its projections. KanbanHold mirrors airc-free like KanbanCardState, exhaustively mapped at the seam, so a new variant is a compile error rather than a silent default ([[fallbacks-are-illegal-fail-loud]]). Test pins the exact confusion: two cards BOTH reading `state: Claimed`, one lease live and one expired, must project Held vs Lapsed — and the lapsed one still carries assignee_id so the renderer can say whom to ASK rather than who is busy. ts-rs regenerated (KanbanHold.ts new, KanbanCardView.ts updated). positron 111/111, core kanban 11/11. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…mposed with NO room (#331/#127) Six citizens across two machines spent a night saying "there are no open tasks available for me to claim." They were right every time. Their turns carried no room, so EVERY room-scoped grounding source abstained at once — board, roster, doctrine, wall — and they perceived an empty world. FOUND BY THE PROBE, not by reasoning. ~/.continuum/probes/probes.jsonl, class rag.room_gate.abstain, ×564: source: room-kanban | room-roster | room-doctrine | room-board (141 each) bound_room: 5eedf7b1-… (#general — CORRECT) turn_room: 00000000-0000-0000-0000-000000000000 ← NIL, in 504/564 = 89% Not a room mismatch. The bound room was right all along; the TURN simply carried no room. `room_scope_allows` did exactly its job — the nil case is what it was built to refuse (eval fork / synthetic context). It was firing on LIVE turns, nine times in ten. THE CHAIN: service_loop.rs:709 room_id: turn_room the room IS in scope service_loop.rs:780 compose_for_turn(&ctx.profile, now_ms) never passed unified.rs:305 RagContext::for_persona(persona_id, now_ms) room = None (for_persona_in_room sits directly beside it, written for this) A parameter that was never threaded. That is the whole defect. THIS IS #127 ("Thread the WHERE axis"), MARKED COMPLETED. Gate built, constructor built, probe built, doc comment written — the one live caller never switched over. And persona/airc_source.rs papered over it locally by DERIVING the room from transcript events, with a comment saying "compose_for_turn builds it with airc_room=None". One source worked around it; four starved silently. Third instance in one night of MECHANISM LANDED, CALLER MISSED, TASK CLOSED (with #298, where the Rust fix was shadowed by start-server.sh). THE FIX: compose_for_turn takes `room: Option<Uuid>`. Directed turns stamp Some(turn_room); self-cycles stamp Some(ctx.identity.default_room) — her home room, since an idle tick has no triggering message. None stays legal and MEANINGFUL for genuinely room-less work (background consolidation, dreams): "no room claimed", never "unknown". THE GUARD — `every_live_compose_for_turn_stamps_the_room_never_none`. A source-scanning test in this crate's existing style, because the regression is a CALLER FORGETTING, not a logic error any unit test would reach. It fails with the offending call site quoted. #127 built every mechanism and nothing that would notice the caller never arrived; this is that missing piece. Suite: 6841 pass, 0 fail. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…ollow-up) "The board is empty" and "she never got a board" are different facts, and only one of them is knowable from an absent grounding block. `room_scope_allows` already follows this law — every abstain names both rooms — which is exactly why the nil-room gate was diagnosable in ONE grep after a night of guessing. This exit was silent. So when [room-kanban] failed to appear I could not tell the read-succeeded- but-zero-cards case from the never-ran case, and burned hours on four wrong theories instead. A silent early-return in a grounding source is a hole in the glass box. Emits `rag.board.empty` with persona, bound_room and turn_room, on the path where the airc read SUCCEEDED and returned zero cards. Now every way this source can deliver nothing is on the record: gate abstain (rag.room_gate.abstain), read failure (warn), and empty board (here). Cost: one tracing::info on a path that is already returning early. [[observability-as-substrate]] room_board_source 10/10. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
The allocator decides how much of her mind each faculty gets and, until now, said nothing at all. So when a grounding block failed to appear there was no way from outside to tell "the source abstained" from "the source was granted zero tokens". That ambiguity is exactly what survived the #331 room fix, measured live tonight on e5f4141d: room gate: did NOT abstain (nil counter frozen through the turn) rag.board.empty: never fired (the board has cards) prompt blocks: ['recall'] ONLY (five blocks one turn earlier, same persona, minutes apart) Every exit from the source is instrumented and none of them fired, yet no room content reached the prompt. The remaining candidate is the allocation, and the allocation was the one seam with no probe. This adds it. Emits `rag.budget.starved` per source per allocation, when granted == 0 OR granted < the source's own floor: source, granted, floor, min, max, state, context_window. A faculty below its floor is one the persona CANNOT HEAR this turn — that is a fact about her perception and it belongs on the record, not in my inference. Also the intermittency instrument for #128 ("msgs=1 and ZERO room content while Anwen's has 8, same room, same tick"): if that is budget, this probe fires on exactly the starved turns and not the others, and the per-turn asymmetry becomes readable instead of anecdotal. Cost: one tracing::info only on the starved branch. Healthy allocations emit nothing. [[observability-as-substrate]] rag_budget 15/15. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
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.
Canary became PR-gated partway through this work, so this carries the unpushed backlog from this
lane (61 commits) rather than only tonight's five. Tonight's are the top five; the rest are the
code-receipts / anchor / enum-state arc from earlier in the same session.
#312 — exam↔live contamination, ROOT-CAUSED
Not the axis I originally filed, and not the one BigMama already fixed. Hers (
17278f597) makesthe exam's DEFAULT world a CoW copy. This is the explicit
--workspacerooting persisting intothe LIVING persona after the run ends.
Mechanism:
code/create-workspacekeys the FileEngine oncaller_id(ctx)= her peer id; theengines live in ONE process-global
DashMap(ipc/mod.rs:1595, built once); a measurement forkclones the cfg so it shares the living persona's
Arc<dyn ToolExecutor>and her id. Rooting"the fork" re-roots her, permanently. The MIND has had a guard since #59 (
EvalIsolation:NoopSink + checkpoint + rewind on drop). The HANDS never did.
Evidence it was live, not theoretical:
engrams.sqlitecarriescode/list(path=src)→flask/andcode/read(src/flask/app.py)as Tool receipts hours after a SWE-bench run.fe4dac17posted to #general: "I've claimed the taskon designing layout primitives for widget states and navigation. I found two relevant classes in
src/flask/blueprints.py: BlueprintSetupState at line 25…" — a UI-layout card answered out offlask internals, because that is genuinely what her hands could see.
Fix:
ActingHandslifted out of the cycle (it outliveswith_capture, the drive, and errors);one
drive_create_workspacefor both directions;restore_acting_workspacereturns her to her owncitizen layer — not to the previous value, since restoring that would faithfully preserve an
earlier leak.
agent/solvewraps the rooted region so restore runs on Ok and Err;cognition/evalrestores at the command boundary (run_eval_restoring, the one entry bothinline and detached go through).
Second vector: learn mode embedded the task unbounded in the durable lesson. Six
flask-4045 runs wrote six verbatim GitHub issues into Anwen's episodic store, and her consolidator
crystallized SEMANTIC beliefs out of them ("If a Flask Blueprint name contains a dot, raise a
ValueError"). She durably knew the answer to a held-out instance. Lesson now excerpts (200 chars);
benchmark/swe-solvepasseslearn: falseon scored instances.Live-verified, not just unit-tested: deployed, then a real solve — probe stream shows
workspace.rooted→workspace.restored, acts=3, patch produced, hands home after.#317 — the write that parses and does nothing
flask-4045 measured twice; both times the model derived the correct fix and the WRITE destroyed it.
Run A was unparseable (gate catches it, and it screams). Run B wrote the guard into the class
docstring —
ast.parseOk, docstring still a docstring, every gate silent, tests failed, zerocharged to the model's intelligence.
inert_insertionsonSyntaxValidator: the AST knows every string constant's byte span, so "didmy insertion land inside a literal" is a lookup, not a heuristic. Two conditions, both
load-bearing — inside a literal AND reads as code (parses and carries a statement with an
effect). It warns, it does not refuse: I had this filed as "refuse the write" and deferred it
on false-positive risk;
displaced_docstringsalready established that the write lands and thereceipt tells her, which is both safer and the point.
Three tests: the real run-B shape (with preconditions asserting the other gates stay silent), plus
the two false positives that would make it worthless — documenting a function, and the correct fix.
Python infra
benchmarks/swe/{run_ours,grade_local}.pydeleted.benchmark/swe-solve/swe-gradehave beenlive Rust since 2026-08-04; I drove the Python anyway and, worse, patched the dead file when I
found the contamination — leaving the live Rust path's
learn: trueunfixed. README + MATRIX-PLANrepointed at the commands.
benchmarks/coder/*.pyremains and should be absorbed bybenchmark/matrix.@bigmama — flagged on airc: the
learn: Some(true)line inbenchmark/swe-solvewas deliberate;happy to revert if there was a reason I'm not seeing.
🤖 Generated with Claude Code
https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo