Blog

One repository, twenty checkouts

Running several agents at once is easy until two of them edit the same file. Codecast gives each one its own worktree, with the env files, dependencies and a port of its own, in one command.

the codecast team5 min read

The shared checkout problem

Three weeks ago on this blog, a Codex agent and a Claude Code agent coordinated a repair by messaging each other, because both were editing one working tree and one of them had to stay out of the other's files. That was the right thing to do in a shared checkout, and it is also a cost: two agents spending turns negotiating who may touch which file. The cheaper answer, when the work allows it, is to not share the checkout at all.

Git has had worktrees for years: extra checkouts of the same repository, each on its own branch, sharing one object store. What stops people using them for agents is everything around the checkout. The ignored files that hold secrets are not copied. Dependencies are not installed. The dev server wants port 3200, which the main checkout already holds. By the time an agent has fixed all that, it has spent its first ten minutes on plumbing. So codecast made the plumbing one command.

Twenty of them

Here is the codecast repository's own list this morning. Every row is a checkout an agent asked for by name, most of them for a feature branch, two for a task by its id:

cast ws ls
$ cast ws ls
NAME                      STATE       BRANCH                            PATH
deprecate-reconcilers     ready       codecast/deprecate-reconcilers    /Users/ashot/src/codecast/.codecast/worktrees/deprecate-reconcilers
browser-watch-pane        ready       codecast/browser-watch-pane       /Users/ashot/src/codecast/.codecast/worktrees/browser-watch-pane
deploy-repo-objects       destroying  codecast/deploy-repo-objects      /Users/ashot/src/codecast/.codecast/worktrees/deploy-repo-objects
linear-token-deploy       ready       codecast/linear-token-deploy      /Users/ashot/src/codecast/.codecast/worktrees/linear-token-deploy
safe-query                ready       codecast/safe-query               /Users/ashot/src/codecast/.codecast/worktrees/safe-query
browser-resident-driver   ready       codecast/browser-resident-driver  /Users/ashot/src/codecast/.codecast/worktrees/browser-resident-driver
ct-49675                  ready       codecast/ct-49675                 /Users/ashot/src/codecast/.codecast/worktrees/ct-49675
grok-fork                 broken      codecast/grok-fork                /Users/ashot/src/codecast/.codecast/worktrees/grok-fork
…  11 more rows, all ready  …

Nineteen tracked: seventeen ready, one being torn down, one broken. The broken one is the interesting row. A workspace carries a contract, and codecast checks it rather than assuming it. Ask about one and you get the state and every clause of the contract, verified:

cast ws status
$ cast ws status safe-query
safe-query
  state:   ready
  path:    /Users/ashot/src/codecast/.codecast/worktrees/safe-query
  branch:  codecast/safe-query
  ports:   web=3201
  updated: 2026-08-11T22:34:58.491Z
  contract: ok
    ✓ worktree-exists
    ✓ git-branch
    ✓ deps-installed
    ✓ env-vars
    ✓ port:web
    ✓ port-free:web

The worktree exists, the branch is right, dependencies are installed, the secret files are present, the port is assigned and nothing else is listening on it. When a clause fails, the row reads broken in the list, and cast ws heal reruns the setup to make it true again.

What a workspace promises

The contract comes from one file in the repository, which cast ws init generates by looking at the project and which the team then edits and commits. Ours is short:

.codecast/workspace.toml
$ cat .codecast/workspace.toml
[setup]
copy = [
  ".env",
  ".env.local",
  "packages/convex/.env.local",
  "packages/web/.env.local",
  "packages/cli/.env.local",
]
install = ["bun install"]

[ports.web]
base = 3201
range = 20

Three things: which ignored files to copy from the main checkout, what to run after the copy, and which ports to hand out. Each workspace gets an index, and its web port is the base plus twenty times that index, probed free before it is handed out. The workspace above is index zero, so its status says web=3201. Two agents can each run a dev server without reading each other's pages.

Acquire, work, destroy

For this post we asked for a workspace that did not exist, watched it come up, and took it down again. This is the whole lifecycle, as it ran:

cast ws acquire
$ cast ws acquire blog-demo
created: blog-demo
  path:    /Users/ashot/src/codecast/.codecast/worktrees/blog-demo
  branch:  codecast/blog-demo
  state:   ready
  ports:   web=3401
  port pool of 10 indices exhausted; extended the range to indices 10-19 and took index 10 (web=3401)

A worktree on a new branch, the secret files copied and the install run behind that one word ready, and a port. The last line is the honest part: the pool of ten port indices was already spent on the workspaces above, so the allocator extended it and this one became index ten, port 3401. The command prints the path because a program cannot change your shell's directory; the next line is always cd "$(cast ws path blog-demo)". An agent that already has a workspace by that name attaches to it instead of making a second one, so a session that restarts lands back where it was.

cast ws destroy
$ cast ws destroy blog-demo
destroyed: blog-demo

Teardown runs any teardown hooks, removes the worktree, and drops the state, so the port goes back to the pool and the name is free. The list above is as long as it is because agents are better at acquiring than destroying; the one marked destroying is a teardown in progress.

Where this fits

A workspace is a checkout, not a sandbox. The agent in it still runs on your machine, with your logins and your tools, and its session lands in your inbox like any other, with the checkout named in the session header so you can tell at a glance which branch a conversation is editing. Two agents in two workspaces cannot overwrite each other's files, and they still share everything else codecast gives a team: the inbox, search across both sessions, and messages between them when the work does need a word.

The rule we have settled on is simple. Work that can live on a branch gets a workspace. Work that must land in the main checkout, because the human is in it too, stays shared and the agents talk. Both are one command away, and the list tells you which is which.

Codecast is where your team sees, steers, and remembers every coding agent session — any agent, any machine.

All five terminal captures are genuine cast ws output from the codecast checkout on 2026-10-04. The list keeps eight of nineteen rows and marks the rest; the status, the config file, the acquire and the destroy are shown in full. The demo workspace was created and removed for this post and nothing else on the machine changed; on a machine carrying 183 registered worktrees and a full load of agents, the acquire took about twenty minutes and the destroy about ten, nearly all of it waiting on git. There is no screenshot: the feature lives in the terminal, and the sessions that use these workspaces belong to other work.