The loop for building
This is how wsp gets built: tickets go out as threads on workspaces, opened by an agent on the person's computer, and reviews come back the same way. The loop below is the one that works on 2 vCPU and 4 GB machines.
The loop
-
One workspace with the repo imported.
wsp import <folder> --to <workspace>on a fresh workspace, the app's import dialog, orwsp new <name> --from <project golden>from a snapshot taken after an import.wsp threads --in <workspace>shows what is on it. -
One thread per ticket, each in its own worktree. The brief opens with
git worktree add -b ticket/<n>-<slug> <folder> origin/main, and--cwdpoints at the repo. Two threads writing in one checkout collide. -
A brief that can be built. It names the ticket, the files to read whole, the laws the repo's review skill holds, the exact test commands, and the proof required. It says to run every command in the foreground and wait for it. A reply that lands with a command still running in the background reads failed, with the reason
ended with 1 background task running, and the thread takes no send until that process exits. A brief that says "fix the bug" comes back with a guess. -
Start detached, then wait.
wsp thread new --in dev --detach --title "413 build" "<brief>"prints each thread's id and returns. Thenwsp threads wait <id>...blocks until one of them finishes and prints its finished line, one thread per call; drop each returned id from the list and call again. Never pollwsp threadsfor a state change. A thread on a machine that wants its builders' ends as messages starts them with--notify <its thread>; a person uses--notify me. -
Review in its own thread. On the same workspace, with the branch name and the review skill. The builder fixes in its own thread through
wsp send. A review runs well on a cheaper model,--modelonthread new. -
Work leaves the machine by
git pushfrom the thread, when the golden signed in to GitHub duringwsp init, or bywsp exportto your computer. -
Gates run one at a time. Full test runs, builds and packaging run on the person's machine or one at a time on the workspace. A 2 vCPU, 4 GB machine runs one build or one agent at a time; two starve the daemon and the app reads the machine as not answering.
Running work on a workspace well
- Snapshot after the install. Once a project's dependencies are installed on a workspace,
wsp snapshot <workspace>keeps that disk as a project golden, andwsp new <name> --from <that golden>starts every later workspace with the install already there. No thread installs the same dependencies twice. - One heavy thread per machine. One thread runs tests or a build at a time; a second thread is a light send. Three test runs at once starve the machine and every turn in flight fails.
- Make the worktree cheap, not skipped. pnpm's side-effects cache compiles a native module once per machine, and the checkout's
node_moduleshardlinked into the new worktree beforepnpm install --offlineleaves only a verify. A worktree set up from scratch relinks thousands of files and rebuilds native modules, minutes on 2 vCPU. - Send only into a thread that has replied. A send into a running turn opens no second turn. Wait for the turn to end, or
wsp stop <thread>first. - Finish turns before restarting the host.
wsp downandwsp upcut every turn running on every workspace. - Pause what nobody uses.
wsp pause <workspace>costs nothing beyond the disk and gives back one of the two machines the account runs at once. The next thread or command wakes it. - Name threads. The person's app and the command line read the same host, so every thread opened here shows in their sidebar.
--titlenames it, and a short, complete reply reads well there.
Rules learned the hard way
Each of these cost a day once. They are in the code now, and they are here so nobody pays twice.
- A send into a running thread steers it or waits behind it, never a second concurrent turn.
- A thread's end reaches its parent thread or the person only when its start said
--notify; nothing polls. - An agent on the person's computer starts threads with
--detachand reads their ends withthreads wait, one per call.thread_newwithout it blocks for the whole turn. - A thread works in
--cwdor the workspace's project folder; a relative--cwdis refused before anything starts. - A turn ends on 10 minutes of silence, not a 15 minute wall clock, and at 6 hours in all. Long steps must print.
- A turn's process group dies with the turn. A server that must outlive it starts with
setsid nohup ... &. thread newandsendreturn when the reply is complete, not when the process exits minutes later. A send that meets that gap is refused in words naming the thread.- Snapshot only a running first-life machine with a project loaded; a woken machine is refused.
- Export refuses an existing folder;
--replaceoverwrites it on purpose. - A resumed thread runs in the folder its session started in, whatever folder is followed in the app.
- A send carries its own request id, so two clients sending the same text do not adopt each other's turn.
- The MCP server and the command line refuse a host of another version in one line; restart it with
wsp up.
Not here yet: a GitHub credential on the machine outside a sign-in during init. If gh auth status fails on the machine, export instead of pushing.