Spawning an Agentic Team in Claude Code
Today we will explore a multi-agent pattern: a lead Claude agent that doesn't write code. It decomposes the work and dispatches to specialist teammates. Each specialist gets its own context window, its own git worktree, and its own tmux pane. They talk to each other through a shared mailbox – not just up-and-down to the lead.
This post bootstraps the smallest useful version of that pattern: lead + backend + frontend, on a real Flask blog (this site). The task is intentionally tiny so you can watch the agents coordinate without ceremony – add reading time to posts.
The pattern
Three Claude processes, three worktrees, one mailbox dir:
~/code/chernenko.net/ # main checkout – lead lives here
~/code/chernenko.net-agents/
├─ backend/ # worktree, branch agents/backend
└─ frontend/ # worktree, branch agents/frontend
~/code/chernenko.net-mailbox/
├─ {lead,backend,frontend}.inbox.md
└─ msg # helper on PATH
The lead writes one dispatch per specialist with: contract, dependencies, acceptance criteria. Specialists work in parallel, ping each other directly when contracts need confirming, and report back when done.
The POC task
Add reading time to posts. BE adds a
reading_time_minfield (ceil(words / 200)) to the dicts returned byload_page()and items inlist_posts(). FE shows "~N min read" on the index list and at the top of each post page intemplates/layout.html.
Two files, two agents, one clear contract.
Bootstrap
Prereqs
command -v claude && command -v tmux && echo "tools ok"
ls ~/code/chernenko.net/app.py >/dev/null && echo "project ok"
free -h
That last free -h is not cosmetic. More on that below.
Worktrees
cd ~/code/chernenko.net
git fetch --all && git checkout main && git pull
mkdir -p ~/code/chernenko.net-agents ~/code/chernenko.net-mailbox
git worktree add ~/code/chernenko.net-agents/backend -b agents/backend
git worktree add ~/code/chernenko.net-agents/frontend -b agents/frontend
Each worktree is a full checkout on its own branch. Specialists can git status, commit, rebase without stepping on each other.
Mailbox
MB="$HOME/code/chernenko.net-mailbox"
touch "$MB"/{lead,backend,frontend}.inbox.md
cat > "$MB/msg" <<'EOF'
#!/usr/bin/env bash
# usage: msg <to> <from> <message…>
set -e
TO=$1; FROM=$2; shift 2
MB="$(dirname "$(readlink -f "$0")")"
TS=$(date '+%Y-%m-%d %H:%M:%S')
{ echo; echo "---"; echo "**[$TS] from:$FROM**"; echo; echo "$*"; } >> "$MB/${TO}.inbox.md"
echo "→ delivered to $TO"
EOF
chmod +x "$MB/msg"
The mailbox is just markdown files appended to. Grep-able, diff-able, and the transcript falls out for free. The msg helper goes on each pane's PATH.
Role files
Each agent's worktree gets a CLAUDE.md telling it who it is and how to use the mailbox:
write_role () {
local DIR=$1 ROLE_LC=$2
cat > "$DIR/CLAUDE.md" <<EOF
# Role: $ROLE_LC
Team: **lead** (decomposes, merges), **backend** (Flask app.py), **frontend** (templates/).
Your worktree is on \`agents/$ROLE_LC\`. Commit only here. Never touch the other agent's files – message them instead.
Inbox: \`$HOME/code/chernenko.net-mailbox/$ROLE_LC.inbox.md\`
Send: \`msg <to> $ROLE_LC "..."\` (peer-to-peer fine; cc lead on milestones or blockers).
Loop each turn:
1. \`tail -n 50\` your inbox
2. do the next item, commit small
3. reply to the asker; notify any dependent
4. when inbox empty and task done → \`msg lead $ROLE_LC "done"\`
EOF
}
write_role "$HOME/code/chernenko.net-agents/backend" backend
write_role "$HOME/code/chernenko.net-agents/frontend" frontend
The lead gets a separate file with the rule that drifts most often:
cat > "$HOME/code/chernenko.net/CLAUDE.lead.md" <<'EOF'
# Role: lead
You decompose work and dispatch. You do NOT write code. Team: backend, frontend.
For each agent, send one message with: the contract (function/field/template names), dependencies, acceptance criteria. Then watch your inbox and unblock conflicts. When both report done, merge backend → frontend.
EOF
tmux launcher
The first version of this script used numeric pane refs (team.0, team.1), which silently misalign if you have pane-base-index 1 set in .tmux.conf. The fix is {last} – always the just-created pane:
cat > ~/agents.sh <<'EOF'
#!/usr/bin/env bash
set -e
S=agents
MB="$HOME/code/chernenko.net-mailbox"
LEAD="$HOME/code/chernenko.net"
BE="$HOME/code/chernenko.net-agents/backend"
FE="$HOME/code/chernenko.net-agents/frontend"
tmux kill-session -t $S 2>/dev/null || true
tmux new-session -d -s $S -n team -c "$LEAD"
tmux send-keys -t $S:team.{last} "export PATH=\"$MB:\$PATH\"; echo '[lead] ready'" C-m
tmux split-window -h -t $S:team -c "$BE"
tmux send-keys -t $S:team.{last} "export PATH=\"$MB:\$PATH\"; echo '[backend] ready'" C-m
tmux split-window -v -t $S:team -c "$FE"
tmux send-keys -t $S:team.{last} "export PATH=\"$MB:\$PATH\"; echo '[frontend] ready'" C-m
tmux split-window -v -t $S:team -c "$MB"
tmux send-keys -t $S:team.{last} "tail -F $MB/lead.inbox.md $MB/backend.inbox.md $MB/frontend.inbox.md" C-m
tmux split-window -v -t $S:team
tmux send-keys -t $S:team.{last} "watch -n 2 'free -h; ps -eo pid,rss,comm --sort=-rss | head -10'" C-m
tmux select-layout -t $S:team tiled
tmux attach -t $S
EOF
chmod +x ~/agents.sh
~/agents.sh
Five panes: lead, BE, FE, mailbox tail, memory monitor.
Running it
Start claude in each pane one at a time (stagger – see next section). The lead gets:
Read CLAUDE.lead.md, then skim app.py and templates/layout.html.
GOAL: add reading time to posts.
- BE: add reading_time_min (ceil(words/200)) to dicts from load_page() and items in list_posts().
- FE: show "~N min read" on the index post list and at the top of each post page in templates/layout.html.
Dispatch BE and FE now via the mailbox with the precise contract. Do not write code yourself.
BE and FE get:
Read CLAUDE.md and idle. First action: tail -n 50 your inbox, then follow the Loop.
What you watch for in the tail pane:
- Lead writes one dispatch per agent. If it writes code, remind it.
- Specialists confirm contracts peer-to-peer before long work.
- If two agents ping-pong the same question twice, the human arbitrates.
When both report done, merge in order:
cd ~/code/chernenko.net && git checkout main
git merge --no-ff agents/backend
git merge --no-ff agents/frontend
git worktree remove ~/code/chernenko.net-agents/backend
git worktree remove ~/code/chernenko.net-agents/frontend
git branch -D agents/backend agents/frontend
tmux kill-session -t agents
Out of Memory lesson
I ran the first version on a 1 GB droplet with no swap. Three concurrent claude REPLs [Read-Eval-Print Loops], each holding its own context and occasionally shelling out to pip, ate all the RAM [Random Access Memory]. Linux without swap doesn't gracefully kill one process – it freezes the whole box.
Add swap before you run:
sudo fallocate -l 4G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile && sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
And keep the memory monitor pane visible. If RSS [Resident Set Size] climbs alarmingly, stagger startup – lead + BE first, hand off, then FE.
What I'd tune next
- More specialists. Add QA agent and docs once the lead's dispatch rhythm is reliable. The mailbox scales fine; the bottleneck is the human reading the tail.
- Acceptance gates. Require the lead to ask QA to verify before merging, not just trust "done" messages.
- Trim the lead's prompt. "Do not write code yourself" is the single most useful sentence in the lead role file – worth restating in every dispatch.
Small enough to fit in one tmux session, structured enough that the whole engagement reads back as a transcript afterward. For anything that crosses subsystems, the pattern pays for itself the first time you use it.