Factory
Turn comments into tasks that coding agents finish in sandboxes, reviewed before anything is pushed.
The factory takes a comment from the canvas or the code to a reviewed branch. A person makes a task from the comment, starts it, and a coding agent works on it in a sandbox: a checkout of the project with its own dev server. When the agent is done, the task waits for review. Reviewers open the live preview, ask for changes or approve. Only approval pushes, and only the task's own dev/ branch. The Agents screen lists every run, the ones waiting for you first, and starts new work from one message; each run's agent can be followed as a chat or in its terminal.
The flow
| Step | Who | What happens | Task status |
|---|---|---|---|
| Make task | a person, on a discussion | A task with the discussion's text, a screenshot around a canvas comment with its point marked, the element there and its source line, and a link back | Open |
| Start task | a person, on the task | A sandbox with the project on a new dev/<task>-<title> branch, its dev server and the agent, which gets the task as its prompt | In progress |
| Agent works | the agent | Edits, runs the project's checks and commits; the task shows what it does | In progress |
| Needs context | the agent, then a person | An agent that can't go on without something only a person knows asks instead of guessing; the task shows its question with Needs context, and your answer goes on in the same session | In progress |
| Review | a person | The agent's summary, Open live preview, Request changes or Approve & push | Needs approval |
| Approve & push | a person, confirmed | Bud pushes the branch, then pictures it on the canvas (Show on canvas) | Ready to merge |
| Merge | your Git host | Bud notices once main contains the pushed commit and removes the sandbox | Merged |
While a run waits for review, an answer or a retry, its sandbox sleeps after sleepAfter minutes, so it costs nothing; opening the preview, requesting changes or approving wakes it again, with its dev server and agent, in under a minute. Discard removes the sandbox at any point before the push; nothing reaches the remote. A failed step says why, with Retry when trying again may help. The agent asks with a last line NEEDS CONTEXT: <question>, which Bud's prompt tells it to use; the board and the task's discussions show Needs context until someone answers. The board shows each task's run as a badge, so you can see which tasks agents are working on and which wait for you. With the factory on, the board's columns are its steps, Open to Merged, with Closed folded at the end; other statuses, such as Beads' Pinned, Hooked and Deferred, wait behind Other statuses, and only show when tasks are in them.
Turning it on
bud.config.ts
import { defineConfig } from '@bud.sh/app'
import { local } from '@bud.sh/app/plugins'
export default defineConfig({
routes: ['/'],
// Where agents work; without a sandbox plugin, tasks can't start.
plugins: [local()],
factory: {
setup: 'pnpm install',
dev: { command: 'pnpm dev --port $PORT', port: 5173 },
instructions: './bud/factory.md',
},
})Then open Tasks. The factory appears once everything it needs is there: a task store, a sandbox plugin whose sandboxes can run here (boat() needs its key) and the task store's statuses for its steps. Bud adds missing statuses to a store that allows it, such as Beads (see Factory in the configuration for the statuses and their options). Until then there are no run badges, Start task or Agent view; bud doctor says what's missing.
Without dev, runs have no preview: Agent view shows the agent's terminal and a shell, without a browser or the dev server's log.
| Option | Default | Use |
|---|---|---|
setup | none | Runs once in each new checkout, such as installing dependencies. |
dev | none | The dev server: $PORT in command is replaced by a free port, and path is where Open live preview lands. env adds values it needs; never secrets. Without it, there's no live preview. |
instructions | none | A Markdown file in the project the agent reads with every task, such as how to run the checks. |
branchPrefix | dev/ | The prefix of every run's branch. |
sleepAfter | 5 | Minutes a sandbox stays up while its run waits for people; then it sleeps with its disk kept and wakes when needed. 0 keeps it up. |
push | origin, Git's credentials | remote, and credential: the server environment variable holding a token that may push, with the username your Git host expects (x-access-token for GitHub, x-token-auth for Bitbucket). |
statuses | Beads' names | The task store's name for each step. |
herdr | false | Also runs the agent in herdr in each sandbox, so Bud shows live whether it's working, needs input or is done. |
Sandboxes
Agents work in the sandboxes of the project's sandbox plugin: local() or boat() from @bud.sh/app/plugins, or your own.
local() makes a Git worktree of your project in the system's temp folder for each run, on its branch, and runs the dev server and the agent there. Your own checkout, index and branch stay as they are; discarding or merging removes the worktree and the local branch. Runs that were working when Bud restarts fail and can be retried.
local() is for trying the factory on your own machine with tasks you trust. It isn't isolated: the agent runs as you, with your files, network and Git credentials. Bud keeps the agent from pushing to your remote, and withholds the push token, but that guards against mistakes, not against a task written to misuse it. For tasks from other people, use a sandbox provider such as boat.dev.
boat() runs each sandbox on boat.dev: a full Ubuntu machine billed by the second. Set BOAT_API_KEY on Bud's server (boat({ key }) names another variable; type picks the machine's size). Bud clones your project from its push.remote over HTTPS, with the push token when there is one, and starts sandboxes without any of your boat account's secrets: only the harness's keys and dev.env go in. Previews are boat's token-protected URLs; dev servers that check the host name need to allow .on.boat.dev (Vite: server.allowedHosts). An approved run's sandbox is stopped, a discarded or merged one deleted. Sandboxes stop on their own after two hours.
A harness of your own is a plugin with a harness (the Harness type from @bud.sh/app/plugins): how its server starts in the sandbox, a client for its sessions, prompts and events, and its model and keys.
Your own provider is a plugin, definePlugin({ name, sandbox }), whose sandbox makes a SandboxAdapter (both from @bud.sh/app/plugins) from what Bud passes: the checkout, the server's environment, the push remote as an HTTPS URL, clone credentials and the variables sandboxes must never see. The adapter creates a sandbox on a branch, runs commands, starts and stops named processes, exposes a port for the preview, and stops, resumes and destroys sandboxes. @bud.sh/factory/testing has the tests every provider passes.
The agent
The agent is OpenCode unless a harness plugin says otherwise. opencode() from @bud.sh/app/plugins picks the model, as OpenCode names it (provider/model), and keys, the server environment variables the model needs, such as DEEPSEEK_API_KEY, which Bud passes into sandboxes as secrets. The default, opencode/deepseek-v4.1-flash-free, is free and needs no key, enough for trying the factory:
bud.config.ts
import { defineConfig } from '@bud.sh/app'
import { boat, opencode } from '@bud.sh/app/plugins'
export default defineConfig({
routes: ['/'],
plugins: [
boat(),
opencode({ model: 'deepseek/deepseek-v4.1-flash', keys: ['DEEPSEEK_API_KEY'] }),
],
factory: { setup: 'pnpm install', dev: { command: 'pnpm dev --port $PORT', port: 5173 } },
})OpenCode starts as a server in the sandbox with a password Bud derives for each run. It can also read the project's whole task database, to find related tasks, duplicates and earlier decisions: Bud serves it read-only as an MCP server (bud, with search_tasks, get_task, list_discussions and recent_changes) behind a token for that run alone, which stops working once the run is pushed, merged or discarded. Agents reach it where they reach Bud: with local() on this machine, and from boat.dev when a hosted Bud sets its public address (see Hosting). Bud sends the task (with the comment's screenshot as an image, when it came from the canvas), your instructions and a few rules (commit on the branch, run the checks, never push, end with a summary) and follows its progress. Its model needs a key OpenCode can use in the sandbox, from keys, unless it's a free one; with local(), the agent also has your own OpenCode login.
Agent view
A task with a run opens Agent view from its toolbar (or at /tasks/<id>/agent): the task's details beside a browser on the run's live preview and a terminal in its sandbox, or a chat with its agent. Opening it wakes a sleeping sandbox, and it keeps the sandbox up while it's open.
- Browser. The preview runs in a sandboxed frame on its own address, never Bud's, with back, forward, reload and the address. On boat.dev the preview gets a public address only while the sandbox is up, which goes down when it sleeps, is discarded or is merged. Pick an element with the crosshair and write what should change: Bud sends it to the agent as a change, with the page, the element and its source line, while the run waits for review. The preview goes through a small proxy Bud starts in the sandbox, which adds Bud's page bridge to every page and its DOM picker, so following and picking work in any project; with Bud's Vite plugin in the dev server, picking uses your configured picker, such as React Grab, and the plugin starts no Bud of its own there.
- Terminal. Agent opens the agent's own interface on the run's session (
opencode attach), so you can talk to it and watch its tools run; Dev server follows the dev server's output; Shell opens a shell in the checkout. It's xterm.js, drawn on the GPU, so complete programs work as they would on your machine: scrolling, the mouse and keyboard shortcuts such as OpenCode's Ctrl+P. Bud starts a small terminal bridge in every sandbox (Python 3's standard library is all it needs) and each tab talks to it over its own WebSocket through Bud's own address, so typing feels local even while the preview loads; a dropped connection picks up where it left off. - Chat. A switch at the top right of the terminal's toolbar flips it to a chat with the agent, as in Codex: the prompts, the agent's replies and reasoning, its tool calls and the edits it made with their diffs, streaming while it works. Bud reads it from the agent's own session each time and keeps no copy, so what you type in the terminal's Agent tab shows here and the other way round. Write at the bottom: Enter sends and Shift+Enter starts a new line, and you can paste, drop or pick images. What you send goes where the run is: while the agent works it joins its turn, in review it asks for changes, and when the agent asked a question it's your answer. Stop ends the agent's turn, and the run waits for review with what it has. Runs that ended take no messages.
- Layout. With room, the terminal or chat sits beside the browser; its button hides it and the split button puts it below. In a narrow window or panel, a switch at the top picks the details or the agent's panes, and one in the toolbar the browser or the agent's pane. Bud remembers the layout and your choice of terminal or chat for you. The terminal and the agent's interface take your theme, light or dark as Bud is, and switch with it while they run.
Agents
Agents in the rail lists the factory's sessions, one per task with a run: those waiting for you first (a question, review or a failure), then those at work, then the ended ones, each with its state, when it last changed and who started it, live. Search finds one by its title or task. Opening one (/agents/<task>) shows its header, the task's title linking to its page, the branch and the run's actions (Approve & push, Discard, Open live preview, Agent view), and below it the agent's chat or terminal, with the same switch as Agent view.
Without a session open, the screen asks What should the agent do? in one large box. Sending it makes a task on the board from it (the first line as its title, the whole message as its description, you as its creator) and starts its run at once, then opens the session as it sets up; images pasted or dropped go to the agent with the task. On a phone, the list and a session are two steps, with a way back to the list. The screen shows once the factory is set up.
Live status with herdr
With herdr: true, each sandbox also runs herdr, a terminal multiplexer that knows what coding agents do, and the agent's interface in it on the run's session, so Bud shows live what the agent is doing: Working, Needs input (such as a question or a permission it waits on), Idle or Done. The board's run badges, the task's Agent section and Agent view's toolbar show it within a second of each change, also for work you start from the agent's terminal or the chat while the run waits for review. The run's own steps stay as they are; herdr's status is the live activity on top.
Bud installs herdr's pinned release in each sandbox the first time, checked against its checksum (or uses herdr already there at that version), with herdr's OpenCode integration, so OpenCode reports its own state. If herdr can't be installed, the run goes on without the live status. Agent view's Agent tab keeps opening the agent's own interface on the same session in your theme, since herdr doesn't pass a program's color questions to the terminal; the chat, the terminal and herdr all follow one session. Without herdr, nothing of it runs and no live status shows.
Security
- Credentials stay on the server. The push token reaches only Bud's push and remote check, through a credential helper set for that one command; it's never written to the checkout, its Git configuration or the agent's environment. Runs, task data and the browser never hold a credential.
- Review comes first. Nothing leaves the sandbox before a person approves, and Bud pushes only the run's branch, without force. Protect
mainon your Git host, and allow the push token onlydev/*. - Tasks are prompts. Anyone who can write a comment or task can tell an agent what to do. Agents may edit, run commands and fetch from the web inside their sandbox, so give sandboxes only what a task needs: a dev server that talks to production services can change production data.
- Previews are links to the run's dev server: with
local(), a local port; on boat.dev, an unguessable public address while the sandbox is up, shown only to people signed in to Bud. They stop working when the sandbox sleeps, or the run is discarded or merged. - Terminals are opened by Bud, with a password derived for each run that never leaves the server; a browser gets a ticket for one session that's good for a minute, which only opens a socket through Bud, from Bud's own pages; Bud checks it and pipes the socket on to the sandbox, so the browser never connects to the sandbox itself. Everyone who can act on runs can type into them, as into the agent itself.
- The task database is read-only for agents: the MCP tools can search and read tasks and discussions, never write, and each run's token stops working when the run ends. An agent can read everything in the task store, so keep secrets out of tasks and comments.
- Who can act. Everyone who can open Bud can start, approve and discard runs. A hosted Bud names them from its sign-in.