Git LFS's pre-push hook rejected the scp-style host:path remote and stalled
every Aetheria verify (found by Imagination and Soul, 2026-09-30). The mirror
never needs LFS objects; jobs that need one smudge it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On 2026-09-30 three hung test binaries held all three slots for up to an hour
at ~2% CPU while Yggdrasil sat at load 1.5 of 16. The operator wants
Yggdrasil pinned. CPU caps now oversubscribe on purpose (jobs run niced);
memory is the ceiling, so the per-job cap drops to 8 GiB. The watchdog samples
docker stats each minute and kills a container under 5% of a core for IDLE
seconds (default 900), reporting it red.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The watchdog subshell reported its own killed sleep as "Terminated" on
every job, successful or not. Agents read that as the job failing: one
Hands reported "the script reports exit 0 after Terminated even when the
job fails", which is false. Tested: exit 3 gives rc 3, and true gives rc 0.
Now the subshell's stderr is /dev/null and the TIMEOUT notice goes out
through fd 3. Checked: TIMEOUT=20 on sleep 60 prints TIMEOUT and exits
137, and a clean run prints no stray line. The watchdog is also reaped
with wait.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A Hands pass on Aetheria Cut 12.3 edited and built the main checkout by
using absolute Read/Edit paths, while its shell sat in the worktree.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Operator ruling, 2026-09-25: Epiphany and Aquarium call this organ Life
(role id `reorientation`), and it is "a much better fit as the actual
load-bearing organ". The eureka-site landing page already says Life.
SKILL.md (frontmatter description, faculty table, dispatch lines, step 6,
model allocation), README.md and briefs.md now say Life. The role itself is
unchanged. SKILL.md defers to global doctrine for the agent type, which the
operator owns and which still says mind-steward. Dated records keep the old
name: changelog entries as written, a note on the 2026-09-15 Epiphany
comparison, and "then called Mind Stewards" in the CultCache postmortem.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Operator direction, 2026-09-25: moderate inference spend mainly by making
cuts smaller, so each Hands agent carries less context. Sonnet degrades at
about 0.5M tokens.
SKILL.md step 3 now owns cut sizing: Self estimates the brief, the source
Hands must read, one verification and one fix round, and the item count,
treats 0.5M as the danger line, splits when the estimate passes about half
of it, and checks the reported token total afterwards. The Self-owned
clauses ("Self sizes a cut", one fix batch per file) moved out of the Hands
brief template into step 3; the Imagination template's split clause names
the budget. Section 5 no longer frames the 2026-09-23 spend as misplaced:
the operator counts it as legitimate work, the regret being the model.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From the StreamPixels transport campaign, 2026-09-23 to 2026-09-24:
- SKILL.md section 5: quota is spent through scope. One Soul gate per cut,
fix batches only for critical-path findings, and narrow passes for
small deltas.
- briefs.md: one fix batch per file (800k and 550k runs); require a
non-zero test count for each named file; a failure called
"pre-existing" still needs a one-line cause.
- changelog: the evidence, with StreamPixels commit refs.
Staged by path. The unrelated uncommitted Ghostlight hunks in briefs.md
and changelog.md are left in the working tree.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The watchdog added in 9359e9c ran in a subshell that inherited the slot
lock's file descriptor. When a job finished early, `kill $watchdog`
ended the subshell but not its `sleep` child. That orphaned sleep kept
the slot for up to TIMEOUT seconds. Two slots were held this way on
2026-09-23, and four jobs queued behind them.
The subshell now closes the lock fd before sleeping, and the cleanup
kills the watchdog's children before the watchdog itself.
Tested on Yggdrasil: a fast job exited 0, and a TIMEOUT=15 hang exited
137 with the TIMEOUT line. Afterwards no sleep process held any
slot lock.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Investigating a QUIC interop failure (native bridge client vs .NET managed
server, both on libmsquic, both over loopback) needs to test IPv6-disabled
Docker networking as a hypothesis, which the script had no way to express:
no docker-args hook existed. DOCKER_ARGS is forwarded verbatim into the
container's own `docker run`, word-split by the remote shell, appended right
before the image name — e.g. DOCKER_ARGS='--sysctl net.ipv6.conf.all.disable_ipv6=0'
or DOCKER_ARGS='--network host'. Empty by default, so every existing caller is
unaffected.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
A Cut 4 test process sat at 0% CPU for 25 minutes on Yggdrasil. It had
no JS work left, but its event loop never exited. The Hands agent
waiting on the job was never woken, and nothing moved until Self went
looking.
The job's container is now named, and a watchdog kills it after
TIMEOUT seconds (default 3600). Killing the docker client alone would
have left the container running. A timed-out job prints a TIMEOUT line
and exits 137.
Tested on Yggdrasil: TIMEOUT=20 on `sleep 90` exited 137 with the
TIMEOUT line, and a fast job exited 0. No containers were left behind.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A job piped into tail loses its exit status, because the command runs under
bash -c and the sentinel then reports the pipeline's zero rather than the
job's. That is the same false-green the CRLF fix closed, arriving by a
different road, and it cost a Soul run today.
The second is smaller and just as costly to rediscover: CultLib has no
Cargo.toml at its root, so a rust job must cd into the package first or cargo
fails in a way that looks like a broken image.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A brief said "use ygg-verify.sh" where earlier ones gave the full path. The
agent could not find it, verified the whole batch on the operator's
workstation instead, and said so plainly rather than claiming the bar was met.
A name is a guess; a path is an instruction. The brief must also say what to
do when a named tool is missing: stop and report, never substitute, because a
substitution can silently relocate work onto a host the load budget forbids.
Second scar. A redundancy ruling of Self's was wrong for the second time in
one campaign, and this one shipped: a guard was deleted on the premise that
aliases are unique per descriptor, which the source contradicts, and a legal
document shape then faulted on every hop path. Both Self and Hands reasoned
from the same unchecked property. Build the input that would need the guard
and watch the other authority handle it, or the argument is just the claim
restated.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Soul lost a run to CRLF and it still exited 0. The cause: this file's own line
endings travel with the heredoc, so the remote bash fails on `set -euo
pipefail\r` before `set -e` is in force, limps past the failure and exits
clean. A verification runner that can report success without running anything
is worse than no runner.
Two guards, because one was not enough to notice this for a day. The remote
now strips carriage returns before bash sees them, and it prints a verdict
sentinel the local side requires; a missing sentinel is treated as failure
rather than as silence. A .gitattributes keeps the file LF on disk so the
first guard is rarely needed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The same commit mutated three times at -j4 gave three different survivor sets,
while every -j1 run reproduced the same one. Likely the shared cargo target
directory under concurrent builds, which is this file's existing scar wearing
a new hat.
A score survives concurrency roughly intact, because a genuinely killed mutant
stays killed. A survivor list does not, and survivors are what drive rulings.
Stryker's concurrency flag is in the same position, so figures taken at four
in earlier campaigns are provisional; the rulings they informed rest on the
hand-confirmed mutations beside them.
Second entry: three fixtures in two days passed by coincidence rather than by
rule. Values whose bit pattern happened to match the assertion, hashes that
happened to sort the right way, a file wrapped in a directory so a different
path ran. Breaking the rule and watching the test fail is what separates these
from coverage, and it is already in the brief.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three Soul passes over one file and a selection batch found the same thing
three ways. Every artifact-digest test wrapped its file in a directory so a
different code path ran, while production passes a bare file immediately
before launch. A row-tiebreak test passed with the rule reverted and swapped,
because of which real hash happened to sort first. An xattr test pinned
presence while the privilege lives in the value.
None of these looked like gaps. They looked like coverage, which is why they
survived. Write the fixture at the shape production uses, and derive keys from
ids discovered at runtime rather than hoping their order cooperates.
Second scar, same campaign: a counter was an honour system, so it became a
port, and the port is an honour system one level up. Soul restored the bug
around it with the suite green. Where a rule says all access goes through one
place, the enforcement belongs in the build, not in a habit. If the fix is
another thing to remember, it is the same defect one level up.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three scars from one Idunn batch.
A test pinned a cost regression with a ratio whose clamp always fired, so it
was really a fixed 200 ms threshold, on a host that now runs three jobs at
once. Clean code had already measured 198.9 ms. The reported 33x separation
was an artifact of one pair straddling the clamp; the real figure was 1.63x.
Count the calls instead, and treat a reported ratio as a claim to check rather
than a result to accept.
Two fixtures in the same batch would have lied, and mutating caught both. A
setuid fixture chowned after chmod, which silently clears the bit. An
observe_frozen test tampered where a different check also fires, so it stayed
green with the check it pinned deleted. A third term could not be pinned
honestly at all, and Hands deleted the fixture rather than keep one that
implied otherwise.
Last, Self ruled a mount carry nosuid without checking that Docker offers it.
It does not, and the stronger mitigation was already in place. A ruling naming
a flag is a factual claim about a tool.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Soul reported four containment checks where one sufficed, each surviving
deletion on its own, and Self ruled three deleted. With all three gone a
symlink whose whole target is ".." at the root escapes containment. The method
was sound; the conclusion did not follow from it. A redundancy finding has to
be probed at the combination that will actually be deleted.
A Hands hand-trace caught it, because the brief says a deletion judged unsafe
comes back with evidence rather than being quietly kept. That rule stays, and
it applies to deletions Self ordered.
Second scar, same batch: Soul's HTTPS rig had evaporated by the time Hands
was told to commit it. Probes stay throwaway, but once Self rules one becomes
a committed test it is no longer scratch, and the handover has to happen while
it still exists.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A QUIC test suite passed every scenario on Windows against an unrelated
msquic.dll picked up from PATH, a tenth the size of the pinned build. The
same missing copy failed loudly on Linux. Windows' search order makes silent
substitution the default failure mode.
Two rules follow. A verification step that loads a pinned native library
reports what it actually loaded, by size or hash, rather than only that the
run passed. And a defect found on one platform gets checked on the other
before it is closed, because both earlier fixes for this lived inside
if(NOT WIN32) and nobody looked.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two Hands were dispatched into one working tree. The second switched the
branch out from under the first's running build, then found an uncommitted
edit it had not made and stopped rather than forcing the checkout. Nothing was
lost because of that refusal, not because of anything Self did.
The rule was already written and Self ignored it, so the fix is not a louder
warning to Hands. A brief that names a repo path without naming a worktree is
a defect in the brief.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two scars from one Hands report. The map it read was five days old because
Self commits maps to whatever branch it is standing on; SKILL.md now says
main, and says every brief names the map's branch.
The same agent refused a mid-task instruction from Self that it could not
distinguish from an injection. The instruction was genuine. The fix is not to
make the agent more trusting: scope expansion should arrive as a new dispatch,
not appended after a tool result.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A Mind Steward pass falsified SKILL.md's claim that the stopgap runs at most
two jobs at once. The operator raised it to three this morning and the script
carries that; SKILL.md did not. An agent steering by SKILL.md would have
under-parallelized verification and quietly contradicted an operator ruling.
The changelog's entry for the same day is worse: it records caps of 6 CPUs and
16 GiB, which match neither the script nor SKILL.md. I cannot tell whether
that was ever live. The correction says so rather than inventing a history.
Both surfaces now name ygg-verify.sh as the owner of its own numbers. Prose
that restates a running tool's configuration goes stale in silence, which is
how three surfaces disagreed inside one day.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The operator: "throw in a third slot, the other cores are mostly sitting idle".
Verification now takes 12 of Yggdrasil's 16 CPUs at most, and the live
services keep four.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Operator ruling 2026-09-22: better nothing than a harness that punishes
refactoring. Behavioural tests plus the ecosystem's mutation tools on a cut's
diff; Soul's hand mutants stay one-off probes. The stopgap image carries
cargo-mutants instead of pwsh and is tagged by its Dockerfile's hash.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A worktree's .git points at a host path the container cannot see, so
Ghostlight's build.rs failed asking git for the commit. Editing the running
script in place killed a job with 'unexpected EOF': bash reads scripts from disk
as it runs, so new versions go in by rename.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A job that found both slots busy blocked on slot 1 alone while slot 2 freed,
and ssh sessions whose remote end was gone hung on Starfire for over an hour
with no keepalive to notice.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Hands and Soul measure suites with Stryker.NET, cargo-mutants, StrykerJS or
mutmut scoped to the cut's diff, and triage every survivor by name. The
hand-written anchor harness is demoted to code no tool reaches. Evidence in
the changelog: Aetheria's 23 harnesses stranded on refactors and one anchor
silently re-targeted; a Stryker spike killed everything they defended and
found the splash-handedness bug they missed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Soul found 36 Idunn tests fail without it, so Hands' numbers only reproduced
after a hand-made machine-id inside the container.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Records the three defects Soul's H14 pass found and this pass fixed:
the startup-repair marker check for H1's kept sidecar, the unique
overwritten-copy naming, and the case-sensitive content/hash/no-op
comparisons. Points at the fixing commits.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Every hash comparison in the script (sidecar verification, mutant
verification, restore verification: Assert-TargetUnedited, Restore-
Targets, the startup repair loop, and M0) used PowerShell's plain -eq/
-ne, which is case-insensitive. Hashes are hex strings where case never
carries meaning here (Get-Hash and Get-BytesHash always produce a fixed
case each), so this was latent, not exploitable -- but it left every
"is this file still what I think it is" check one string-comparison
operator away from silently accepting a corrupted file whose hex
happened to differ only in case. Switched all eight sites to -ceq/-cne.
Hash case is chosen to matter, both for defence in depth and because
these functions are the harness's only proof that a restore or a
mutant write landed the intended bytes.
The no-op guard that rejects a mutation whose old and new text come out
identical also used -eq: `if ($old -eq $new)`. Under case-insensitive
comparison a mutant that differs from the source only in case, such as
`"query"` to `"Query"`, compared equal and was refused as a no-op even
though it is a real source change. Switched to -ceq.
Anchor matching (Measure-Sites, and the splice-site IndexOf that
follows it) already used [System.StringComparison]::Ordinal, which is
case-sensitive; checked and left unchanged.
Demonstrated in a scratch repo (demo-case) with a single-mutation suite
whose only edit is `"query"` -> `"Query"` against `fn f() -> query { 1
}`, and a fake test command that fails unless the unmutated lowercase
text is present (i.e. is killed by the mutation).
Before: "CaseDemo: replacement changes nothing in t.txt.", exit 1,
mutation never ran.
After: the mutation runs, the fake test fails against the mutated text,
verdict "killed", M0 green, exit 0.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
H1 (Assert-TargetUnedited, :~166-220) keeps a target's sidecar rather
than removing it when it catches an edit the harness did not make, so
the M0 original survives beside the lost edit. Startup repair (the
sidecar loop after "Repair a previous run that died mid-mutation") could
not tell that kept sidecar apart from a crash's: on the next run it
restored the M0 original over whatever an operator had since hand-
cleaned into the target, moved the operator's clean file to
`.eureka-mutation-overwritten` (clobbering H1's own copy there before
the F2 fix), printed "a previous run died mid-mutation", removed the
sidecar, and finished green. The hand-cleaned edit was gone one run
later with no non-zero exit to notice it by.
Get-H1MarkerPath names a marker, `<target>.eureka-h1-stopped`, that
Assert-TargetUnedited now writes beside the sidecar whenever it keeps
one. Startup repair now scans for these markers before touching any
sidecar; if any exist it refuses the whole pass, restores nothing,
prints which files to reconcile and where their marker is, and exits
non-zero. An operator deletes the marker (and the sidecar, once done
with it) by hand to let repair run again.
Demonstrated in a scratch repo (demo-f1): H1 fired (a fake test command
appended stray bytes to the target mid-run), leaving a sidecar, a
marker, and an overwritten copy. The target was then hand-cleaned to
"alpha-HANDS-EDIT" and the harness rerun.
Before (original script, ignoring the marker): exit 0, target silently
reverted to the M0 original ("alpha"), the hand-cleaned edit gone.
After (fixed script): exit 1, "Startup repair refuses to run: 1 file(s)
were stopped by H1 in a previous run, not crashed...", target still
"alpha-HANDS-EDIT", sidecar/marker/overwritten all untouched.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Assert-TargetUnedited (tools/eureka-mutations.ps1:172-173 before this
change) and the startup sidecar repair (:342) both wrote an unconditional
"$path.eureka-mutation-overwritten" whenever they preserved bytes that
were about to be overwritten. A second EDIT LOST or a second dead-mid-
mutation repair against the same target silently destroyed the first
preserved copy -- and so did an operator's own file already sitting at
that name.
Get-UniqueOverwrittenPath now picks the first unused name in the family
(".eureka-mutation-overwritten", then ".eureka-mutation-overwritten.1",
".2", ...) before either site writes. Every preserved copy now survives
beside every other.
Demonstrated in a scratch repo (demo-f2): a file was planted at
t.txt.eureka-mutation-overwritten holding "PRECIOUS-PRE-EXISTING-COPY",
then an H1 edit-lost event was triggered against the same target.
Before: the planted file's content was replaced with the newly-lost
bytes; only one copy survived.
After: the planted file is untouched (still "PRECIOUS-PRE-EXISTING-COPY")
and the new loss lands at t.txt.eureka-mutation-overwritten.1; both
copies survive.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Restore-Targets never checked what a target's bytes actually were before
restoring over them, so a build step or stray edit landing mid-run or
between entries was overwritten without a trace (Soul's finding on the
Huginn memory-organ Cut 10 fourth fix batch, 2026-09-22; that same batch
had lost a Hands edit this way). Restore-Targets and the entry loop's
pre-write step now hash the target first: a match against the M0 original
or this entry's own mutant restores as before, anything else is saved to
<target>.eureka-mutation-overwritten, warned about loudly, and fails the
run with a non-zero exit and no further entries.
Demonstrated in the scratchpad with a throwaway repo whose test command
appends a line to the target mid-run: pre-fix the run went green and the
target came back pristine; post-fix it failed loudly and kept the
appended line. Recorded in references/changelog.md.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>