Skip to content

Far-side lifecycle shim: keep a busy box awake through the provider (boxd first) #257

Closed

Description

@joelmoss

Parent

Part of #154 (remote agent sessions). Phase 4. Design doc docs/designs/remote-workrooms.md: Constraints ("One provider-specific component is sanctioned on the far side"), Provider Decision, Phase 3 "A lifecycle shim", Distribution Plan ("Two artifacts ship to the far side"), OQ19 and OQ22, and Next Steps item 5 ("Apply the measured wakefulness policy through the far-side shim").

What to build

The agent already decides whether a workroom is busy or idle (#215, policy from OQ19). A sleeping Mac can tell a provider nothing, so a small per-driver shim on the box turns that verdict into the provider's own control-plane call, and a long job survives the provider's idle timer with the Mac asleep.

  • The boxd shim. On boxd the in-VM boxd CLI is self-scoped: boxd machine config set auto-hibernate.timeout 0 targets only its own machine and needs no Workroom-managed credential (Phase 0 results, item 1). The shim holds auto-hibernate off while the agent says busy and restores it when idle.
  • The awake ceiling (OQ22). Advisory by default: a box busy past the ceiling is reported, not hibernated. With the ask-the-user setting on, an unanswered prompt stops the agent asserting busy, so the provider's own idle timer can hibernate the box. The shim never hibernates a box itself.
  • The bootstrap pushes a set keyed by driver (agent, shim, supervisor unit), not the agent alone as Remote bootstrap: push the Linux agent on first connect, hand off on version mismatch #231 does today, and each driver's shim has its own Linux build target.
  • Small enough to audit by reading. It dies with the instance, including on failure paths, and its credential scope is a declared driver trait.

Acceptance criteria

  • On boxd, a job longer than the provider's idle window finishes with no client attached, where an unprotected control box is hibernated during the same wait.
  • Once the workroom goes idle, the box hibernates within the provider's normal idle window.
  • With the default policy, a box busy past the awake ceiling is reported in the app and stays awake; with ask mode on, an unanswered prompt lets it hibernate.
  • Connecting a newer app pushes the updated shim with the agent, and connecting the same version pushes nothing.
  • The shim holds no credential that outlives the instance.

Blocked by

#256 (boxd driver).

Filed by Claude Code at the owner's request, 2026-09-27.

Activity

  1. added a commit that references this issue on Oct 1, 2026
  2. added
    enhancementNew feature or request
    remoteRemote workrooms: hosts, providers, the far-side agent and broker
    on Oct 3, 2026
  3. joelmoss commented on Oct 6, 2026

    @joelmoss
    OwnerAuthor

    🤖 Left by Claude Code

    Built as an agent heartbeat, not a shim (PR #353). The eng review's D5 replaced the far-side lifecycle shim: boxd's suspend and hibernate timers count only idle network time, so while its published verdict (after the awake ceiling) is BUSY, wr-agent sends one 1-byte UDP datagram a minute to the box's IPv4 default gateway. An idle box, an unanswered ceiling prompt or a dead agent sends nothing, and the provider's own timer sleeps the box. There is no boxd CLI call, no timer change, no separate artifact to push, and no credential on the box. The agent is pushed by hash as before, so an older machine gets the heartbeat with its next agent update.

    Live acceptance on boxd (2026-10-06). Three fresh machines, 120 s timers, the same 6-minute job with no network use and nothing attached:

    • control, no agent: hibernated mid-job;
    • this agent: kept all 36 ticks, then hibernated 121–141 s after the job ended;
    • ask mode, nobody answering: hibernated mid-job.

    Against the acceptance criteria above:

    • A job longer than the idle window finishes with no client attached; the control is hibernated (live run).
    • Once idle, the box hibernates within the provider's normal window (live run).
    • Advisory default reports past the ceiling and stays awake; an unanswered prompt in ask mode lets it sleep (live run; the app now shows each remote host's prompt card).
    • [~] "Pushes the updated shim": there is no shim. The agent itself carries it, pushed by hash.
    • No credential: none is needed (HostDriverTraits.keepAwakeHoldsCredential is false for every driver).

    Still open:

    I haven't closed this; your call.

  4. joelmoss commented on Oct 6, 2026

    @joelmoss
    OwnerAuthor

    🤖 Left by Claude Code

    Shipped in #353 (merged as b8809ab0). A busy remote box now stays awake with the Mac asleep and nothing attached, and it sleeps on the provider's own timer once it goes idle.

    What shipped

    • Agent heartbeat. While its verdict is BUSY (after the awake ceiling), wr-agent sends one 1-byte UDP datagram to the IPv4 default gateway every 60 s. That replaces the planned far-side lifecycle shim: no boxd CLI, no timer changes, no credential on the box. An idle box, an unanswered ceiling prompt or a dead agent sends nothing.
    • Ceiling settings. A new Status settings request sets the ceiling, prompt timeout and ask mode, bounded to 30 s – 30 days. The agent keeps them in <socket>.settings, and they win over flags at the next start. status also reports keep_awake (the last send and any error) and stalled (no tick for 30 s).
    • App.
      • Every remote connect hands the agent this Mac's ceiling settings. Ask mode goes only to hosts whose provider sleeps them (new HostDriverTraits.sleepsWhenIdle).
      • Each remote host's ceiling prompt shows as its own clickable card, named for its workroom.
      • The badge says "busy but not kept awake" for an unanswered prompt, a stalled agent, a failing heartbeat or an old agent, and only on hosts that can sleep.
    • Live acceptance on boxd (2026-10-06). Three machines with 120 s timers ran the same 6-minute job:
      • With no agent, the machine hibernated mid-job.
      • With the agent, it kept all 36 ticks and hibernated 121–141 s after the job ended.
      • With the agent in ask mode and nobody answering, it hibernated mid-job.

    Left as P1 TODOs (TODOS.md)

    1. Put the acceptance run in BoxdIntegrationTests. The live run above was done by hand, and nothing reruns it. It should be a gated case, so a boxd metering change or a new provider (exe.dev, exe.dev driver: the second real provider #259) gets the same proof.

    2. Remove the lifecycle shim's leftovers. That means the <socket>.wake verdict file, verdict_written, net_masked and wr-wakeshim from the exclusion list. The verdict file is written on the service thread each tick, so a stalled disk can delay the next heartbeat and let a busy box sleep.

    3. Gate before wiring boxd into the app (boxd live-fork derivation, as an accelerator behind deriveFromBase #258, Phase 4 gate: the success criteria on two real providers #260).

      • A wake-on-connect resets the awake ceiling, so a box whose prompt went unanswered could be held awake in ceiling-sized chunks.
      • Base connects get ask mode with no prompt watch.
      • An idle window under 60 s defeats the heartbeat silently.
      • Prompt cards don't scroll when many hosts ask at once.
      • A stall after an IDLE reading shows as idle.

      None of these is exposed today: boxd isn't wired in, and containers never sleep.

    P2 follow-ups (settings hardening, several Macs, a crashed wakefulness thread) are in TODOS.md too.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestremoteRemote workrooms: hosts, providers, the far-side agent and broker

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions