Repository navigation
runner, server: opt-in independent Runner that outlives its gateways (P1R-C01) - #102
Merged
Merged
Conversation
…(P1R-C01) The worker takes `--lifecycle managed|independent`. `managed`, the default, is unchanged. An independent Runner is started by an operator or a harness and prepares its own 0700 socket directory. It serves one gateway connection at a time: its processes, their output, their timeouts and their exit events outlive each connection, and it accepts the next connection only after every request of the previous one has ended, a preparation or a launch included. A gateway leaves it with Detach or ends it with Stop; a connection that ends without either is a loss, and the processes keep running. SIGTERM or SIGINT stops the Runner, which waits up to 5 seconds for its processes. Hello states the lifecycle, the Runner's identity (boot id, pid, start time and a nonce), whether an earlier connection controlled it and how many processes it inherits. The worker protocol is now 8. The gateway takes `--runner-lifecycle independent` only with `--runner uds`, `--runner-socket` and `--runner-exit-intent detach|stop`, and refuses a Runner whose lifecycle differs. At shutdown it detaches or stops the Runner as the exit intent says, and SIGUSR1 always detaches. It releases no lease when the connection ends and does not reconnect: later requests fail with RUNNER_DISCONNECTED. A gateway that inherits an earlier controller's processes can query, read, write to, resize and terminate them, but refuses new executions, patches and approval resumes with RECOVERY_PENDING, checked before an approval is claimed so that the approval stays as it was. An independent gateway exits without waiting for a pending stdin read, so a signal ends it while its client keeps stdin open. With DevGuard, the independent Runner reads its consumer secret from `--devguard-credential-file`, and workspaces that require participation run only when it states that it launches. The MCP instructions and workspace_info's lifetime values follow the mode. The docs describe the mode as not supported yet, with what each event does in each lifecycle, and the CS-RG adoption step keeps naming the qualified tree. The macos-core validation stage runs the new tests. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
novelKR
marked this pull request as ready for review
October 14, 2026 14:54
novelKR
pushed a commit
to novelKR/DevGuard
that referenced
this pull request
Oct 14, 2026
P1R-C01 (novelKR/CodeSpace#102) was merged on the owner's approval as CodeSpace main feb5f52, and its post-merge CI is green. On the same approval the owner started P1R-C02. The ledger records P1R-C01 as merged-verified and P1R-C02 as active, and the dated notes say so. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This was referenced Oct 14, 2026
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.
Issue
Part of #101: P1R-C01, the first P1-RECOVERY unit. This PR does not close an issue.
mainat0f585b5, whose code tree is the qualified candidateca306ee'sc22e8236e7e065; both unchangedSummary
Before: the worker served the one gateway that started it and exited when that connection closed. Whenever a gateway shut down, was killed or lost its connection, every process it had started ended, and their output and timeouts went with them.
After: an operator can start the worker as an independent Runner. Its processes, their output and their original timeouts outlive the gateway: a gateway detaches from it, is restarted, is killed or loses its connection, and the processes keep running. The next gateway can query, read, write to, resize and terminate them with the same
process_id, but it starts no new work until the Runner is stopped and a new one is started. Nothing else changes:managedstays the default, and the in-process runner and a gateway-startedudsworker behave as before. The mode is documented as not supported yet. Reconnecting (P1R-C03) and reconciling (P1R-C04) come in later units.An operator starts the Runner, and a gateway only connects to it:
How.
crates/runner):IndependentRunnerserves one connection at a time. Exit events go through a hub that outlives connections and holds an event a lost connection did not deliver for the next one. The next connection is accepted only after every request of the previous one has ended, the DevGuard preparation and launch answered beside the others included.Detachends a connection and keeps the processes.Stop, or SIGTERM or SIGINT to the Runner, terminates every process, waits up to 5 seconds for them and exits. A connection that ends without either is a loss, never a stop. The Runner prepares its socket directory itself, 0700 and owned by its user, and refuses/,/tmp,/var/tmpand$HOME.crates/server): it checks the new settings at startup, refuses a Runner whose lifecycle differs, and at shutdown sendsDetachorStopas--runner-exit-intentsays. SIGUSR1 always detaches. It releases no lease when the connection ends and does not reconnect. A gateway whose Runner had an earlier controller refusesexec_command,apply_patchandoperation_resumebefore an approval is claimed, so the approval stays as it was. In independent mode the gateway exits without waiting for a pending stdin read, so a signal ends it while its client keeps stdin open.devguard): the independent Runner registers itself with the consumer secret from--devguard-credential-file, and the gateway hands it no secret. A workspace that requires participation runs only when the Runner states that it registers and launches.ca306eeitself), operations, agent integration, architecture and Codex reuse are updated, with the registry hashes recorded.macos-corestage ofscripts/validate-upstream.pyalso runs the new runner, worker and gateway tests on hosted macOS.Contract changes
These need the owner's approval at merge (D15). The error code names are proposed here.
--lifecycle managed|independent(defaultmanaged), and, with thedevguardfeature,--devguard-credential-file <path>, accepted only withindependent.--runner-lifecycle managed|independent(defaultmanaged) and--runner-exit-intent detach|stop.independentrequires--runner uds,--runner-socketand an exit intent, and refuses--runtime-binand--runner-dir. An exit intent is refused withoutindependent. In independent mode only, the gateway handles SIGTERM and SIGINT as a shutdown, and SIGUSR1 as a restart that detaches.Hellogainslifecycle,identity(boot_id,pid,started_at_ms,nonce),previous_controllerandinherited_processes. New operationsDetach(answeredDetached) andStop(answeredStopped { terminated }), which a managed worker refuses.RECOVERY_PENDINGfor new work refused on a Runner that an earlier gateway controlled, andRUNNER_DISCONNECTEDfor any request after this gateway lost its Runner connection.detachandstop, that state only what the Runner guarantees, including that terminating a pipe process signals that process alone (OX-01). The default text is unchanged.workspace_info.execution.process.capabilities.lifetimein independent mode:runner_disconnectiskeep_running, andgateway_shutdowniskeep_runningwithdetachandterminatewithstop. Other modes are unchanged.Tests
The brief's cases and the tests that cover them. Three of them run a pipe and a tty process side by side: the runner's
the_next_gateway_inherits_the_processes_but_starts_nothinganda_stop_ends_the_processes_and_the_runner, and the worker binary'san_independent_runner_outlives_its_gateways_until_one_stops_it. The others use pipe processes.process_id's output and status, and the process times out at its original deadlinethe_next_gateway_inherits_the_processes_but_starts_nothing; worker binaryan_independent_runner_outlives_its_gateways_until_one_stops_it; gateway binaryan_independent_runner_keeps_a_killed_gateways_process_to_its_original_timeoutan_independent_gateway_after_a_detach_starts_nothing_and_keeps_the_approval; the runner test abovea_stop_ends_the_processes_and_the_runner,a_stop_request_ends_the_connection_and_the_processes; worker binarysigterm_stops_an_independent_runner_and_its_processes; gateway binarya_gateway_leaves_or_stops_its_independent_runner_as_its_exit_intent_saysonly_an_independent_runner_with_an_exit_intent_is_accepted; gateway binarya_gateway_refuses_independent_settings_or_a_runner_that_do_not_fit; worker binaryan_independent_runner_refuses_a_shared_directory_and_unknown_settingsa_detach_or_stop_whose_answer_is_lost_still_holdsan_independent_runner_keeps_a_killed_gateways_process_to_its_original_timeouta_call_past_its_deadline_closes_the_connection_and_keeps_the_processesa_lost_connection_keeps_the_processes_and_their_exit_eventsVerified locally (cloud Linux, Rust 1.97.0):
cargo fmt --checkfor the root and every adapter,python3 scripts/check_docs.py("Documentation registry verified: 20 paired documents"), the script tests (102) andscripts/check-no-model-deps.sh.-D warnings: the root with--all-targets; the server, runner and domain withdevguard; the worker with and withoutdevguard; the patch adapter.cargo test --workspace, with the patch helper and the worker built: 361 passed, none failed,independent_runnerincluded.cargo test -p codespace-server --features devguard: 141 passed, none failed. The 5 ignored tests are the existing child-process tests, and the tests that need DevGuard's fixture authority and launch helper return early without them here; CI'sintegrationstage builds them.runtime_binary: 9 passed) and its DevGuard binary tests;cargo test -p codespace-domain --features devguard --lib: 32 passed.cargo test -p codespace-runner --features devguard --lib: 168 passed and 1 failed,launch::tests::nothing_is_committed_when_the_owner_cannot_launch. It fails here because the cloud container runs as root, so the test's group-writable helper belongs to group 0, which counts as private. This PR changes neitherlaunch.rsnoradmission.rs, and CI runs the test as a non-root user.Not verified locally: the macOS legs and the documentation site build (it needs Node 24.21.0); CI covers both.
CI: passed on this head in run 38583282332: all 17 jobs,
Integration,Linux isolationandmacOS contractsincluded. The hosted macOS leg ran the new runner, worker and gateway tests in itsmacos-corestage, and its 400 fork-race trials had no failure. The documentation site's run 38583283114 built the site.Security scenarios
/,/tmp,/var/tmp,$HOME). It removes its socket when it stops, and the directory if it created it.Helloholds a boot id, a pid, a start time and a random nonce, none of them secret.RUNNER_DISCONNECTEDinstead of pretending the processes ended, and a request in flight when the connection ended keeps the uncertain result it has today (dispatch_status: unknown). A gateway on a Runner it did not first control starts nothing, and an approval it refuses to resume is not consumed.Out of scope
Claim(P1R-C03), process records (P1R-C02), reconciliation (P1R-C04), Runner loss handling (P1R-C05) and the qualification harness (P1R-C06).Requested by Seongjae · project thread