Skip to content

feat: the box decides its own wakefulness (#380) - #381

Merged
joelmoss merged 11 commits into
masterfrom
box-decides-wakefulness
Oct 9, 2026
Merged

joelmoss merged 11 commits into
masterfrom
box-decides-wakefulness

Conversation

@joelmoss

@joelmoss joelmoss commented Oct 8, 2026 •

Copy link
Copy Markdown
Owner

Fixes #380.

After merge, close #371's items 6 (rest), 8, 9, 13 and 17. They are edge cases of the app-side wakefulness logic this PR removes. Item 18 (#380 itself) closes with them.

Release note

  • Removed: the "Ask before a busy machine sleeps" setting and the awake ceiling. Workroom no longer manages when a remote machine sleeps. That is now between you and the machine's provider: set its auto-suspend or auto-hibernate to suit your work.
  • Workroom neither holds nor releases a remote machine. While the app is open it stays connected to your remote workrooms, which can keep a machine awake. To free a machine, delete its workroom.
  • Remote port forwards survive idle disconnects. A forward keeps its local port when its connection goes, and reconnects the next time something uses it.

Summary

The agent reports (wr-agent). The wakefulness service pushes status changes to listeners on the Status service (v2), and a new listener gets the current status on the next tick. The awake ceiling, its --awake-* flags and the settings request are gone. Push threads use thread::Builder::spawn, so a refused thread no longer panics the service. Nothing lets go of an idle connection. A first version had the agent close idle connections, but Codex found it also dropped local containers' connections and their git credential relay. The owner then decided an open app holding a box awake is the user's concern (delete the workroom to free it), so the let-go was removed.

The app follows (macapp). WakefulnessModel asks for status once per connection, then follows pushes. It re-asks every 60 s only while BUSY. The app-side let-go (boxd-only on master), its "idle, not connected" row state, the status poll, the ceiling prompt, the idle-window warnings and the setting are removed. A background read still never wakes a box boxd says is asleep (#356). Port forwards outlive their connection and reconnect on use (PortForward.Reconnect), since a connection still drops when the network does or the box sleeps. Deleting a remote workroom forgets its forwards (PortForwardingModel.forgetHost).

Docs. docs/designs/remote-workrooms.md has an "As built (#380)" section with the design and the decisions behind it.

Follow-up #382 (after merge). The owner decided that keeping a box awake under running work is the user's concern too. #382 removes the heartbeat, the BUSY/IDLE classifier, the Status service and the badge. Its let-go replacement and the dead-pane case are moot now that there is no let-go.

Test Coverage

Tests written: AgentWakefulnessTests.testTheChangesStreamEndsWithItsConnection, and wr-agent unit tests for push-on-change, the pushed status reply and the new-listener push. (The let-go's tests, tests/let_go.rs among them, went with the let-go.) Extended: PortForwardingModelTests' reconnect test, and testForgettingAHostForgetsItsForwards (confirmed red with forgetHost's stop loop removed).

Coverage: 76% value-weighted (79% including 1 weakly covered path). 6 paths are intentionally uncovered, per the owner (D2): they belong to the classifier and heartbeat that #382 removes.

Test value: 2 tests written, 0 rejected by the authoring gate, 2 existing tests extended, 1 path weakly covered.

Pre-Landing Review

Three passes, plus a Claude and Codex adversarial review.

  • Pass 1: 8 informational findings auto-fixed (stale comments, test gaps). The busy re-ask (D3) and plain reconnect (D4) were approved fixes. One finding was skipped (D5) and 4 coverage gaps skipped per D2.
  • Pass 2: 3 stale comments fixed. One test-timing item was skipped after measuring it 16/16 green, and one simplification was skipped because Wakefulness is the user's concern: remove the heartbeat, classifier, Status service and badge #382 removes that code.
  • Adversarial: a refused push thread could panic the service, and a new listener could wait for a verdict change before its first push. Both fixed in 553479d, with a Linux push test made deterministic. The dead-pane let-go case is deferred to Wakefulness is the user's concern: remove the heartbeat, classifier, Status service and badge #382 by the owner.
  • Pass 3 (553479d only): one informational testing finding, a vacuous forgetHost test. Fixed in c2cd8af.
  • PR review (Codex P2): the agent's let-go also dropped local containers' connections and their credential relay. Owner decision: remove the let-go entirely.
  • Review binding: the adversarial review saw the tree before 553479d and c2cd8af. Pass 3 hit /ship's fix-cycle cap. The owner accepted finishing without another review round (D8), because the PR reviewers below cover those commits.

Exploratory QA

QA smoke 001–004 ran against the app and agent. 003 (the Linux push test) was flaky and was fixed in 553479d. On Linux the fix passed 10/10 under both busy and quiet load, and the mutant without the new-listener push failed 3/3 quiet. 005 and 006 were revalidated and pass.

Live boxd acceptance ran on the let-go version (billed, 2026-10-08, both PASS). Both tests went with the let-go: what they checked no longer exists, and whether a box sleeps is the provider's and the user's concern, deliberately not tested.

Not run, by owner decision: the #257 heartbeat live test (testTheHeartbeatKeepsABusyBoxAwakeAndABoxWithoutOneSleeps). The heartbeat's behaviour is unchanged, and #382 removes it.

Design Review

UI changes are removals only: the setting, the ceiling prompt and the warning texts. No new UI. Design review was skipped.

Eval Results

No prompt-related files changed, so evals were skipped.

Greptile Review

Greptile triage: UNAVAILABLE (Greptile is not installed on this repository). Codex and Copilot review instead:

  • Copilot, refused push thread could panic: [ALREADY FIXED] in 553479d.
  • Codex P2, any disconnect on an idle host marks it released: [FALSE POSITIVE], intended. Reconnecting for background reads would keep an idle box awake.
  • Codex P1, restore the short idle-window warning: [FALSE POSITIVE], removed by owner decision (The box decides its own wakefulness: agent-side let-go, pushed verdicts, no app-side wakefulness management #380).
  • Codex P2, the let-go drops a container's credential relay: [FIXED] by removing the let-go entirely (owner decision).
  • Codex P2, prune forwards when another build deletes the host: [WON'T FIX]. It needs two builds running plus a cross-build delete of a host with an open forward. The cost is a localhost listener until quit.

Plan Completion

Plan: issue #380. 23 items: 15 done, 6 changed, 1 partial (is_busy is not pinned by a test), 0 not done, 1 unverifiable (the #371 closes, which are listed above to do after merge).

Verification Results

Step 8.1: the live heartbeat check is NOT RUN, and its risk was accepted by the owner. The live let-go and busy-box checks passed on the let-go version and were removed with it. Rust and app suites: PASS (see Test plan).

TODOS

No TODO items completed in this PR. "Harden the agent's kept settings" is trimmed to its one remaining item, because the settings request it covered is removed. The P0 entry for the flaky crossing-keep test is dropped, because the test went with keep. "A crashed wakefulness thread hides the badge" loses its let-go sentence.

Documentation

Status: updated. 3 factual doc edits, no blockers.

Scope: branch box-decides-wakefulness (#380, audit docs-381-1791501403) against base d8bbe27. Audited committed, staged and unstaged content (TODOS.md is the only unstaged file; there are no new files). Discovered 17 docs. Fully read: the three edited design docs, macapp/AGENTS.md, and the TODOS.md diff. remote-workrooms.md was read by section. The rest were grepped for the removed surface (awake, ceiling, wakefulness, keep, let go, heartbeat, Ask before a busy machine) with no hits.

Documentation health:

Coverage and debt: The removed surface (ceiling, settings request, prompt cards, Settings row, app-side let-go) has no user-facing doc outside the design docs, so nothing else was stale. New behavior (pushed status verdicts, agent-side let-go, forwards that survive idle disconnects) has reference and explanation coverage in "As built (#380)". It has no how-to or tutorial coverage, and none is owed because wakefulness is the user's concern. Release notes for the removal are the parent's. No architecture diagrams exist, so no diagram drift. Minor prose debt: the Known limits paragraph in remote-workrooms.md (near line 1799) repeats the ceiling-prompt sentence awkwardly.

Test plan

🤖 Generated with Claude Code

joelmoss and others added 5 commits October 8, 2026 20:28
Workroom no longer manages a provider's sleep or billing. It owns two
things only, both because it causes them: an open app never holds an
idle box awake, and a box never sleeps under running work (the
heartbeat, #257).

The agent pushes each change of verdict to every connection that has
asked for `status` (Status service version 2), so nothing polls: the
app's 10 s poll crossed eth0 often enough to vote its own box BUSY, and
a connected boxd box never slept. Once the verdict has been IDLE for
30 s, the agent closes every connection at least that old that carries
no forwarded connection, unless a pane is attached anywhere: a pane's
ssh holds a box awake whatever happens, and a local container's
credential relay rides the service connection.

The app asks once per connection and follows the pushes, ordering a
connection's readings by the agent's clock. A connection that ends on an
IDLE box of a host that sleeps marks the host released, so the status
sweep does not ssh back in and hold the box awake; selecting or opening
its workroom, a click, a pane attaching, a forward in use or any new
connection forgets it. A pane attaching connects a host with no service
connection, waking it only when released. A remote host's port forward
outlives its connection and reconnects when used; it never starts a
container the user stopped.

Removed: the awake ceiling everywhere (its prompt, `keep`, `settings`,
the kept settings file, the `--awake-*` flags and variables, the three
preferences, the Settings row, the prompt cards), the app's status
poll and let-go, `disconnect(_:unless:)`, the boxd idle-window read and
its badge warning, and this Mac's own badge, whose agent runs no
wakefulness service.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ness (#380)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lness (#380)

An idle box's agent lets go of the attached app, the app takes that as
idle and stops reaching it, and a kept forward and a selection reconnect
it; a busy box keeps the app's connection and logs every tick of its
job. Whether an idle box then sleeps is its provider's: a fresh boxd box
was still running 300 s after its agent let go, with nothing of
Workroom's holding it, so that is no longer asserted. A failure reports
the verdicts the app saw.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Drives real connections through handle_connection: a pane spares every
connection, an in-flight forward spares its own, and an unused one is
closed and its peer sees EOF.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A busy box is asked for its status again every 60 s: the agent pushes
only on a change, which a stalled or crashed wakefulness service never
makes, so the badge kept showing a box kept awake while its heartbeat
had stopped. Only while BUSY, so an idle box is never held awake.

The Linux push test causes a verdict change after it subscribes; the
service's first push usually came before it did, and the test failed
10 of 10 times run alone. The push decision is one helper the service
and its test both call. Forwards' reconnect is a plain value, now that
nothing is read late. Coverage for the pushed-change stream ending with
its connection and for a forward's live connection. Stale comments and
superseded design-doc bullets fixed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Nothing "never polls" any more: an idle box is never asked again, a
busy one is once a minute. Two more #254 bullets marked superseded, and
the routing tests say the agent lets go, not the app.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@joelmoss
joelmoss requested a balanced review from Copilot October 8, 2026 22:27
@joelmoss
joelmoss marked this pull request as ready for review October 8, 2026 22:27
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-09T07:46:53.547978Z 40e34d8 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 35c9612e04

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Status push thread-creation failure can permanently stop wakefulness protection and idle release.

1 open finding
What changed in this PR

Moves wakefulness ownership into wr-agent: verdicts are pushed to clients, idle connections are released agent-side, and awake-ceiling behavior is removed.

Changes:

  • Adds pushed status events and agent-side idle connection release.
  • Reconnects remote port forwards on demand.
  • Removes awake-ceiling settings, prompts, polling, and related tests/UI.
File Description
vcs/​crates/​wr-agent/​tests/​status_service.rs Tests status v2 and pushed verdicts.
vcs/​crates/​wr-agent/​tests/​let_go.rs Tests idle connection release.
vcs/​crates/​wr-agent/​src/​wakefulness/​tests.rs Replaces ceiling tests with push/release tests.
vcs/​crates/​wr-agent/​src/​wakefulness/​heartbeat.rs Drives heartbeats directly from verdicts.
vcs/​crates/​wr-agent/​src/​wakefulness.rs Implements status v2, pushes, and idle release.
vcs/​crates/​wr-agent/​src/​serve.rs Registers and selectively closes connections.
vcs/​crates/​wr-agent/​src/​main.rs Removes awake-ceiling CLI options.
vcs/​crates/​wr-agent/​src/​forward.rs Reports active forwarded connections.
TODOS.md Updates wakefulness follow-up work.
macapp/​WorkroomAppUITests/​WakefulnessPromptUITests.swift Removes obsolete prompt UI tests.
macapp/​WorkroomAppTests/​WakefulnessBadgeTests.swift Removes polling-specific badge test.
macapp/​WorkroomAppTests/​Session/​AgentBootstrapTests.swift Removes ceiling-setting tests.
macapp/​WorkroomAppTests/​RemoteWorkroomRoutingTests.swift Updates released-host routing tests.
macapp/​WorkroomAppTests/​RemoteWorkroomDeletionTests.swift Updates host-watch deletion coverage.
macapp/​WorkroomAppTests/​RemoteHostsTests.swift Tests agent-released host behavior.
macapp/​WorkroomAppTests/​RemoteHostIntegrationTests.swift Watches pushed host status.
macapp/​WorkroomAppTests/​HostConnectionManagerTests.swift Removes app-side let-go coverage.
macapp/​WorkroomAppTests/​BoxdIntegrationTests.swift Tests idle release and busy protection.
macapp/​WorkroomAppTests/​BoxdHostDriverTests.swift Removes idle-window parsing tests.
macapp/​WorkroomAppTests/​AgentPortForwardingTests.swift Tests reconnecting forwards.
macapp/​WorkroomAppTests/​AgentFileIntegrationTests.swift Updates fake status events to v2.
macapp/​WorkroomApp/​WorkroomApp.swift Removes prompt-watch lifecycle hooks.
macapp/​WorkroomApp/​Views/​ToastStack.swift Removes awake-ceiling prompt UI.
macapp/​WorkroomApp/​Views/​SettingsView.swift Removes awake-ceiling settings.
macapp/​WorkroomApp/​Views/​ProjectSidebar.swift Uses push-driven remote badges.
macapp/​WorkroomApp/​Views/​ChangesPanel.swift Simplifies wakefulness badges and forward status.
macapp/​WorkroomApp/​Core/​WorkroomStatus.swift Updates idle-state terminology.
macapp/​WorkroomApp/​Core/​UITestFixture.swift Removes prompt fixture support.
macapp/​WorkroomApp/​Core/​Session/​PersistentSessionService.swift Reconnects hosts when panes attach.
macapp/​WorkroomApp/​Core/​Session/​AgentBootstrap.swift Removes ceiling-setting synchronization.
macapp/​WorkroomApp/​Core/​RepositoryLocation.swift Updates released-host errors.
macapp/​WorkroomApp/​Core/​RemoteWorkrooms.swift Replaces app let-go with release tracking.
macapp/​WorkroomApp/​Core/​RemoteProvisioning.swift Simplifies agent bootstrap.
macapp/​WorkroomApp/​Core/​LocalAgentVCS.swift Removes local wakefulness connection modes.
macapp/​WorkroomApp/​Core/​HostDriver.swift Documents sleep-related traits.
macapp/​WorkroomApp/​Core/​HostConnectionManager.swift Removes conditional disconnect API.
macapp/​WorkroomApp/​Core/​DefaultsKeys.swift Removes awake-ceiling preferences.
macapp/​WorkroomApp/​Core/​BoxdHostDriver.swift Removes idle-window querying.
macapp/​WorkroomApp/​Core/​AgentVCSConnection.swift Decodes pushed status changes.
macapp/​WorkroomApp/​Core/​AgentPortForwarding.swift Preserves forwards across reconnection.
macapp/​AGENTS.md Documents agent-side idle release.
docs/​designs/​remote-workrooms.md Records the #380 architecture.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread vcs/crates/wr-agent/src/wakefulness.rs Resolved
A new listener is pushed the current status on the next tick, whatever
the box is doing; the push test waited for the verdict to change,
which on a busy CI runner it never did. A refused push thread no
longer panics the wakefulness service.

At most 64 clients wait on a forward's reconnect, and removing the
forward closes them: a burst against a slow wake held descriptors
without bound. A deleted host's forwards go with it, since its
connection no longer drops them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 553479d355

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

The old test passed with no forward registered. It now adds a real
forward under a host and checks that forgetHost drops the model, stops
the forward and closes its listener. Confirmed red with the stop loop
removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c2cd8af235

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

The ceiling, `keep`, the prompt event and the agent's settings are gone,
so the older design-doc passages that describe them say so. TODOS drops
the flaky crossing-`keep` test entry (the test went with `keep`) and
trims the kept-settings entry to its one remaining item.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: efc646d934

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread vcs/crates/wr-agent/src/serve.rs Resolved
joelmoss and others added 2 commits October 9, 2026 08:42
The owner decided an open app holding a box awake is the user's concern:
deleting the workroom frees it. The let-go also closed local containers'
connections, which never sleep, taking their git credential relay with
them (Codex review on #381). The connection registry, LET_GO_GRACE_S,
Forwards::carrying and their tests go; serve.rs is back to master apart
from the removed awake-ceiling settings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With the agent no longer letting go of idle connections, the app's
`RemoteHosts.released` marker, the `.idle` routing error and row state,
and the badge's "Workroom doesn't hold it awake" text have nothing left
to describe. A background read still never wakes a box boxd says is
asleep (#356). The let-go's live boxd tests and unit tests go with it;
the design doc, macapp/AGENTS.md and TODOS say so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 40e34d81f5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@joelmoss
joelmoss merged commit b5dd6d7 into master Oct 9, 2026
10 checks passed
@joelmoss
joelmoss deleted the box-decides-wakefulness branch October 9, 2026 08:03
joelmoss added a commit that referenced this pull request Oct 9, 2026
The owner decided an open app holding a box awake is the user's concern:
deleting the workroom frees it. The let-go also closed local containers'
connections, which never sleep, taking their git credential relay with
them (Codex review on #381). The connection registry, LET_GO_GRACE_S,
Forwards::carrying and their tests go; serve.rs is back to master apart
from the removed awake-ceiling settings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@joelmoss joelmoss mentioned this pull request Oct 9, 2026
12 of 19 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants