Build & Ship
A run is an agent building one task. Runs happen in isolated git worktrees — each task builds off a fresh copy of the project's base branch (main unless you've set another) in its own directory, so runs never touch your working tree or each other's. You watch the whole pipeline on one Build Board and accept the finished work — branches or PRs — from its Review column.
One task
$ ptln work "add a dark-mode toggle to the navbar"Runs a single task autonomously in a sandboxed worktree and leaves a branch for you to review. It uses read/edit tools only by default and never pushes — a human merges.
Flags: --worktree <name> · --thread <id> (attach a Context Thread) · --allow-bash (let it run shell commands) · --engine <e> (defaults to the repo's registered engine — which engine builds) · --model <m> · --timeout 20m.
A backlog
$ ptln crank --file backlog.txt --thread checkoutDrives a backlog one task at a time, each in its own worktree, all sharing one context thread. It halts on a cap, a token budget, or a run of failures, and prepares N branches for review — nothing is pushed or merged by default.
Flags: --thread <id> · --max N (stop after N tasks) · --max-tokens N · --halt-on-fail K (stop after K failures) · --timeout 20m · --allow-bash · --engine <e> · --model <m> · --resume · --literal (every non-blank worklist line is a task, # included) · --max-repairs N (0–5 in-run repair rounds; defaults to 2).
Fleet mode claims tasks from the run store (dispatched from the board) instead of a file:
$ ptln crank --claim --run <id> --workers 3The board
You don't have to type commands. Promote a task from a planning session and it's dispatched to the daemon on the right machine — in the right project directory — with one click. Everything then moves left to right across one Build Board (Build in the nav), six lanes:
- Planning — the planning sessions still in flight, so work is visible before it's buildable. A card is either
● session open— click it to rejoin — or⏸ N items need answers, meaning plan leaves scored below the readiness floor; clicking that one reopens the conversation that produced them. A plan filed from the terminal is a record rather than a conversation — no transcript, no agent to rejoin — so its card opens its own page instead of trying to reopen anything. Both card kinds carry a✕, and it means something different on each: on an open session it ends the party (after a confirm — connected agents disconnect and any unanswered drafts are archived); on a needs-answers card it archives just those below-floor drafts. Neither ends the record: the session stays under History and can be reopened either way. - Backlog — promoted, waiting. Drag to reorder, or drag a card onto Building to start it now. This lane has no sort control on purpose: its order is the execution queue.
- Building — an agent on it. The card streams the run's state; its page shows every step.
- Blocked — a run that stopped and needs a decision. Also where
Needs your eyescards sit: an autonomous run (one a trigger started, not you) that finished and has something to tell you. It isn'tShipped— nothing shipped — and it isn't a failure, so it gets its own state rather than disappearing into the lane you stop reading. - Review — passed the gates, waiting on you. Each card carries its PR link and the reviewer's grade (A–F). When your team's review gate is on, the PR opens as a draft: Accept marks it ready to merge (and arms auto-merge if the policy says so); Decline closes the PR and asks whether to re-run the task or send it back to the backlog; Discard archives the card without touching git. Multi-select to Retry or Discard several at once, or drag a card onto Shipped to accept it.
- Shipped — done and merged.
Every lane except Planning and Backlog has a sort caret — newest-first by default, click for oldest-first — and every lane header has a « that tucks the lane away into a thin rail showing its count and one tick per card. Empty lanes rail themselves automatically; the ones you collapse by hand are remembered across visits, and the board renders at the right widths on the first paint rather than snapping after load. Filter by machine, project, status, or owner, and group cards within lanes. On a phone the board shows one lane at a time with arrows to step between them.
Promote a whole feature as a chain: its tasks run one at a time in order, halting if one fails. Making a chain applies immediately — the cards snap into contiguous execution order under one ⛓ container rather than waiting for a reload, so you can't accidentally scatter a chain you just created. The PRs are stacked — each task builds on the previous task's branch and opens its own PR against it, so every task sees the work of the ones before it (no blind conflicts) and each PR shows just that task's diff. Merge them in order: as each PR lands, GitHub retargets the next one to your base branch. The chain is a single container on the board — a sequence of small, focused PRs, not one giant one.
Run mode — on an auto project, promoting runs it
Every machine decides, per project, whether work dispatched to it starts on its own. The setting lives in that machine's own local registry, and it has two values:
auto— a promoted run starts immediately, unattended. Nobody is asked.ask— the run queues until someone approves it at that machine'sptln daemon runconsole.
auto is the default, because registering a directory is itself the affirmative act: ptln daemon add-project (or ptln project setup) is the machine's owner declaring that directory
available for their team's agents to build in, and asking again at every run asks the same question
twice. So on a machine set up the normal way, promoting a task is not queueing it for later — it is
starting it, on someone's hardware, spending model budget, right now. Say that out loud before you
promote on someone else's behalf.
Change it with set_run_mode from an agent, or when you point a machine at a project from the web.
One caveat worth knowing before you choose ask: the approval prompt renders only in a foreground
ptln daemon run console or the mux banner — if the daemon runs as the always-on background
service (the normal install), there is nowhere for the prompt to appear, and runs will sit waiting.
Readiness is a warning, not a wall
Tasks carry a readiness score from the Planning agent, and 4/5 is the floor for starting one. Below the floor, the Start button stays enabled — it reads ▶ Start · 3/5 — and clicking it asks you to confirm rather than refusing:
Start this before it's fully specified? Readiness 3/5 — below the 4/5 floor, so the plan may still owe an answer. The agent will build from the spec as written. If something genuinely is unresolved, reopen the plan instead — but a stale score is not a reason to be stuck.
Start anyway proceeds. Every start path goes through that same confirm — the Start button, a multi-select batch start, and dragging a card onto Building — so there is no gesture that refuses you outright. A stale score shouldn't be able to strand work you know is fine.
The floor is advisory by design: /api/v1/runs/[id]/start has no readiness check, so a client that refused would have been a dead end with no way out of the product. Runs with no plan item behind them (a hand-enqueued run) have no score and are never gated.
The Git panel
A run's page carries a Git panel listing the branch each task produced — with its pull request link, or a local only — on <machine> badge and the git checkout line when the branch was never pushed. A project's page has the same inventory across its recent runs, under Branches. Both are built from what the control plane already knows (run_tasks branches and PR urls); neither asks a machine anything, so there's no ahead/behind count and no check that the worktree still exists.
A run's page also keeps the full record: the tamper-evident event ledger grouped into attempts (newest first — retries stay legible), the verify and review output in full, honest spend — fresh tokens (new input + output, never the cache-inflated total) and the provider's own dollar cost — and Continue / Restart controls, including an automatic pause-until-reset when the model provider rate-limits mid-run. Need a second opinion later? Request review re-runs the independent reviewer on demand — on the machine that built the run, or (on the latest CLI) any other machine with the project when the builder is offline and the work is already pushed as a PR.
The run list
Runs (/runs) is the plain list behind the board: every run your team can see, newest first, each showing its project, status, thread and task progress. It is read-only — a run is driven by the daemon, not from this page — and New run (/runs/new) is the one exception: it enqueues a crank worklist directly, picking a machine and project, a thread, a preset, one task per line, an optional token cap, and a merge policy of manual or open PR. That's the web twin of ptln crank --file; the board is the twin of promoting a plan.
Which engine builds
Every phase — plan, build, review — runs the engine you pick, resolved in order: a per-run override (the Start control's engine · model disclosure, or --engine) → the project's phase setting (project settings) → the machine's registered engine (ptln daemon add-project --engine) → claude. The set is closed: claude · codex · gemini · opencode · goose · antigravity.
Each engine runs at the strongest permission posture it can actually enforce, and anything an engine can't do is logged on the run, never silent. On non-claude engines today: no live streaming (output is buffered), no session resume (an interrupted task restarts in its worktree), and build workers don't get context-thread remember/recall tools yet.
The hard limits:
- antigravity is interactive/planning-only. It has no enforceable headless mode — its only headless knob is a full permissions bypass, which partyline never passes — so build, review, and one-shot describe jobs refuse it, fail-closed. Interactive sessions and party/planning launches work (deny-by-default).
- codex builds require
--allow-bash(or bash enabled on the run) — codex can't separate file edits from shell, so without it the run refuses with an actionable error. - gemini needs the project directory trusted — it refuses headless tools in untrusted folders. Trust it once interactively, or set
GEMINI_CLI_TRUST_WORKSPACE=true. - opencode models are provider-scoped (
provider/model); blank falls to your opencode config's default. Needs a daemon on CLI ≥ 0.25.0 (older daemons refuse it fail-closed). - goose resolves provider and model from its own config (
GOOSE_PROVIDER/GOOSE_MODEL); read-only runs are refused (goose can't enforce that posture). Needs CLI ≥ 0.25.2. - Build/review engine choice needs CLI ≥ 0.10.0 on the daemon — older daemons ignore the field and run claude.
Whatever builds, the trust gates' evidence verifier and the visual verify layer deliberately run your local claude — the verifier is never the producer.
Worktrees
Every run gets its own worktree so nothing collides. List and clean them up:
$ ptln wtA finished task now cleans up after itself, and what survives depends on how it ended:
| outcome | worktree | branch |
|---|---|---|
| PR opened | removed | kept until the PR merges |
merge policy manual | removed | kept — a local branch is the intended result |
| quarantined by a verify gate | kept | kept — with the reason recorded, because a human has to look |
| committed but the PR failed to open | kept | kept — the work is real and recoverable |
| nothing committed | removed | removed |
A worktree with uncommitted changes is never removed, whatever the outcome says.
For the backlog left by older runs:
$ ptln wt prune # dry run — shows what it would remove
ptln wt prune --yes # do itIt only touches worktrees crank made, never your own, and it never deletes a branch — the commits survive either way.
When two tasks touch the same code
Agents build in parallel, so two branches can change the same lines. partyline handles this in three places rather than leaving you to discover it at merge time.
Building against the current base
A task's worktree forks the base branch when the task starts. A forty-minute task is forty minutes stale by the time it finishes — and a task that ran while a teammate merged is stale by exactly the change that will conflict with it.
So before pushing, the worker rebases onto the current base and re-runs the verify gate on the result. This is what survives work partyline didn't plan: a teammate's PR, or a direct push to the base where no PR exists to compare against.
The collision warning
A merge conflict is symmetric; a scan for one is not. The earliest PR in a colliding set gets checked while it is still alone, records "clean", and would keep that record forever — and it is the one you would merge first, which is what breaks the others.
Conflicts are therefore mirrored across every branch in the set. If three PRs collide, all three say so, not two.
In flight
The In flight panel on the Dashboard is about the set rather than any one card. Its Waiting to merge section counts work that is built and has landed nowhere yet — the Review column, where most in-flight work actually sits — and says the two things no single card can: which items are about to collide (computed from file overlap, so it fires before anything is merged) and what order to land them in. It is built from partyline's own runs and the conflict records a daemon computed with git merge-tree, so it needs no git-host API and reads the same on Bitbucket or a private remote.
Repairing a branch
A conflicted branch offers two routes:
- Rebuild on the current base — re-runs the task against the other change. The agent reads the code as it is now and does the work properly in that context, yielding a branch with no conflict rather than a guessed merge. This is possible only because the task spec still exists, which no ordinary merge tool has.
- Rebase onto base — a worker fetches, rebases, and resolves conflict markers over several rounds.
Either way the result passes the verify gate before it is published. A worker resolving conflicts has read/edit tools and no shell — it cannot check that what it wrote still compiles — so publishing on its word would be the one route to a mergeable branch that skipped the gates everything else goes through.
The repair streams live on the tile it is repairing. It is minutes of an agent rewriting a branch; "it is working" and "it silently failed" should never look the same.
What happens to a finished task
That's the merge policy (set per run; the board defaults to pr):
- manual — leave a reviewable branch, push nothing. The default for local
ptln crank/ptln work. - pr — push the branch and open a pull request. With the review gate on, it opens as a draft — Accept on the board is what marks it ready.
- auto — open a PR and enable GitHub auto-merge, but only when the base branch gates on required status checks; otherwise leave the PR open.
Whatever the policy, a task only reaches it after clearing the trust gates — your tests plus an independent reviewer. A failure is quarantined for a human, never shipped.
Next: Trust gates · Projects · Context Threads.