Eugene Chernenko

AI, Engineering Management, Distributed Systems, SRE, Productivity

Spawning an Agentic Team in Claude Code

2026-05-18

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_min field (ceil(words / 200)) to the dicts returned by load_page() and items in list_posts(). FE shows "~N min read" on the index list and at the top of each post page in templates/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:

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

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.