Runs outlive the agent: run supervisor, run directories, adoption, late effectful verdicts (SPEC §7.5) #28
Loading…
Reference in a new issue
No description provided.
Delete branch "lu/run-supervisor"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Builds on #21 (acked delivery), which builds on #12 and #15 — until those land, this diff includes them.
What
Live-upgrades §4.1 ("runs outlive the agent") and the late-effectful-verdict part of §4.3, specified as the new SPEC §7.5 with amendments to §3.3, §3.3.1, §6.1, §6.2, §7.1, §7.2, §7.2.1.
<work_dir>/runs/<instance>-<attempt>/:run.json(contract version, assignment, run token, lease inputs, launch, supervisor pid + start time, slot, phase, acknowledged log offset, verdict),supervisor.json,task.json, a framedlog([stream u8][len u32 LE][bytes], whole UTF-8 per frame, secrets masked),exit.json(renamed in only once the tree is dead), acancelrequest file (class inside),out/stdout,w/,secrets/. One writer per file; fields only grow, anything else bumpsCONTRACT.kb-agent supervise <run-dir>(same binary, own session): spawns the task and sidecars, is its subreaper and owns its cgroup (teardown moved here fromproc.rs, which is gone), holds the slot flock by inherited fd, writes the log, honours an absolute deadline and cancel requests, writesexit.jsononly after teardown. No network, no protocol. exec: direct child; container: podman under the supervisor (rm -fon teardown, waited for); nix-drv: thenix buildclient; microVM: cloud-hypervisor + two virtiofsd sidecars, the guest's exit file ends the run. In-process agents (kb run, tests) run the same supervisor code as a tokio task.Logevents and persists the offset its acknowledged events cover; renders the NixDrv realiser log (#25) and folds daemon builds into usage; relays cancel/timeout/lease-loss ascancelrequests; afterexit.jsonruns the post-phases (collected→pushed→reported), each recorded and idempotent; sendsFinished(or the negotiated legacy sequence) and removes the directory once that event is retired.exit.jsonpresent or phase past running → resume post-phases; neither → forget, killing anything a crashed supervisor left.Hello.leasesclaims adopted and unacked runs with additiveleases_complete: true; a CP seeing it takes back at once what the Hello leaves out (assigned in an earlier second): re-queued, orlostfor effectful — which lifts the rerun refusal.Delegate=yes,DelegateSubgroup=agent,KillMode=process(module checks); runs in<unit>/runs/<token>; old fallback roots without delegation.Finishedfor an effectful instance failedlease-expired, from its assigned node, replaces the failure (through #8's infra-aware finish, logged). A late success returnsdependency-faileddependents topendingand reopens afailedgraph so it completes and posts anew; cancelled/superseded graphs are left alone. The rerun refusal stays while the run may still be going; the instance stays charged to its node until its timeout.spawn-erroronly when nothing ran;wait-errora failed wait; newsupervisor(supervisor died with the task started — infra, against the node) andlost(infra, not against the node).Why
Every agent restart (any
nixos-rebuild switchtouching the package) killed every task on the node and failed effectful ones outright. After this, a restart costs a run a few seconds of log latency.Deploy notes
leases_completeand reaps forgotten runs at lease expiry as before.docs/forgejo-setup.md.DelegateSubgroupneeds systemd ≥ 254; nixpkgs ships 257), cgroup+cpu +memoryenablement underUser=krisbuild, and survival acrosssystemctl restart; tests and CI exercise the non-delegated fallback plus in-process and real-subprocess adoption.Tests
Supervisor (exit.json only after tree death incl. orphans, with/without cgroup; cancel/deadline teardown; masking; spawn failure); run-dir contract round-trip and pinned contract-1 shape; log tail/ack/resume; in-process adoption (agent dropped mid-run → attempt 1, contiguous log; exit.json present; reboot leftovers forgotten); subprocess adoption with real
kb-agent supervise(effectful run survives its agent; adopted run can be cancelled); CPit/adoption.rs(complete Hello re-queues the unclaimed at once; late verdict replaces lease-expired, dependent revived, graph failed → succeeded, deploy never ran twice; complete Hello lifts rerun refusal); health classes; module check.🤖 Generated with Claude Code
Version skew becomes explicit and safe for the mixed CP/agent versions a staggered nuxbox/ares upgrade produces (docs/live-upgrades.md §4.4, and the `Hello.leases` part of §4.1). - `Hello` carries `proto` (absent = 0, legacy), `build` (`kb_core::BUILD`: `<version>+<rev>` from the flake, else the crate version), `caps`, and `leases`, the instances the agent is running. All additive: today's control plane ignores them. - The control plane answers every admitted Hello with `Welcome { proto, build }` (not sent to legacy agents, which could only log it as undecodable) and an immediate `LeaseAck` that renews the claimed leases exactly as a heartbeat does. It admits proto cp-1..=cp plus legacy 0 and refuses others with a 1008 close naming what to upgrade; refusals are logged and shown on /ui/nodes, /api/nodes and `kb nodes`, which also gain the agent build, proto and caps. - `kb_core::caps::required_caps(def, sched)`: an exhaustive map from every payload/input/isolation/effect/resolved-ref kind and SchedMeta enum value to a cap, empty for everything at proto 1, plus `constraints.caps`. The matchmaker requires them (legacy agents have exactly the frozen legacy set) and checks resolved refs against the chosen node. The skew rule is written into kb-core's docs and SPEC. - The bootstrap eval script is `KB_EVAL_FRONTEND=1 exec kb-eval-nix ...`: the frontend generation is identity (every eval's def_hash changes once; no kbN- bump), the eval requires `eval-nix:1`, agents with the nix feature advertise it, and kb-eval-nix (and `kb` as kb-eval-nix) refuses another generation. An environment variable rather than a flag, because today's frontend ignores it where it would reject an unknown flag - so a legacy agent is safely treated as having `eval-nix:1`. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>@krisbuild r+
Merged as
ed82c3885c.