# One workstream per task: git worktrees, tmux, and agent isolation > [!summary] TL;DR > One task, one branch, one worktree, one tmux session, one agent conversation. If any of those is shared between two tasks, the tasks eventually share a mistake. The `cf` zsh function (1,777 lines) implemented this for a year; the Go tool `af` replaces it with a state machine. Three weeks of session logs: 885 agent sessions, nearly all code work inside a task worktree, never on `main`. ## Key points - The rule: one task, one branch, one worktree, one tmux session, one agent conversation. Anything shared between two tasks means the two tasks eventually share a mistake. - Three weeks of session logs: 885 agent sessions, nearly all code work inside a task worktree, never on `main`. - `cf issue-42` does five things in order: fetches and resolves the base branch, checks out a fresh worktree at `~/Workspace/.worktrees/<repo>/issue-42`, derives a session ID as uuid5 over the repo and branch, creates a detached tmux session named after the branch, launches Claude Code with that session ID. - The uuid5 session ID is the part worth keeping: deterministic over `repo` and `branch`, so the same task always resumes the same conversation, even after a reboot. - The `cf` family: `cfr` resumes (falls back to `claude --continue` inside the worktree when the tmux session died with the machine), `cfd` tears down session, worktree and branch, `cfl` lists everything and flags orphans, `cf --from-pr 42` forks a worktree straight off a pull request, `cfgc` garbage-collects worktrees whose branches merged. - Hard cap of ten live sessions; the cap exists because it has been hit. - `cfgc` detects squash merges by fingerprinting diffs, because branch history alone can't tell you a squashed branch is safe to delete. - `af` defines a **workstream**: the triple of a worktree, a tmux session, and one or more agents (pi by default, `claude` or `codex` on demand). State lives in a `state.toml` under `~/.local/share/af/v1/sessions/<name>/`. - Parallel subagents each get a sibling branch and worktree named `<branch>--<slot>` (ADR-038); merge-back stays deliberately manual in v1. - One Go interface drives all three agents (ADR-043). - v1 status: 42 ADRs complete, one not-applicable, zero pending; remaining work is tagging v1.0.0. "Binaries are not published; this is a single-user tool." - The real ceiling is review bandwidth: ten concurrent workstreams fit in tmux, but not in one head. This week, 39% of messages were sent while at least one other session ran in parallel. ## The rule: one task, one branch, one worktree, one tmux session A fresh conversation is not worth much if it opens in a dirty working tree. Ask an agent to fix a bug while a half-staged refactor sits in the same tree, and twenty minutes later `git status` is archaeology: which change is the fix, which is the refactor, which is the agent "cleaning up" something it was never asked about. That class of accident was stopped by making it impossible, not by debugging it after the fact. Three weeks of session logs agree it stuck: 885 agent sessions, nearly all of the code work inside a task worktree, never on `main`. ## cf: the 1,777-line zsh function (claude-focus) The first implementation was a zsh function called `cf` (claude-focus). `cf issue-42` does five things in order: 1. fetches and resolves the base branch 2. checks out a fresh worktree at `~/Workspace/.worktrees/<repo>/issue-42` 3. derives a session ID as uuid5 over the repo and branch 4. creates a detached tmux session named after the branch 5. launches Claude Code with that session ID The uuid5 is the part that would be kept even if the rest were thrown away. A deterministic ID means the same task always resumes the same conversation, on purpose, even after a reboot, so there is never any scrolling a session picker trying to remember which of nine conversations was the one about the flaky test. Around `cf` grew a small family: `cfr` resumes (and falls back to `claude --continue` inside the worktree when the tmux session died with the machine), `cfd` tears down the session, worktree, and branch in one motion, `cfl` lists everything and flags orphans, `cf --from-pr 42` forks a worktree straight off a pull request, and `cfgc` garbage-collects worktrees whose branches merged. Unnamed tasks get generated names, so session history reads like a ship manifest: `eloquent-cohen`, `naughty-dubinsky`, `clever-chaum`. There is a hard cap of ten live sessions; the cap exists because it has been hit. Then the backends multiplied. Some tasks want the agent inside a microVM sandbox with the worktree mounted in. Some want the worktree local but the agent watched. Some want the whole session on a remote machine over SSH, where the build caches live. `cf` grew a flag for each. The source lives in private dotfiles and is not linked. ## Where bash ran out Every one of those features works. Each also carries its own failure shape: metadata that has to survive reboots in sidecar files, a reconstruction path for when the metadata is missing, health checks before resume, backoff when SSH drops. Roughly thirty lines per edge case, and the edge cases compound. The moment of clarity was `cfgc` learning to detect squash merges by fingerprinting diffs, because branch history alone can't tell you a squashed branch is safe to delete. When your shell function starts fingerprinting diffs, it is filing a formal request to become a real program. ## af: workstreams as a state machine The request was granted. [af](https://github.com/kakkoyun/af) is the Go successor, public as of this summer. The README's own pitch: > **af** manages isolated AI-agent workstreams across git worktrees, tmux sessions, sandboxes, and SSH remotes. Its [SPEC](https://github.com/kakkoyun/af/blob/main/docs/SPEC.md) names the unit `cf` never had a word for. A **workstream** is the triple of a worktree, a tmux session, and one or more agents ([pi](https://pi.dev) by default, `claude` or `codex` on demand). The lifecycle is a state machine instead of a pile of flags: ``` af create issue-42 → active af suspend issue-42 → suspended (tmux down, VM destroyed if any) af resume issue-42 → active again af done issue-42 → completed (or abandoned) ``` State lives in a `state.toml` under `~/.local/share/af/v1/sessions/<name>/`, so there is nothing to reconstruct from tmux environment variables and no sidecar files to lose. When a task needs parallel subagents, each gets a sibling branch and sibling worktree named `<branch>--<slot>`, which is [ADR-038](https://github.com/kakkoyun/af/blob/main/docs/adr/038-workstream-and-worktree-layout.md)'s answer to two agents fighting over one file tree: full git isolation, and merge-back stays deliberately manual in v1 so what comes back can be read. One Go interface drives all three agents ([ADR-043](https://github.com/kakkoyun/af/blob/main/docs/adr/043-agent-providers.md)). Honest status: the v1 ADRs are closed (42 complete, one not-applicable, zero pending) and the remaining work is tagging v1.0.0. "Binaries are not published; this is a single-user tool," says the README, and it is right. The migration is underway, not done: `cf` still lingers while `af` takes over task by task, and the primitive shell scripts watch their replacement being raised in a proper test suite. ## What transfers, and the real ceiling With any harness, or none, three things carry over: - Deterministic names buy resumability for free; anything derived from `repo/branch` means never asking "which session was that." - State belongs on disk, in a file you can `cat`, not in whatever your shell happened to export. - Teardown deserves as much design as create: `cfd` and `af done` are the reason the machine has worktrees instead of a worktree midden. The old ceiling on parallel work was typing speed. The new one is review bandwidth: ten concurrent workstreams fit in tmux, but they do not fit in one head. Claude Code's own usage report puts a number on it: this week, 39% of messages were sent while at least one other session was running in parallel. [WakaTime](https://wakatime.com) keeps the other half of the ledger: where the hours actually went, by repo and by language, agent sessions included via its Claude Code plugin. The feeling of "that took all afternoon" gets checked against a number too. Some weeks the real number is three; some weeks it is one. [[resources/documentation/Agents/Agents - Plan First, Fresh Sessions, Write It Down|Part 1]] argued for fresh sessions: every conversation starts clean, and context comes from disk. This part is about where the freshness comes from. ## FAQ ### What is a workstream in af? The triple of a worktree, a tmux session, and one or more agents (pi by default, `claude` or `codex` on demand). Its lifecycle is a state machine: `af create` → active, `af suspend` → suspended, `af resume` → active again, `af done` → completed or abandoned. ### Why give each agent task its own git worktree? If any of task, branch, worktree, tmux session or conversation is shared between two tasks, the tasks eventually share a mistake. `git status` becomes archaeology. Three weeks of logs: 885 agent sessions, nearly all code work in a task worktree, never on `main`. ### How does cf derive its Claude Code session ID? As uuid5 over the repo and branch. A deterministic ID means the same task always resumes the same conversation, even after a reboot, so there is no session-picker scrolling to find which conversation was the flaky-test one. ### What are the cf family commands? `cfr` resumes (falls back to `claude --continue` in the worktree when the tmux session died), `cfd` tears down session, worktree and branch, `cfl` lists everything and flags orphans, `cf --from-pr 42` forks a worktree off a pull request, `cfgc` garbage-collects worktrees whose branches merged (it detects squash merges by fingerprinting diffs). ### Where does af store session state? In a `state.toml` under `~/.local/share/af/v1/sessions/<name>/`. Nothing is reconstructed from tmux environment variables, and there are no sidecar files to lose. ### How does af isolate parallel subagents? Each subagent gets a sibling branch and sibling worktree named `<branch>--<slot>` (ADR-038): full git isolation, with merge-back deliberately manual in v1 so what comes back can be read. ### How many agent workstreams can run at once? `cf` has a hard cap of ten live sessions, and the cap exists because it has been hit. Ten concurrent workstreams fit in tmux, but they do not fit in one head: review bandwidth is the real ceiling, and this week 39% of messages were sent while another session ran in parallel. ### Is af published and ready to use? The repository is public, but "binaries are not published; this is a single-user tool." The v1 ADRs are closed (42 complete, one not-applicable, zero pending); the remaining work is tagging v1.0.0, and the migration from `cf` is still underway. ## Related - [[resources/documentation/Agents/Agents Workflow|Agents Workflow]] (series hub) - [[resources/documentation/Agents/Agents - Plan First, Fresh Sessions, Write It Down|Plan First, Fresh Sessions, Write It Down]] (part 1: fresh sessions, context from disk) - [[resources/documentation/Agents/Agents - One Memory, Many Harnesses|One Memory, Many Harnesses]] (part 3: one Go interface, ADR-043, driving all three agents) - [[resources/documentation/Claude Focus Workflow]] ## Sources - [af on GitHub](https://github.com/kakkoyun/af) - [af SPEC](https://github.com/kakkoyun/af/blob/main/docs/SPEC.md) - [ADR-038: workstream and worktree layout](https://github.com/kakkoyun/af/blob/main/docs/adr/038-workstream-and-worktree-layout.md) - [ADR-043: agent providers](https://github.com/kakkoyun/af/blob/main/docs/adr/043-agent-providers.md) - [pi](https://pi.dev) - [WakaTime](https://wakatime.com)