Roadmap
11. Existing platforms
| Option | Use | Gap |
|---|---|---|
| Coder | Workspace portal, IDE access | No task supervision; Apple backend unverified; external provisioners Premium |
| DevPod | Portable environments | Client-only; no central service |
| Portainer | Infra UI, templates, REST API | No agent progress/review semantics; add-ons need Business Edition + Kubernetes |
| Socktainer | Docker API over Apple Container | Partial compatibility; exec and restart recovery limits |
| Eclipse Che | Kubernetes dev workspaces | Heavy |
| code-server | Browser editor | No orchestration; Open VSX |
| Agent-task supervisors (OpenHands, Vibe Kanban, Sculptor, Coder Agents) | Assessed by desk research (issue #5, D22): none fits release 1 | See the scorecard below |
Agent-task supervisors and vendor remotes (desk research from docs and repositories, 1 October 2026, issue #5; nothing was installed, so every capability below is as documented, and the marked ones are unverified). Fit is against release 1: phone or browser remote, detached runs, mid-run messages, approvals routed to a human, Apple Container, default-deny egress, supervisor-only push, more than one forge.
| Candidate | Runtime | Agents | Approvals to a human | Push | Forges | Fit | Verdict |
|---|---|---|---|---|---|---|---|
| OpenHands 1.24 (MIT) | Docker or a plain process; no Apple Container | Own agent; Claude Code and Codex over ACP | Confirmation mode for its own agent; for ACP agents unverified | Agent holds forge tokens | GitHub, GitLab, Bitbucket | 5 | Borrow: ACP as an adapter protocol, the confirmation states |
| Vibe Kanban 0.1.44 (Apache-2.0) | Host worktrees, no isolation | Claude Code, Codex, Gemini and others | Plan approval cards | The UI pushes, opens and can merge PRs | GitHub, Azure Repos | 3 | Reject: the company shut down and its phone pairing went with its cloud service |
| Sculptor 0.48 (MIT, research preview) | Desktop app; Docker backend experimental | Claude Code, Pi | None: tool permissions are auto-approved | Pushes and opens a PR on a click | GitHub | 3 | Borrow: its Claude control-protocol integration and editable message queue |
| Coder Agents 2.36 (AGPL; Coder Tasks was removed) | Terraform templates; no Apple Container provisioner | Its own loop on API keys; no Claude Code or Codex subscription | Plan mode only | Agent pushes as the user | Through templates (unverified) | 2 | Reject |
| Claude Code Remote Control and on the web | Local CLI relayed by the vendor, or vendor VMs | Claude only | Permission modes; approval from the phone unverified | The cloud agent pushes itself | GitHub only | 3 | Complement: a reference phone UX and a fallback for Claude |
| Codex cloud | Vendor containers | Codex only | unverified | User-initiated PR | GitHub only | 2 | Complement |
The original rating table missed agent-task supervisors; it is re-rated here (D22).
| Strategy | Original fit | Revised note |
|---|---|---|
| Existing runner + thin supervisor + native Apple Container | 9 | Preferred; Claude Code first, Codex CLI second (D12, §5.2) |
| Same supervisor via Portainer/Socktainer | 7 | → ~4–5; optional shim only |
| Coder workspace layer + task supervisor | 7 | → ~3: Coder Tasks was removed and Coder Agents runs its own loop on API keys (D22) |
| Portainer + templates alone | 4 | Insufficient |
| Full new Codespaces/DevPod replacement | 3 | Excessive scope |
| Adopt/extend an agent-task supervisor | — | 2–3: none supports Apple Container, default-deny egress, host-routed approvals under subscription logins and supervisor-only push together (D22) |
12. Open decisions and spikes (reordered)
Ordered by what is cheap and blocks the most work. The measured results are on the spike pages.
- Runner scorecard (value 10, effort 3). Target agents are Claude Code, Codex CLI and Google Antigravity; Aider, OpenHands, Goose and others are scored for reference. Claude Code ships first and Codex CLI second. Antigravity ships a CLI (
agy) with a headless print mode, so the gate is met: spike #1 drove it headless with typed events and resume. It has no mid-run injection and no approval channel in print mode, so it is a second-tier adapter in degraded mode (§5.2). Its account requirements and vendor terms for headless use are unverified. Spike #1 (issue #1) measured Claude Code, and Codex CLI and Antigravity in part; the results are in §5.2, and still open are a real Codex run (usage limit until 3 October), a login that expires mid-session, what the usage-limitstatusreads once exhausted, and approvals for Codex and Antigravity. One page comparing them on: headless mode, permission/approval bypass, session-ID resume after process or VM kill, mid-run message injection (stdin vs resumed turn; a release 1 requirement, §5.2), structured event output (also required), how “blocked, needs human” is reported. Also score subscription sign-in for Claude Code and Codex CLI (all unverified): headless or device-code login, where the token is stored, whether it survives a container restart and a Mac reboot, refresh behaviour inside a container, what happens when two environments share one login, how an expired login or exhausted usage window is signalled, current vendor terms for this kind of use, and whether a run keeps going with no client attached. Pause via SIGSTOP or stop-after-turn is not a resumed session; most CLIs resume only between turns. - Adopt-or-extend (value 9, effort 3): decided by desk research, build (D22, §11). Hands-on checks of the unverified claims are optional and only worth doing if a candidate adds Apple Container support or host-routed approvals.
- Apple Container native spike, merged with benchmarking. Run as spike #2 (issue #2, branch
spike/apple-container,RESULTS.md); measured on Apple Container 1.5.0, macOS 26.6.2. Compatibility checklist:- Create/start/stop/delete representative workspaces (start about 1.1 s; use
--init, §5.1) - Enforce explicit CPU/memory (vCPU count and a cgroup limit inside the VM)
- Preserve project data across stop/start and rebuild (volumes and bind mounts; §4.4)
- SSH, VS Code, selected JetBrains IDE (code-server optional). Not tested;
execworks - Recover after runtime/manager restart (§5.3): every container comes back
stoppedwith its data - Recover after a Mac reboot (LaunchAgent vs LaunchDaemon; auto-login/FileVault implications). Not triggered; no plist for the services exists on disk, so
container system starthas to run after login - Private registry pulls and credential handling. Public pulls from Docker Hub worked; private registries not tested
- Agent session and its auth directory survive environment stop/start and rebuild: after
container stopandstart, the same agent session resumed from the home volume and remembered an earlier instruction, with the login supplied per exec. Surviving a Mac reboot was not tested - VPN reachability, forwarding or jump host. Not tested
- Default-deny egress and network isolation controls (
--internalnetwork plus a proxy sidecar; §7.2) - Escape tests: guest cannot reach host or sockets; forbidden mounts rejected (mounted unix sockets unusable; mount rejection is the adapter’s job; §7.4)
- Memory behaviour at 1 then 4 instances, including pressure and swap; 8 only once 4 is measured (#39). Not started; an idle agent in a container used about 277 MiB
- Stock images with a shared read-only tool store (§5.6)
- Agent run inside a container with a real login (spike #2,
05c-agent-run.sh): a stock image on an--internalnetwork, tools from the store, the model reached only through the proxy sidecar, the spike #1 harness on the host driving it with live events, token deltas, a mid-run message and a resume after a container restart. The agent container used about 290 MiB - Approvals from inside the container (§4.2, D26): the stdio control protocol over
container exec -i, measured with its crash and deadline cases (spike #7) - A reliable cancel from the host (§5.1, D25): measured in spike #10 using the
whr-shimlauncher from the tool store (§5.6, D19). Signalling the host exec client fails; signalling the process group viacontainer exec <id> /tools/whr-shim killterminates cooperative processes in ~10 ms and stubborn trees after a 500 ms grace in ~514 ms, with 0 orphans left - Repositories mounted from the host (§4.5): bind-mount speed with
node_modules-style trees and much larger repositories
- Create/start/stop/delete representative workspaces (start about 1.1 s; use
- Autonomy and approval policy (§6) and threat model (§7): decided (D36); the policy table is implemented with a fixed floor (issues #4, #51), and the threat model is written (issue #11). Trust tiers for untrusted input remain (issue #53).
- Persistence semantics (§4.4): decided (D16, D39). Open: a checkout on a volume for repositories that track more than about 50 000 files. It needs the branch exported from inside the guest for hostgit, for example as a bundle written by a disposable helper container that mounts the volume read-only; measured cost of seeding such a volume: 68 s for 153 000 files (spike #40).
- Primary forge for release 1: decided, GitHub through a GitHub App (D15). A login provider is not needed before OAuth; release 1 signs in with a static token (§9.5).
- CI credentials and event handling for Gitea/Drone (medium term).
- The
whrgrammar of the slice is decided (D37); the stack too (D3, D8, D14).
Reboot considerations also include power-loss/UPS behaviour and macOS auto-update reboot policy.
13. Delivery
Release 1: one vertical slice
Built CLI first (D12): the slice is the core loop through whr; the web UI and the phone client come after it. The Dogfood milestone comes first: the subset that lets workharbor run its own issues (D34).
- Apple Container backend, one host, native adapter
- Built-in agent adapters for Claude Code (first, built) and Codex CLI (a target: not built, #35; §5.2, §5.5), with observed progress and validated recovery
- Task/workspace/run/decision model with durable state, event log, reconciler
- Workspaces with named agents (D42) in two steps: the dogfood slice runs one named agent per workspace, with its agent clone and the bundle export (#90, #91); several agents in one environment at once follow in R1 Complete (#94)
-
whrCLI (scripting contract, completion,doctor) - Web UI with inbox, live transcript, send-message, start task, pause/resume/cancel, transcript purge (§9.3, §5.4)
- SSH access (certificates,
whr ssh --config) - Policy table, per-run credentials, egress proxy, resource budgets, audit log,
whr kill-all - Installable PWA as the phone client: web app manifest and a service worker for the app shell, so the remote-control UI (§9.3) installs to the Home Screen. Stays inside the server-rendered stack (D8), needs HTTPS on the VPN hostname, and signs in with a per-device revocable passkey (D45), not a token
- Static-token login for the host, which also signs in to the web UI until the first passkey is enrolled (D45; see the matrix)
- GitHub through a GitHub App installation (D15)
- Runtime and forge adapters as interfaces with one implementation each
Explicitly out of release 1: code-server, JetBrains validation, OAuth, editor launch and takeover in the UI, CI adapter, multi-host, scheduler beyond an admission counter.
Capability matrix: as built (issue #248)
What the checklist above promises, and how far each item has come, as read from the code of main (the composition is internal/serve/real.go and serve.go) and from the named spikes. Four stages, each including the one before:
- Specified: the design or a decision describes it.
- Implemented: code and tests exist, mostly against fakes.
- Integrated:
whr servecomposes it (serve.Build,serve.Run), so a running supervisor uses it. - Measured: a named spike or live test showed it working on Apple Container 1.5.0. The spikes ran on a development Mac, so no row is measured on the reference Mac mini until the slice demo (#28) runs.
A target in the checklist above is a target, not a claim, until its row says integrated. The status shortcode says how far the evidence goes: verified for what a named spike or test measured, unverified for what is built but not measured, decided for what is only specified, open for what has no code or an open question.
| Capability | Stage | Evidence and open ends |
|---|---|---|
| Apple Container runtime adapter, whr-shim cancel, volumes and bind mounts | Measured | Spikes #2, #10, #40 and builder-store: verified. Reached from the real stack in the integration run: verified up to the agent’s first request |
Egress proxy sidecar (whr-proxy) on an internal network | Measured | The integration run: the guest’s direct request failed and the same one through the sidecar reached the allowlisted host (verified). The host’s global IPv6 prefixes are refused in tests only (unverified, threat model T5) |
| Claude Code agent adapter | Integrated | claude.New in Build. The integration run reached Claude’s login prompt (verified); a signed-in turn and resume wait for a logged-in agent home on the dev host (#82’s spike, agent-signin.md, is closed), and the quota stop waits for #36 (unverified) |
| Codex CLI agent adapter | Specified | §5.2 and spike #1 describe the degraded mode; no adapter exists, and Build has no Codex path. Issue #35 is open: open |
| Antigravity agent adapter | Specified | Medium term (§5.2, spike); no adapter: open |
| Task, workspace, run and Decision model, event log, reconciler | Integrated | Run reconciles at start and every 30 s; the integration run reconciled, stopped on SIGTERM and showed the inbox. Recovery of a real interrupted agent run is tested on fakes only (unverified) |
| Workspaces with named agents, agent clone, bundle export | Integrated | #90 and #91 (closed). Several agents in one environment at once is #94 (open): decided |
| Publish path: prepare, the check, per-SHA approval, signed commit, push (D51) | Integrated | PublishFor in Run, and off with a logged line when a pusher, committer or repository copy is missing. Not run against GitHub: unverified until #28 |
| GitHub App forge adapter behind the Guard, the policy table and workflow presets (D15, D47) | Integrated | newGitHub, NewForgeAccess and NewIssueAccess, which hand serve the narrow access, not the raw adapter (#247). Only a stand-in for the GitHub API was used; the real App waits for #73 and #27: unverified |
Per-run credentials, resource budgets, audit log, whr kill-all | Integrated | #37 (closed): service.AgentCredentials, the budgets in the configuration, store audit entries, the kill-all command and API route. API-key mode with a key proxy is medium term (#83, D48): decided |
| JSON API on a host-only unix socket, API token | Integrated | api.ListenSocket and api.Listen in Run (D29). Loopback unreachable from guests: verified (#69); the forwarder and pf rules are not measured (unverified) |
| Web UI: inbox, live transcript, send-message, start, pause, resume, cancel | Integrated | web.Options in Run, htmx and SSE. Not driven from a real phone: unverified |
| Web sign-in: the host token, then passkeys (D45) | Integrated | See the note below. Passkeys need public_url; without it the log says they are off. Untested on a real phone: unverified |
| Installable PWA shell | Integrated | manifest.webmanifest, sw.js and the offline page in internal/web/static, served by web. Installing to the Home Screen on iOS: unverified |
| Console and SSH access with short-lived certificates (D43, #32) | Integrated | console.ssh_ca_key_file loads the authority in Build; the console image is built by ensureConsole. The image build has a live test that skips without container; an SSH session on the host is unverified |
| Previews (D33, #72) | Integrated | serve.Run wires them when the configuration turns them on, but the Apple adapter does not implement runtime.Previewer, so opening one says the runtime cannot reach the port. Waits on #69’s forwarding result: open |
| Devcontainer features and a repository’s Dockerfile (D38) | Integrated | Environment in Build. The libraries are tested against a fake registry; the build itself is the spike’s: unverified end to end (§5.1) |
| Notifications through ntfy (§9.4) | Integrated | ntfyNotifier. Web Push is medium term: decided |
whr doctor, whr setup, completion, launchd install | Implemented | internal/doctor, internal/setup, internal/launchd; commands in internal/cli. Run on the reference host only with --dry-run by the lanes: unverified |
| Release pipeline and the install from a draft (D24) | Implemented | Not measured against a real draft release (T19, #180): unverified |
Tool store: pinned agent CLIs, whr-shim, whr-proxy mounted read-only | Integrated | checkToolStore and toolProfile in Build, SpecOptions. Spike musl-cli measured the musl builds (verified) |
| CI adapter | Specified | An interface only (internal/ci, §10); Drone is medium term: open |
| Docker and Podman backends, remote hosts, Firecracker, Proxmox, vSphere, a Windows host | Specified | Medium and long term; no code: open (#140) |
Web sign-in as composed. The checklist items “Single static-token login” and “per-device revocable tokens” for the PWA are the plan that D45 replaced. Run builds token authentication (web.NewTokenAuth) always, and a passkey service only when public_url is set (serve.go, the passkey block). The host token signs in to the web UI until the first passkey is enrolled; then OnFirstEnrolled ends the sessions the token started, and from then on each phone or tablet signs in with its own passkey, which the administrator revokes per device (OnRevoked ends that passkey’s sessions). Enrolment and revocation happen only from the host. There are no per-device tokens. The checklist items above say so.
Codex as built. The release 1 checklist and §5.2 name Codex CLI as a built-in adapter. Build composes only the Claude Code adapter (claude.New); Codex is issue #35. D12 and the threat model’s scope line, which are in rule sections, also name Codex as the second agent and are for wh/design to read as a target.
Releases (D24)
- When. The pipeline is built during release 1 and stays dormant; the first release,
v0.1.0, is cut when the slice demo (issue #28) passes, so the Mac mini runswhr servefrom a released binary under launchd (issue #38). Until then, a signed prerelease tagv0.1.0-alpha.Non a green commit ofmaingives a dogfood build: its draft is never published, and the administrator installs it withmake install-release VERSION=<tag>, which downloads the draft withgh(a repository writer’s login), checkschecksums.txtand the build-provenance attestation (signed by this repository’s release workflow) and installswhr,whr-shimandwhr-proxyinto an admin-owned prefix (default/opt/whr). Then one0.xrelease per milestone.v1.0.0waits until the JSON API (OpenAPI, D3), the adaptercontract_versionand the database migrations are stable and an upgrade with a backup has been tested. - Version. The tag is the only source:
git describeis stamped into the binary with-ldflags, built with-trimpath;whr versionprints the version, commit and whether the tree was dirty, andwhr version --jsongives the same as data on stdout with aschema_version. Without a tag the version isv0.0.0-<commits>-g<sha>, never empty, and a build that was not stamped (plaingo build) reads it from the Go build info. No version file. - Prepare. For a release to publish (not a dogfood prerelease), an ordinary commit, which an agent may make:
chore(release): prepare vX.Y.Zregenerates CHANGELOG.md with git-cliff for that version. CI must pass on it. - Tag. Only the human, signed and annotated:
git tag -s vX.Y.Z. A tag ruleset lets only the repository admin createv*tags and forbids updating or deleting them. - Build. A workflow triggered by the tag checks that the tag is annotated and signed by a known key (an SSH key listed in
.github/release-signers), that the tagged commit is onmainand that CI passed on it. It then runs GoReleaser (pinned):darwin/arm64first,linux/arm64andlinux/amd64for later remote hosts, checksums, an SBOM, a build-provenance attestation and release notes from git-cliff, into a draft release. Only that job getscontents: writeand the attestation permissions. - Publish. The human checks the draft and publishes it. A second workflow, triggered by the publication, updates the Homebrew tap (
brew install wstein/tap/whr), so the tap never points at a draft; a prerelease does not update it. The workflow checks the assets’ checksums and attestations, renders a formula (the macOSwhrplus the guest binaries as a resource inlibexec/whr, and the shell completions) and pushes it towstein/homebrew-tapwith a deploy key of that repository only, kept in ahomebrew-tapenvironment. - macOS distribution. The tap is the supported install path. Signing and notarizing a downloaded binary need an Apple Developer ID and are deferred until someone other than the developer installs it from a download.
Medium term
Auth-mode phasing (D41): subscription logins first, with the guardrails of D40 (quota stops, the shared usage window, the agent’s permission mode; #36, #48, #82); API keys as a full peer in the medium term (#83); an enterprise offering in the long term.
- API-key mode as a full peer (D41, #83): the host-side key proxy that issues a key per run (§7.3) instead of the configuration’s key file, spend budgets (#37) and per-key usage (#48)
- OAuth providers, Gitea/Forgejo/GitLab/GitHub adapters
- Docker/Podman backends, remote Linux hosts (host worker becomes remote-capable)
- Alpine as a first-class console base (#93): a small image and a fast package manager for the human’s console (D43); no agent runs there, so musl only affects the human’s own tools
- Firecracker as a runtime target (#140): a microVM with its own guest kernel per environment, the isolation model of Apple Container, on a Linux host with KVM (remote, or a Linux VM on the Mac if nested virtualization allows it, unverified). It boots a kernel and a root filesystem rather than an OCI image, so stock and devcontainer images need a conversion or Kata Containers or firecracker-containerd underneath; the spike decides, and the runtime conformance suite is the gate
- Antigravity adapter in degraded mode (
agyprint mode, §5.2, §12) and additional runners - Out-of-process adapter plugin loader with conformance checks (§5.5, §7.8)
- Drone CI with revision-aware feedback
- Resource-aware scheduling, recovery improvements
- code-server, UI editor launch and takeover,
whr top - Web onboarding wizard over the same service layer (§9.5); release 1 uses
whr loginandwhr doctor - Web Push alongside ntfy (§9.4) for the installed PWA. iOS Web Push needs the app installed to the Home Screen (unverified)
Long term
- An enterprise offering: several developers on one supervisor under the vendors’ commercial terms (Team or Enterprise plans, API keys or a cloud provider), with per-user identity and audit. D40’s one-human rule is for consumer subscriptions; this needs its own decision (D41)
- Proxmox VE and VMware vSphere/ESXi as runtime targets (#140): environments as full VMs on an existing virtualization cluster, each with its own kernel (a Proxmox LXC container shares the host kernel and does not meet §7’s bar), driven through their APIs with credentials scoped to one pool or folder, an isolated network per environment with the egress sidecar, and the runtime conformance suite as the gate. A full VM boots far slower than Apple Container’s 1.1 s (spike #2), which may call for pooled environments
- Alpine as a first-class base for agent environments (#93), once each agent CLI has a musl build that passes
agenttestand the common toolchains work on musl (Python wheels, Node native modules); the tool store then pins musl builds next to the glibc ones (D19) - A Windows host with Hyper-V VMs (#140): the supervisor on Windows, each environment a Hyper-V VM with its own kernel. A host port, not only a runtime: the Windows counterparts of Homebrew (D28), the
pfrules (D29), launchd and0600secret files are needed, and whether Firecracker (#140) can run inside WSL2 is unverified - Other microVM/VM platforms, Kubernetes where useful
- Multiple hosts and placement policies
- Wider forge/CI coverage
Not planned:
- Native iOS and Android apps. The installable PWA (release 1) is the mobile client; the JSON API stays the contract for any client.
- VirtualBox as a runtime. One VM per environment would meet §7, but it adds little next to Apple Container on the Mac and Firecracker, Proxmox or VMware on Linux; it is driven only through
VBoxManage, boots full VMs slowly, and its shared folders are slow where spike #40 already found bind mounts costly. Whether it runs on Apple-silicon Macs is unverified. - WSL2 as a runtime. Its distributions share one utility VM and one Linux kernel, so environments would be separated by namespaces only, which fails §7’s bar of a kernel per environment. Windows is a host platform instead (#140).
Phases are proposals, not a schedule.