feat: pause an agent's turn loop without stopping its container

A paused agent keeps its container, its claude session and its
dashboard/todo servers up, but stops driving turns. Messages queue
unacked and are drained on resume.

The whole protocol is a single marker file, `<harness>/paused`. That
directory is already a bind-mount shared between host and container, so
both sides just stat the same path: the harness reads it to decide
whether to drive a turn, hive-c0re reads it to render the badge and
writes/removes it for `hivectl pause|resume`. No new wire protocol, no
container round-trip, and it is sticky across restarts by construction.

Not calling `recv_next` while paused *is* the queueing semantic, so
there is no fencing to get wrong: reminders buffer in their unbounded
channel, the todo `Notify` permit coalesces, and a `request_next_turn`
that raced the pause survives because the gate sits above
`self_continue.take()`.

Graceful stop is handled host-side rather than in the harness: a paused
agent provably has no turn in flight, so `run_signal` skips the fence
entirely instead of eating the full `GRACEFUL_STOP_TIMEOUT` waiting for
a checkpoint turn that will never run.

`paused` is reported on `ContainerView` / `AgentStatusRow` for the
dashboard, orthogonal to `running` and reported for stopped containers
too.

Closes: hyperhive/hyperhive issue 2271
This commit is contained in:
atlas 2026-07-26 02:31:54 +02:00 committed by mara
commit 31008c83df
14 changed files with 305 additions and 1 deletions

View file

@ -93,6 +93,19 @@ pub enum HostRequest {
/// For "kick the container" operations that don't touch the flake or
/// nspawn flags. Mirrors `lifecycle::restart` (kill + start).
Restart { name: Ident },
/// Park (or un-park) an agent's turn loop without touching its
/// container: `hivectl pause|resume <name>` and the dashboard
/// toggle. Writes/removes the marker file the harness polls, so the
/// container stays up and keeps serving its web UI and MCP daemons
/// while burning no tokens. Inbox messages queue unacked and the
/// backlog drains on resume. Not a lifecycle DAG — it's a single
/// marker write, so it applies immediately and works on a stopped
/// container too (the pause is sticky and takes effect at next boot).
SetPaused {
name: Ident,
/// `true` pauses, `false` resumes. Idempotent either way.
paused: bool,
},
/// Stop and restart all managed containers in sequence. Convenience
/// wrapper for `hivectl agents restart-all`; iterates the live
/// container list and restarts each one.