wake-agent: event-triggered headless Pi continuation with a footer badge #285
No reviewers
Labels
No labels
blocked
bug
decision
duplicate
enhancement
help wanted
invalid
landed?
question
ready-to-merge
stale
wontfix
bug
duplicate
enhancement
help wanted
invalid
question
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
allod/tools!285
Loading…
Reference in a new issue
No description provided.
Delete branch "agent/wake-agent-281"
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?
Owner summary
A machine gains one command,
wake-agent <probe-cmd> <brief-file>, which leaves a transient user unit behind, polls the probe until it succeeds twice across a grace sleep, and then runs one freshpi --no-session -p <brief>under a timeout, writing a status file and a log for every outcome. Nothing is installed anywhere yet: the command and the Pi footer extension are new flake outputs in this repo, and no module, timer or VM wires them in, so every running machine is identical after this merges. The proof is the issue's own gate: a live end-to-end pilot on allod-dev where the event fired, Pi resumed, the artifact landed byte-exact and the report was written, plus the Go suite and a Node check of the extension. If it is wrong, revert the commit; there is no state to undo beyond files under~/.local/state/wake-agenton a machine that ran it by hand.Closes allod/tools#281
What changed
cmd/wake-agent: the foreground command validates the brief (regular file, under 128 KiB, no NUL, and the shell-quoted Pi command also under that limit), resolvespiand util-linuxscript, and startswake-agent.servicethroughsystemd-run --user --collectwith itself as--worker. The worker interprets probe exits as 0 observed, 1 wait, 2 dead target, other broken; confirms after--grace; runs Pi under a pseudo-terminal because this Pi build hangs in print mode without one; bounds each attempt with--timeoutand the whole run with--cap; sends TERM to the child group and KILLs survivors after two seconds; retries once with--retryand never after a cap or cancel. Every exit writesstatusand the live sentinel atomically at mode 0600, with probe output bounded before it reaches state.extensions/wake-agent.ts: a Pi extension that sets the persistent statuswake-agent: runningwhile the sentinel names a live stage andwake-agent.serviceis active, and unlinks a sentinel whose unit is gone, so a SIGKILL or a failed terminal write cannot leave a stale badge.flake.nix:wake-agentpackage and check, and awake-agent-extensioncheck that runs the extension's Node test against a mockedsystemctl.go-checksgains util-linux because the Go suite drives the realscript.docs/wake-agent.md: the contract, the probe exit table, state and sentinel paths, the install step for the footer, and the timeout trap below.Risk
R3. It is a wrapper that creates a transient systemd user unit and supervises a child process group, which the rubric names, even though nothing deploys it and a bad run is bounded by the cap and recoverable by
systemctl --user stop wake-agent.service. The worst credible failure after validation is a continuation that runs against the wrong target or a wrong model and burns a timeout's worth of tokens, written to the log and status as such. It cannot touch remotes, secrets or provisioning. Reviewed by one cold Claude-family reviewer, invoked by the Codex overseer that drove the build; its fourteen findings and the fix round are listed below.Validation
Run on allod-dev from the worktree at this PR's head.
Go suite, vet and race:
every other package ok as before.
Every flake check from the committed tree:
The end-to-end pilot the issue names as the promotion gate, run against the real transient user unit with a brief asking Pi to write an artifact and a report:
The pilot pinned Pi to
nullsink-claude/claude-sonnet-5-5through a one-linepishim placed first on PATH, because the tool resolvespifrom the caller's PATH and pins the absolute path into the unit; the command inside the unit stayedpi --no-session -p <brief>. Two earlier pilots against the machine's default model timed out at two and three minutes with no output;probe-modelsthen showed that model answers a one-word prompt in under seven minutes, so those timeouts were too short for a thinking model rather than the tool failing. That is now a documented trap: size--timeoutfor the model, not only the task.The footer extension against the real unit and the real
systemctl, not the mocked one the Nix check uses: with a watcher inwaitingthe extension reportswake-agent: running; a second launch while that unit exists is refused by systemd; aftersystemctl --user stopthe status readsstage=canceled detail=watcher stopped while waitingand the extension reports no badge.Review findings
Fixed in the one fix round: the stale badge after SIGKILL (extension now checks unit liveness and unlinks; sentinel stage parsed by line, not substring), the TERM grace that never ran (no
CommandContext; explicit TERM, two-second wait, then KILL), retry recorded after a cap or cancel (refused when the parent context is done), launcher and unit environment drift (piandscriptresolved at launch and pinned by path into the unit), unbounded probe output copied into state (probe writes to a file, bounded to 4 KiB, detail bounded to 8 KiB; docs warn that the probe string is visible in unit metadata), a backgrounded probe child holding the pipe (file, not pipe, soWaitreturns with the probe), the success-versus-cap race, brief validation at launch and the 128 KiB argument limit, and the comment bloat.Kept as the contract, and documented: the cap bounds the whole run including a running Pi attempt, because the issue's cap is a total lifetime and a watcher that outlives its cap is the failure the cap exists to prevent. The unit name is fixed, so a second watcher per user is refused rather than isolated. Linger is the user manager's business and the docs say so.
Deferred, out of this slice:
--workerand the path flags remain parseable public flags that the usage text omits, and the worker's dead-target exit 2 shares a number with a usage error; neither affects the status file, which is the contract a reader uses. Deployment wiring and the install path for the footer are the deployment's, per the issue's scope.Found on the way
allod/tools#284: building
allod-parityorgo-checkswith apath:reference from a linked worktree fails withinvalid description, because the copied tree carries a dangling.gitfile andgit check-ref-formataborts under it. The same tests pass from the committed tree through the git reference, which is what the Validation section above ran. Not touched here.Caught up with master at
3909ce4by merge commit27607d6, since the forge refuses force-push on agent branches. No conflicts: the only shared file is flake.nix, and the merged tree differs from master by this PR's seven files alone, each flake binding present once. All ten flake checks rebuilt green on the merged tree from the git reference (allod, allod-parity, cascade-suites, flake-status-suite, flake-update-cascade, forge, git-hook-suites, go-checks, wake-agent, wake-agent-extension).View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.