feat(orchestrating-daemons): host-stamped registry + host-aware pid liveness (managed-agents steals) - #10
Conversation
…iveness — managed-agents steals Adopted from Anthropic's 'Scaling Managed Agents: Decoupling the brain from the hands' (transfer analysis in docs/doperpowers/2026-07-12-managed-agents-steals.md): - Every registration/resume stamps `host` (DAEMON_HOST, default hostname) into daemon metas; _pid_alive() in _lib.sh treats a foreign-host pid as dead regardless of kill -0 (a registry migrated on a state volume carries pid numbers, not processes). Empty host = legacy meta = local. - All four pid consumers routed through host-aware checks: codex-resume one-turn guard, daemon-retire kill, review/land-dispatch _is_live dedupe, review-dispatch _wt_occupied worktree-removal guard. - Steals doc also records the state-volume convention (steal 2) and the token-wired-remote scope-split recipe (steal 3, narrows TECH-DEBT accepted note on GH_TOKEN in worker env — pointer added). - Tests: foreign-host pid → resume proceeds + re-stamps, retire leaves the unrelated process alone, dispatchers retire+respawn; same-host controls unchanged. All 5 suites + shell lint green.
… soul, seeding contract Runnable form of the steals doc §2 (state/compute separation): - infra/worker-host/cloud-init.yaml — disposable body for a CX43/Ubuntu 24.04 host: blank-only volume format + /data mount before user creation, worker user (fixed uid, home ON the volume, no sudo), Node LTS, gh, codex, claude (native installer as worker), tailscale, swap. No Docker by design (host- process registry model, codex Landlock on a real kernel, cloud-init IS the reproducibility). - infra/worker-host/env.example — seeding contract: two-token scope split (board token in env, push token only in clone remotes), setup-token / auth.json paths, SSL_CERT_FILE pin. - infra/worker-host/README.md — layer split, hcloud creation, seeding checklist, Linux verification gate (dogfood cell), body-rebuild drill. - _codex_launch + review-engine.sh: SSL_CERT_FILE probe now also covers /etc/ssl/certs/ca-certificates.crt — TECH-DEBT #8 RESOLVED. Suites: codex-scripts, review-engine, shell lint — green; cloud-init YAML syntax-checked.
…ng contract First production tenant of the worker host: the triaging-feedback poller moves off the Mac's launchd onto the VM as its burn-in workload. - cloud-init: triage user (uid 1011, /data/triage — separate from worker because its env holds SUPABASE_SERVICE_ROLE_KEY while worker executes PR-derived code); doper-triage systemd oneshot+timer replacing the launchd label (same single-instance serialization the reclaim design needs); ExecCondition on the seeded .env makes pre-seed ticks silent no-ops and rebuilds zero-touch. - env.triage.example: skill-dir .env contract — VM deltas vs Mac (explicit GH_TOKEN issues-scope since no keychain; contents-READ-only PAT in the ida-solution remote; triage never pushes). - README: §5 tenant seeding + one-tick verification + Mac handoff; rebuild drill and stale feedback-intake bullet updated.
|
Third commit 76cbd9e: triage tenant layer — the triaging-feedback poller becomes the worker host's first (burn-in) tenant.
Cloud-init YAML validated; shell lint green (no shell changes). |
|
Integration re-verified against current main (351bf0d): |
…nv/label, honest PAT boundary Five findings from PR #10 review, all confirmed against the code: - Static hostname doper-worker-1 broke host-aware liveness on the rebuild path (_lib.sh stamps $(hostname); same name = old metas read local, a recycled pid could alias an unrelated process). runcmd now sets doper-worker-$(machine-id prefix) — unique per body since machine-id is minted at first boot — with preserve_hostname guarding per-boot reverts. - Runner registration label doper-worker never matched the shipped workflow's runs-on [self-hosted, claude-review]; jobs would queue forever. - Runner jobs read ~/runner/.env + .path, not .bashrc (non-login; Ubuntu .bashrc early-returns non-interactive). §2.6 now seeds both; the cloud-init comment claiming bash -lc coverage is corrected. - Rebuild drill said 'restart the runner service' but the svc.sh systemd unit lived on the discarded root fs — drill now reinstalls it. - README claimed the push PAT never enters worker reach; any worker-uid process can read it via git remote get-url. Text now states the honest boundary (workers ARE contents-write principals) and requires branch protection before first run.
|
Review verdict (5 findings) addressed in 447e9d1 — all five confirmed against the code before fixing:
cloud-init YAML validity re-verified after the edits. |
Second review wave, both findings confirmed: - nofail (kept: a missing volume must degrade to a reachable system, not systemd emergency mode) let provisioning 'succeed' with /data on the disposable root fs — the users module creates the homedirs there before runcmd. runcmd now opens with a mountpoint assertion that aborts loudly; final_message and README §1 direct to 'cloud-init status --long' before seeding instead of claiming readiness. - README §2.2's scp to worker@host always failed: worker has a locked password, no authorized keys, and no ~/.codex. Documented the real path — through the login account, install(1) with owner/mode set from birth, no temp-file exposure.
|
Second review wave (2 findings) addressed in 16a8b2d — both confirmed:
YAML validity re-verified. |
…ovisioning Third review wave, all three confirmed: - The uid boundary relied on the image's login.defs default home mode; anything group/other-traversable lets triage read worker's clones (push PAT in each .git/config). runcmd now chmods both homes 0700 (idempotent against an existing soul), and the runner .env recipe adds chmod 600 — it carries the same tokens as ~/.env but was born under default umask. - A fresh soul has no git identity and git refuses to auto-detect email from the machine-id hostname, so a worker's first commit dies with 'Author identity unknown'. Seeding §2.4 now sets user.name/user.email once per soul (~/.gitconfig rides the volume). - runcmd merged into one sh script with no set -e meant a failed NodeSource/Tailscale/claude installer still yielded 'cloud-init status: done'; curl|bash additionally masked download failures. First item is now set -e (governs the whole merged script), curl|bash split into download-then-execute (sh is dash — no pipefail; the claude installer keeps the pipe but under an inner bash with pipefail), and provisioning ends with an explicit binary/Node-major/claude-on-worker-PATH gate.
|
Third review wave (3 findings) addressed in 38716d5 — all confirmed:
Verified: YAML parses; the merged runcmd script passes |
… triage verify from admin shell Fourth review wave, both findings confirmed: - Detached workers spawned from an Actions dispatch job inherited RUNNER_TRACKING_ID, and the runner's post-job cleanup kills any surviving process whose environ carries that marker — nohup/--bg detach the session, not the env, so the primary automated dispatch path reaped its own workers at job exit (stale 'working' meta left behind). All three detach points (daemon-spawn, daemon-resume fork, _codex_launch wrapper) now spawn under env -u RUNNER_TRACKING_ID — a no-op outside Actions. README §2.6 names this as the mechanism instead of implying nohup suffices. - README §5.4 had the first-tick verification run 'sudo systemctl' inside the sudo -iu triage shell — triage has no sudo, a locked password, and no journal access. Verification now runs from the admin shell; §5 intro marks step 4 as the exception. Suites: test-daemon-scripts, test-codex-scripts, test-review-dispatch all pass; shell lint clean.
|
Fourth review wave (2 findings) addressed in b525ed0 — both confirmed:
Rebased onto 7db6b8d (boot-id-bound liveness) before pushing; test-daemon-scripts, test-codex-scripts, test-review-dispatch all pass on the combined tree, shellcheck lint clean. |
…/stdin Fifth review wave, confirmed empirically (GNU coreutils install exits 1 with 'skipping file /dev/stdin, as it was replaced while being copied' and creates nothing): the §2.2 recipe could never seed auth.json on the Ubuntu target. Replaced with umask 077 + cat (file born 0600, still no exposure window) followed by chown to worker.
|
Fifth review wave (1 finding) addressed in d49e352 — confirmed empirically before fixing: GNU coreutils Replaced with |
Review trail\n\nEngine: native Codex review via review-engine.sh, model gpt-5.6-sol, effort xhigh.\n\nClosing artifact cross-check:\n- The PR body has no Validation Evidence section. Per protocol, absence is recorded but is not a finding.\n- The five named test suites pass. The board suite initially failed only because sandboxed process inspection denied ps; it passed outside the sandbox.\n- The bare scripts/lint-shell.sh command exits 0 with No shell files found on a clean checkout. Explicitly linting all 13 changed shell files passes.\n- GitHub reports no CI checks.\n\nRound 1:\n- P2, same-host reboot PID reuse: FIX NOW. Added boot_id stamping and host plus boot liveness gates. Regression tests cover resume, retire, review dispatch, and land dispatch.\n- P2, Claude host checks missing from review and land liveness: FIX NOW. Host plus boot identity now gates Claude dedupe, worktree occupancy, and retirement. Unmanaged visible Claude sessions still conservatively block removal.\n- Both fixes were committed and pushed in 7db6b8d. Focused suites and shell lint passed.\n\nRound 2:\n- P1, resumed Claude turns remain foreign until terminal polling finishes: FIX NOW. daemon-resume now registers the new UUID and local host plus boot identity immediately after the UUID appears, before the long terminal poll.\n- The regression failed before the change and passes after it. All four focused suites and shell lint pass.\n- Fix commit 01e31c1 is local only. The first push was rejected because the head advanced to b525ed0; the fix was rebased cleanly. The single retry was rejected again because the head advanced to d49e352. Per protocol, no further rebase and no force-push were attempted.\n- Round 3 was not run because this P1 fix is not on the PR head.\n\nFinding bins:\n- Round 1 P2 reboot identity: FIX NOW, pushed.\n- Round 1 P2 Claude liveness identity: FIX NOW, pushed.\n- Round 2 P1 early resume registration: FIX NOW, verified and committed locally, but blocked from push by repeated head movement.\n\nVerdict: NEEDS ATTENTION. The current PR head still has the unresolved P1. A human must reconcile local commit 01e31c1 onto current head d49e352 and then run the third and final engine round.\n\nTier judgment: not self-merge eligible. The PR targets the default branch main, has no CI checks, exceeds the 150-line and 5-file self-merge bounds, and touches worker provisioning and token/auth handling. No confident-ready label was applied; help wanted marks the human handoff. No board writes were made because this PR has no linked ticket. |
…ed host identity A migrated meta's short is host-local and reusable — stop/rm through it can hit an unrelated local agent. Capture the identity verdict before the fork re-stamps the meta; foreign metas skip both (leftover old-host session files are cosmetic, a purge through a reused short is destructive).
The greedy .*sec-= match landed inside 'usec = ' and recorded microseconds
as the boot identity — a value that can repeat across reboots, defeating
the host-boot liveness gate on macOS. Anchor on the leading '{ sec = N,'.
Regression cross-checks _boot_id against an independent parse.
env.triage.example: header marks the file fork-local (names the fork's real
consumer repo on purpose; never upstream) — review finding triaged as void
for a personal fork, kernel kept as a permanent guard note.
Review trail — reconciliation + final rounds (human-directed session)Completing the earlier NEEDS ATTENTION verdict:
All four focused suites + shell lint green on head b7ead32. Landing by human direction (squash). |
What
Adopts the transferable parts of Anthropic's Scaling Managed Agents: Decoupling the brain from the hands into the board pipeline. Full transfer analysis:
docs/doperpowers/2026-07-12-managed-agents-steals.md.Steal 1 (code). Registry metas now carry a
hoststamp at every registration/resume;_pid_alive()treats a pid recorded on another host as dead regardless ofkill -0. Fixes the silent stall class where a state-volume migration (host died/rebuilt/moved to cloud) leaves reused pid numbers reading as live workers: resume refused, retire signaling unrelated processes, dispatch skipping dead reviewers, worktree removal blocked by ghosts. All four pid consumers (codex-resume guard, daemon-retire kill, review/land-dispatch_is_live,_wt_occupied) are host-aware; emptyhost= legacy meta = local (behavior unchanged).Steal 2 (convention). State-volume spec for a future dedicated worker host: registry, session stores, clones on one detachable volume; compute disposable.
Steal 3 (recipe). Token-wired-remote scope split (push scope in clone remote URL, board scope in env) recorded as the narrowing path for the
GH_TOKENaccepted note — TECH-DEBT pointer added; applies at host provisioning, not on this Mac.Testing
tests/orchestrating-daemons/test-codex-scripts.sh— new: foreign-host pid → resume proceeds + re-stamps host, never signals the unrelated local process; retire leaves it alone. PASStests/reviewing-prs/test-review-dispatch.sh— new: foreign-host live pid → retire+respawn;_wt_occupiednot occupied; same-host control still skips active. PASStests/reviewing-prs/test-land-dispatch.sh— new: foreign-host live pid → retire+respawn. PASStest-daemon-scripts.sh,test-board-scripts.sh,scripts/lint-shell.sh— PASSFork-internal change (personal fork; no upstream PR intended).