Mastering Parallel Coding Agents in Claude Code — A Copy-Paste Guide

rcmiskrcmisk
6 min read

Claude Code Agents

Want your main agent to keep coding while a sub-agent plans the next feature—and a master agent keeps everyone in sync? This post gives you a clean, from-scratch setup you can copy, run, and share.

You’ll end up with three Claude Code sessions running in parallel on the same project:

  • main-dev → implements code (locked to /src and /tests)

  • feature-planner → writes PRDs (locked to /docs/feature-plans/)

  • master-agent → orchestrates (reads/writes /ops, summarizes status)

The system uses guardrail hooks to enforce boundaries and a tiny renderer to stamp agent templates from your inputs.


TL;DR

  1. Create a folder and drop in the bootstrap script below.

  2. Run it to scaffold folders, hooks, templates.

  3. Render agents from inputs.

  4. Open three Claude Code sessions (main-dev, feature-planner, master-agent).

  5. Paste the prompts. Build in parallel. 🎯


Prerequisites

  • macOS or Linux shell

  • jq and python3 installed

    • macOS (Homebrew): brew install jq python

    • Ubuntu/Debian: sudo apt-get install -y jq python3


Step 1 — Create the project & add bootstrap.sh

In an empty directory:

mkdir my-agents-project
cd my-agents-project

Create bootstrap.sh and paste all of this:

#!/usr/bin/env bash
set -euo pipefail

# --- helpers ---
write() { mkdir -p "$(dirname "$1")"; cat > "$1"; }

# --- tree ---
mkdir -p .claude/{agents-templates,hooks} docs/feature-plans docs/release-notes ops/inputs scripts src tests

# --- policy (guardrails) ---
write ops/policy.json <<'JSON'
{
  "writeAllow": {
    "master-agent": ["ops/*"],
    "feature-planner": ["docs/feature-plans/*"],
    "main-dev": ["src/*","tests/*"]
  },
  "inboxNotifyAgents": ["feature-planner"],
  "inboxPath": "docs/feature-plans/INBOX.md",
  "eventsLogPath": "ops/events.log"
}
JSON

# --- hooks (require jq) ---
write .claude/hooks/pretooluse.sh <<'BASH'
#!/usr/bin/env bash
set -euo pipefail
TOOL="${CLAUDE_TOOL_NAME:-}"; AGENT="${CLAUDE_AGENT_NAME:-}"
ARGS="${CLAUDE_TOOL_ARGS_JSON:-}"; POLICY="ops/policy.json"
PATH_TARGET="$(printf '%s' "$ARGS" | jq -r '.path // empty' 2>/dev/null || true)"

if [[ "$TOOL" =~ ^(Write|Edit|MultiEdit)$ ]]; then
  MAP=$(jq -r --arg a "$AGENT" '.writeAllow[$a][]?' "$POLICY" 2>/dev/null || true)
  if [ -z "$MAP" ]; then echo "No write policy for $AGENT"; exit 1; fi
  ALLOWED=false
  while IFS= read -r pat; do
    case "$PATH_TARGET" in $pat) ALLOWED=true; break;; esac
  done <<< "$MAP"
  if [ "$ALLOWED" != true ]; then
    echo "Write blocked by policy. Agent=$AGENT Path=$PATH_TARGET"
    exit 1
  fi
fi
BASH

write .claude/hooks/posttooluse.sh <<'BASH'
#!/usr/bin/env bash
set -euo pipefail
TOOL="${CLAUDE_TOOL_NAME:-}"; AGENT="${CLAUDE_AGENT_NAME:-}"
ARGS="${CLAUDE_TOOL_ARGS_JSON:-}"; POLICY="ops/policy.json"
EVENTS="$(jq -r '.eventsLogPath' "$POLICY")"
INBOX="$(jq -r '.inboxPath' "$POLICY")"
NEEDS_INBOX=$(jq -r --arg a "$AGENT" '.inboxNotifyAgents | index($a) | if .==null then "no" else "yes" end' "$POLICY")

if [ "$TOOL" = "Write" ]; then
  FILE="$(printf '%s' "$ARGS" | jq -r '.path // empty' 2>/dev/null || true)"
  if [ -n "$FILE" ]; then
    TS="$(date -u +'%Y-%m-%d %H:%M:%SZ')"
    echo "[$TS] $AGENT wrote: $FILE" >> "$EVENTS"
    if [ "$NEEDS_INBOX" = "yes" ]; then
      echo "- [$TS] $AGENT updated: $FILE" >> "$INBOX"
    fi
  fi
fi
BASH

chmod +x .claude/hooks/*.sh

# --- agent templates ---
write .claude/agents-templates/master-agent.tmpl.md <<'MD'
---
name: master-agent
description: Orchestrates sub-agents. Writes only to /ops per policy.
tools: Read, Write, Grep, Glob
---
Read /ops/agent-state.json, last 50 lines of /ops/events.log, and last 20 lines of docs/feature-plans/INBOX.md.
Update statuses when there is evidence (new PRD, new branch, QA report). Merge /ops/agent-state.json and append a Standup line to /ops/events.log.
Return a 7-bullet executive summary with links.
MD

write .claude/agents-templates/feature-planner.tmpl.md <<'MD'
---
name: feature-planner
description: Plans features. Writes PRDs only to /docs/feature-plans/.
tools: Read, Write, Grep, Glob
---
Plan "{{FEATURE_NAME}}" for "{{PROJECT_NAME}}".
1) Ask 7 brief scoping questions.
2) Copy /docs/feature-plans/PRD_TEMPLATE.md to /docs/feature-plans/PLN_{{FEATURE_SLUG}}_{{DATE}}.md and fill sections.
3) Append a timestamped entry to Changelog on each update.
4) Return the PRD path + 5 bullets (highlights/blockers/next).
Write only under /docs/feature-plans/.
MD

write .claude/agents-templates/main-dev.tmpl.md <<'MD'
---
name: main-dev
description: Implements current sprint items in /src and /tests only. Reads INBOX + PRDs for acceptance criteria.
tools: Read, Write, Grep, Glob
---
Before coding: read docs/feature-plans/INBOX.md (last 15 lines) and latest PRD for "{{FEATURE_NAME}}".
Summarize AC that impact code. Implement increment in /src (create/update tests). Output a 5-item commit checklist.
MD

# --- renderer ---
write scripts/render_templates.py <<'PY'
#!/usr/bin/env python3
import os, re, sys, json, glob
ROOT=os.path.abspath(os.path.join(os.path.dirname(__file__),".."))
def render(t,c): return re.sub(r"\{\{([A-Z0-9_]+)\}\}", lambda m:str(c.get(m.group(1),m.group(0))), t)

if len(sys.argv)<2:
    print("usage: render_templates.py ops/inputs/current.json")
    sys.exit(1)

with open(sys.argv[1]) as f:
    ctx=json.load(f)

os.makedirs(os.path.join(ROOT,".claude","agents"),exist_ok=True)

for p in glob.glob(os.path.join(ROOT,".claude","agents-templates","*.tmpl.md")):
    out=render(open(p).read(),ctx)
    o=os.path.join(ROOT,".claude","agents",os.path.basename(p).replace(".tmpl",""))
    open(o,"w").write(out)
    print("Rendered agent:",os.path.basename(o))

# render PRD template placeholders
prd=os.path.join(ROOT,"docs","feature-plans","PRD_TEMPLATE.md")
if os.path.exists(prd):
    open(prd,"w").write(render(open(prd).read(),ctx))
    print("Rendered: docs/feature-plans/PRD_TEMPLATE.md")

print("Done.")
PY

chmod +x scripts/render_templates.py

# --- seeds/inputs/state ---
write ops/inputs/current.json <<'JSON'
{
  "PROJECT_NAME":"SaaS Boilerplate",
  "FEATURE_NAME":"SaaS Boilerplate MVP",
  "FEATURE_SLUG":"saas-boilerplate-mvp",
  "DATE":"20250823",
  "DATE_ISO":"2025-08-23T00:00:00Z"
}
JSON

write ops/agent-state.json <<'JSON'
{ "tasks": [], "updatedAt": "1970-01-01T00:00:00Z" }
JSON

write ops/events.log <<'LOG'
[bootstrap] initialized
LOG

# --- docs ---
write docs/feature-plans/INBOX.md <<'MD'
# Feature Plans Inbox
Latest planning updates appear here.
MD

write docs/feature-plans/PRD_TEMPLATE.md <<'MD'
# PRD: {{FEATURE_NAME}} ({{PROJECT_NAME}})
## Context
## Goals
## Non-Goals
## Requirements
## Open Questions (Q&A)
## User Stories
## Acceptance Criteria
## Risks
## Metrics
## Rollout Plan
## Next Steps
## Changelog
- {{DATE_ISO}} – Initial draft created.
MD

echo "✅ Bootstrap complete."
echo "Next:"
echo "  1) Ensure jq + python3 are installed (macOS: brew install jq python)"
echo "  2) chmod +x .claude/hooks/*.sh"
echo "  3) ./scripts/render_templates.py ops/inputs/current.json"
echo "  4) Open Claude Code sessions: master-agent, feature-planner, main-dev"

Make it executable and run it:

chmod +x bootstrap.sh
./bootstrap.sh

You should see the ✅ “Bootstrap complete” lines.


Step 2 — Render the agents from inputs

This stamps the templates with your inputs:

# If needed, install prerequisites
# macOS: brew install jq python
# Linux: sudo apt-get install -y jq python3

chmod +x .claude/hooks/*.sh
./scripts/render_templates.py ops/inputs/current.json

This generates:

.claude/agents/master-agent.md
.claude/agents/feature-planner.md
.claude/agents/main-dev.md
docs/feature-plans/PRD_TEMPLATE.md (placeholders filled)

Step 3 — Open 3 parallel sessions in Claude Code

Open the same project in Claude Code, but create three chats (or windows) and select a different agent in each via /agents.

Session A — main-dev (coding)

Prompt:

Before coding, read /docs/feature-plans/INBOX.md (last 15 lines) and the latest PRD for “SaaS Boilerplate MVP”.
Summarize acceptance criteria that impact code and output a 5-item commit checklist.
Wait for my confirmation before touching /src.

Session B — feature-planner (planning)

Prompt:

Plan “SaaS Boilerplate MVP”.

1) Ask me 7 brief scoping questions.
2) Copy /docs/feature-plans/PRD_TEMPLATE.md to:
/docs/feature-plans/PLN_saas-boilerplate-mvp_{{today}}.md
3) Fill all sections (Context, Goals/Non-Goals, Requirements, Open Questions (Q&A),
   User Stories, Acceptance Criteria, Risks, Metrics, Rollout Plan, Next Steps, Changelog).
4) Append a timestamped entry to Changelog.
5) Return the PRD path and 5 bullets (highlights, blockers, next steps).
6) Write only under /docs/feature-plans/.

When the planner writes, the post-hook will append a line to docs/feature-plans/INBOX.md automatically.

Session M — master-agent (orchestrator)

Prompt:

Read:
- /ops/agent-state.json
- last 50 lines of /ops/events.log
- last 20 lines of /docs/feature-plans/INBOX.md

Then:
1) Update task statuses if there’s new evidence (new PRD, new branch, QA report).
2) Detect stale tasks (>24h); append "Nudge" to /ops/events.log for any.
3) Merge /ops/agent-state.json.
4) Append a “Standup” summary line to /ops/events.log.

Return a 7-bullet executive summary with links (PRD path, branch, QA report).

Step 4 — Sanity Checks

  • Planner writes docs/feature-plans/PLN_saas-boilerplate-mvp_<DATE>.md.

  • INBOX gets a new line, e.g.
    - [2025-08-23T..Z] feature-planner updated: docs/feature-plans/PLN_saas-boilerplate-mvp_20250823.md

  • Master reads INBOX + /ops/events.log, updates /ops/agent-state.json, appends a Standup.

  • Main-dev summarizes acceptance criteria and gives a 5-item commit checklist (and will only write to /src and /tests).


Troubleshooting

  • Agents don’t appear in /agents menu
    Run the renderer again:
    ./scripts/render_templates.py ops/inputs/current.json

  • Writes blocked unexpectedly
    That’s the guardrail working.

    • feature-planner → only /docs/feature-plans/

    • main-dev → only /src and /tests
      Ensure hooks are executable:
      chmod +x .claude/hooks/*.sh

  • INBOX doesn’t update
    Check jq is installed and posttooluse.sh is executable.
    Confirm ops/policy.json includes "inboxNotifyAgents": ["feature-planner"].


Reusing for Any Project

Edit ops/inputs/current.json:

{
  "PROJECT_NAME": "Your Project",
  "FEATURE_NAME": "Your Feature",
  "FEATURE_SLUG": "your-feature-slug",
  "DATE": "20250823",
  "DATE_ISO": "2025-08-23T00:00:00Z"
}

Re-render:

./scripts/render_templates.py ops/inputs/current.json

You’ve now retargeted the same agents to a brand-new idea with zero code changes.


Why This Pattern Works

  • Parallelism: planning continues while coding ships.

  • Safety: hooks enforce hard boundaries (no accidental edits).

  • Awareness: INBOX.md and /ops/events.log are lightweight “message bus” files agents share.

  • Portability: swap inputs and reuse the same scaffolding for any project.

0
Subscribe to my newsletter

Read articles from rcmisk directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

rcmisk
rcmisk

Lover of coding, software development/engineering, indie hackers podcast/community, start-ups, music, guitar, technology, fitness, running, biking, learning new things, travel, the beach, and hiking/mountains. As a kid I had too many interests. I grew up playing soccer from an early age and played through college! Sports and being a part of a team was always part of my DNA. Not only did I value sports and competition but I loved music, art, drawing, animation, film, computers, math, and learning. Once I hit college, the decision to choose my life path was paralyzing, and ultimately led me down many different paths. I explored economics, finance, psychology, philosophy, statistics, communications, and marketing. I graduated with a finance degree and thought the data science, statistics, and the trends and patterns would be a fun career, however my first entry level job in the industry discouraged me to continue in the industry and to explore other paths. I always had an itch to build and start something on my own or with family. Growing up I started a lawn mowing business, shoveling business, lemonade stands, and small Wordpress websites. I loved the creativity of coming up with ideas on how to help people and make money at the same time. I realized I loved technology, and seeing what could be created and started with technology really urged me to start down the path of learning how to code. My brother and I had an idea for a college social network (similar to Facebook), geared solely towards education and only for students at your college. We wanted to give students the ability to meet people on campus, finding work, organize course material, share notes and materials, find extracurricular activities, sell textbooks and furniture. I took it upon myself to learn how to build something like that. Basically taking an idea and making it happen. I learned about software development, coding languages, web frameworks, startups, marketing all on my own. I took online free courses, watched videos and tutorials about Django, Python, Javascript, HTML, and databases. I absolutely loved everything about the process. Seeing my work come to life and seeing people use what I created. It satisfied everything that I enjoyed growing up. The creativity, the design, artwork, coming up with a business, learning new things at my own pace, however I learned best, and working with my brother. I did all this while working full-time at a financial institution during my nights and weekends. We finally launched StudentGrounds, however after a year and 200 user signups later it slowly died down. This experience of taking an idea and learning everything needed to make it a reality basically propelled my interest in learning how to code and do that full time. I learned all about computer science, taking a certificate course at night at a local university. I started another project idea on the side for an event management application for my father's youth soccer tournament, and started applying to every technology company I could think of. I ultimately got my first software engineer job at a small start up in Boston as an apprentice/intern and learned on the job before getting my first full-time software engineer position at a large Boston e-commerce company. My goal there was to learn as much as I could from season professionals, and learning how the corporate world works in terms of software development. My ultimate goal is to create something on my own doing something I love, as well as enjoy life, and give back to others through education. Right now I am a full-time Software Engineer with 6 years in the marketing tech space, trying to finish a SaaS boilerplate so that I can spin up any web application for any idea at the click of a button, which will then set me up for my next idea, IdeaVerify, an automated way to verify/validate you're SaaS application idea before actually starting to code and wasting many hours and years developing something that no one would use. This blog is about my journey navigating the software engineering world, without a CS degree, building in public, keeping record of what I learned, sharing my learnings and at the same time giving back to others, teaching them how to code and giving helpful hints and insights. I am also using this blog to showcase other sides of me such as art, music, writing, creative endeavors, opinions, tutorials, travel, things I recently learned and anything else that interests me. Hope you enjoy!