Repository navigation
A shared cycle runner for two disposable backends, and the three defects running it found - #56
Closed
kacperpaczos wants to merge 42 commits into
Closed
kacperpaczos wants to merge 42 commits into
kacperpaczos wants to merge 42 commits into
Conversation
… statuses, pattern redaction, result contract and a schema-validated manifest.
…the identity probe, project export and diff, export receipts, cleanup rails, auth bootstrap and the Orca worker launcher.
…t and export before stop, and destroy only after both are confirmed.
…it would mount and blocks on an unacknowledged host home.
…n overlay whose base is only a backing file, a seed carrying one public key, and a preflight that refuses an unverified provider schema.
…harness, and the evidence one host produced; require an orca the environment actually carries.
…d delete the transient plan.
…osure the ssh path still has.
…ustom home even for a dry run.
…ema, pool volumes, a user-mode egress interface and address discovery the runner owns.
…, and report a failed command by its tail and exit code.
…an address is not readiness.
…real machine cycle produced.
…ng, and give the guest a video device its display server can use.
…gh to the real process runner launched the Orca application on the developer's own desktop.
…play first, and stop the image racing the runner for it.
…tions apart by what the path resolves to rather than by the path.
…tory metadata out of the patch.
…replace the fixtures that had invented a flatter reply.
…cord the machine cycle that reached one.
…rule that tells them apart.
…t dead at the start.
…d rejects the sender flag the other two take.
…nd the command the plane composed for the dispatch.
…ept and the environment destroyed.
…an environment; a container inherited it and attested as the host's terminal.
…re the machine backend reaches.
A disposable environment gives Claude Code a home it has never run in, and it asks four things before its first turn: finish onboarding and sign in, trust the project copy, allow the external imports this project's own instructions declare, and accept the permission mode it was launched with. Each is a modal the dispatch cannot answer, so the agent holds the task unsent and the execution plane reports a turn that never started — the same symptom as a worker that died, with none of the cause. The answers are written from what the configuration already says rather than copied from the host, so no account, machine or history travels with them. Auth now names its settings contribution in one place, because the first-run state and the key helper share that file.
…decided the run. The completion wait answers with a delivery nested under the reply's result; the reply's top level is the request envelope. Reading the envelope found no messages, so a worker that had finished its task and reported worker_done was recorded as one that never answered, and the independent check was skipped on a run whose artifact was already correct on disk. The test fixtures carried the invented flat shape, so they agreed with the defect. They now carry the shape the plane really sends, and each of the three orchestration replies is exported beside the manifest, redacted. A verdict with no reply behind it cannot be checked by the next reader.
The worker read the prompt, wrote the marker byte for byte, and reported worker_done with outcome succeeded; the check ran outside that session and agreed; the artifacts were exported and confirmed before the box and its per-run home were destroyed. The reported outcome now comes from the worker's own payload rather than from the message type, which only repeated that it had finished.
An unverifiable dispatch says the turn was not observed and nothing more, and the same reply names the terminal it created for the agent and the command to read it. What that screen holds is the difference between an agent still thinking, one stopped at a question nobody can answer, and a launch line that was never submitted. The run now keeps it.
…ing is dispatched from it. A command line inside an environment can reach a runtime outside it. The binary, the user data path and the project path all look right, the reply carries a handle and says the surface is visible, and the terminal belongs to the host — which on this host meant tabs opening on the operator's own desktop for work the environment never did. Nothing in the reply says so. The terminal is now asked, once, and has to answer with the name of the environment it is supposed to be in. A terminal that answers with another machine, or does not answer at all, stops the run before a worker is dispatched to it.
The record said a virtual display left the host's desktop untouched. It did not. Distrobox shares the host's Wayland socket, the application chose it over the DISPLAY it was given, and because the container shares the host's PID namespace, destroying the container did not end those processes: instances from seven runs were found still alive on the host, drawing on the operator's screen. Setting DISPLAY is not isolation from a desktop, and nothing in that run observed where the application actually drew — which is why the claim stood.
…work stops. The index still said no Orca had started in either backend and no worker had been driven through one. Both are now false, and one backend completes a cycle while the other does not. The index says which is which, and keeps the earlier runs because each of them moved a rail.
The same commit, host and task that settle in a container stop here at a worker whose turn is never observed. The capture is the agent's own terminal: the launch line and nothing after it. Network, shell, submission and the first-run questions are ruled out by measurement; the agent's own silence in that guest is a lead, and the measurement that would confirm it was interrupted, so it is written as a lead.
…it is. Cleanup declared a container and a directory and destroyed both, truthfully. It never looked for what the run had started inside them, and on a backend that shares the host's process namespace those survive: instances from six runs were found alive on the host, each holding a user data directory that no longer existed, each still drawing on the operator's screen. The survey reads more than argv, because the application names no path in its own: it reads the process environment, the working directory, and the tree under the identifier the start script reported. It matches paths, never program names, so it cannot reach the operator's own application. A process it cannot stop makes the run RESIDUE rather than DESTROYED. A process identifier only means something where the environment's process table is the host's, so the adapter declares that and the machine backend declares it false. The survey is passed in rather than defaulted, so a test that does not ask for it can never signal a process on the machine running the tests.
Author
|
Opened by mistake against the upstream repository; this belongs on the fork. Closing straight away — sorry for the noise. |
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.
One shared Python runner drives one minimal proof cycle on two disposable
backends — Distrobox Assemble and a Pulumi/libvirt/QEMU virtual machine — from
one configuration, with one
run_id, real Orca in the environment, one ClaudeCode worker on a clean copy of the project, one independent check, artifacts
exported to the host, export confirmed before anything is destroyed, and only
per-run resources removed.
Everything lives under
experiments/minimal-cycle/. Nothing outside it changesexcept
docs/decisions.md.What is actually proved, and where it stops
The container backend completes a cycle. A worker read the prompt, wrote
the marker byte for byte, reported
worker_donewith outcomesucceeded, andan independent check run outside that session agreed.
SETTLED, exported,destroyed. The whole run is in
evidence/distrobox-settled-cycle/, includingthe reply that decided it.
The machine backend does not. Same commit, same host, same task: the domain
is created, a distinct kernel is proved, Orca reports ready, a coordinator
terminal opens, the dispatch is made — and the worker's turn is never observed.
evidence/vm-unobserved-turn/carries the agent's own terminal, and lists whatthat run ruled out (network, shell, submission, first-run state) and what it did
not (the agent's own silence in that guest, which is a lead, not a finding).
That asymmetry is the honest state of this work. No run is marked green on the
machine backend, and the runner classifies it
TIMEOUT, neverSETTLED.The three defects that running it found
A fresh agent home asks four questions. Claude Code asks a home it has never
run in to finish onboarding and sign in, trust the folder, allow the external
imports the project's own instructions declare, and accept the permission mode
it was launched with. Each is a modal a dispatch cannot answer, so the agent
holds the task unsent and the plane reports a turn that never started — the same
symptom as a dead worker with none of the cause.
cycle_runner/firstrun.pyanswers them by writing two small state files, from what the configuration
already says; nothing is copied from the host beyond the one allowlisted token.
The receipt names the four questions in words, because a pre-approval is a real
decision and a reader should be able to disagree with it.
The completion wait was read from the wrong half of the reply. The delivery
is nested under
result; the top level is the request envelope. A worker thathad finished and reported was recorded as one that never answered, on a run
whose artifact was already correct on disk. The fixtures carried an invented
flat shape and so agreed with the defect; they now carry what the plane sends,
and all three orchestration replies are exported beside the manifest.
A disposable environment is not disposable until nothing of it is still
running. Cleanup destroyed the container and the directory it declared and
reported
DESTROYED, truthfully — and never looked for what the run hadstarted inside them. On a backend that shares the host's process namespace
those survive: application instances from six separate runs were found alive on
the host, each holding a user data directory that no longer existed, each still
drawing windows on the operator's screen. A related escape put two terminals
belonging to a per-run directory inside the operator's own application.
Both now have rails. Cleanup surveys for processes tied to this run — by
environment, working directory and process tree, never by program name, so it
cannot reach the operator's own application — stops them, and reports a
survivor as
RESIDUErather thanDESTROYED. The coordinator terminal isasked which machine it is on before anything is dispatched from it.
Corrections to earlier evidence in this branch
evidence/distrobox-full-cycle/README.mdclaimed a virtual display left thehost's desktop untouched. It did not: Distrobox shares the host's Wayland
socket, the application chose it over the
DISPLAYit was given, and thecontainer's PID namespace is the host's. The record now says so and says why the
claim stood — nothing in that run observed where the application actually drew.
evidence/README.mdstill said no Orca had ever started in either backend. Ithas been rewritten to say what each run showed and where the work stops.
Shape of the change
cycle_runner/— one lifecycle, two adapters behind one interface, phasesfrom prepare to close, with
BLOCKED/UNKNOWN/TIMEOUTdispositionsthat refuse to claim knowledge nobody has.
host/— pinned versions, an idempotent host preparation script, and thePacker build for the tool image.
tests/— 549 tests, andcounterexamples.pywith 46 rows. Each row replacesone rail with an implementation that is present, runs and returns a pass, then
runs the tests that must reject it; every row is currently
RED.evidence/— what each run actually produced, scrubbed of the host's home,user name and machine identifiers and of nothing else.
Secrets
Exactly one declared auth method runs per cycle. No credential value, length or
prefix reaches an image, a seed, IaC state, a log, a receipt or this repository.
The one allowlisted file is copied into the per-run home, removed before
cleanup, and the removal is verified.
Gates
git diff --check,bash -n tests/contracts.sh,jqon every plugin manifest,bash tests/contracts.sh, the line budget, the absence checks and bothdisclosure greps all pass. Nothing under
skills/changes, so no version pinmoves.