It's 3pm on a Thursday. You've launched 5 Claude Code agents to build different parts of your full-stack app simultaneously. Within 30 seconds, chaos:
Agent 1 (Frontend): "Claiming port 3100 for the React app..."
Agent 2 (API): "Claiming port 3100 for the backend..."
Agent 3 (Database): "Setting up migrations..."
Agent 4 (Worker): "Starting the job processor..."
Agent 5 (Auth): "Implementing OAuth, need session coordination!"
Without coordination, agents step on each other's files, fight over ports, duplicate work, and collide on dependencies. Port Daddy turns this chaos into a symphony.
The Problem: Multi-Agent Collision
Let's say you're building a payments system. You launch multiple agents:
- Frontend Agent -- Building the checkout UI
- Backend Agent -- Building the payment API
- Database Agent -- Creating the schema and migrations
- Testing Agent -- Writing integration tests (needs backend running first)
- Docs Agent -- Documenting the API (needs final backend shape)
Without coordination, you'd see:
- Both frontend and backend agents try to claim port 3100 (race condition)
- Database agent migrates, frontend agent expects old schema
- Testing agent starts before backend is healthy
- Multiple agents editing
src/types.tssimultaneously (merge conflict) - Nobody knows what anyone else is doing
Port Daddy solves all of this with three primitives:
- Sessions & Notes -- Structured coordination
- File Claims -- Conflict detection on files
- Pub/Sub + Locks -- Synchronization and exclusive access
Sessions: The Coordination Journal
A session is like a bouncer at a club: "I'm working on this. Here are the files I've claimed. Here's what I've discovered so far."
Agent 1: Frontend
# Start a session with file claims
$ pd session start "Building checkout UI" \
--files "src/components/checkout/*" \
"src/pages/checkout.tsx" \
"src/hooks/usePayment.ts"
# Make notes of progress
$ pd note "Installed Stripe React library"
$ pd note "Built CheckoutForm component"
$ pd note "Blocked waiting on payment API types"
Agent 2: Backend
# Start a session
$ pd session start "Building payment API" \
--files "src/api/payments/*" \
"src/types/Payment.ts" \
"src/middleware/auth.ts"
# Make notes as you work
$ pd note "Created POST /api/payments endpoint" --type commit
$ pd note "Integrated Stripe API client"
$ pd note "TypeScript types ready at src/types/Payment.ts"
Then, when you ask the Testing Agent what's ready:
# View all notes across all agents
$ pd notes --limit 20
[Agent 1] Built CheckoutForm component
[Agent 1] Blocked waiting on payment API types
[Agent 2] TypeScript types ready at src/types/Payment.ts
[Agent 2] Created POST /api/payments endpoint
[Agent 3] Created payments table with webhook_id tracking
[Agent 3] Ready for integration tests
Now Agent 4 (Testing) knows exactly what's ready and can write tests against the actual API.
File Claims: Preventing Collisions
When you claim files, Port Daddy warns if another session already claimed them:
# Agent 5 tries to claim a file Agent 2 is working on
$ pd session start "OAuth integration" \
--files "src/middleware/auth.ts"
Warning: src/middleware/auth.ts claimed by session-ab3 (Backend Agent)
Use --force to claim anyway
This is advisory, not enforced. You can force-claim if you need to, but the warning lets you say "wait, should Backend Agent and I coordinate here?" and avoid a merge conflict later.
Pub/Sub: Real-Time Signaling
Sometimes agents need to wait for each other. Use pub/sub for real-time events:
# Backend Agent: "I'm ready, other agents listening?"
$ pd pub payments:api '{"status":"healthy","port":3101}'
# Testing Agent: "Waiting for backend..."
$ pd sub payments:*
{"status":"healthy","port":3101}
In code (JavaScript SDK):
import { PortDaddy } from 'port-daddy/client';
const pd = new PortDaddy();
// Backend finishes setup
await pd.publish('payments:api', {
status: 'healthy',
port: 3101,
typesUrl: 'http://localhost:3101/api/types.json'
});
// Testing agent waits for backend signal
const sub = pd.subscribe('payments:*');
sub.on('message', async (msg) => {
if (msg.status === 'healthy') {
console.log('Backend ready, starting tests');
await runTests(msg.port);
}
});
Distributed Locks: Exclusive Access
When only one agent can run something (migrations, seeding), use locks:
# Database Agent claims exclusive lock for migrations
$ pd lock db-migrations
$ npx prisma migrate dev
$ pd unlock db-migrations
# Testing Agent checks if it can run (non-blocking)
$ pd lock db-migrations || echo "Migrations already running, skipping"
In code:
// Exclusive access pattern
await pd.withLock('db-migrations', async () => {
await runMigrations();
console.log('Migrations complete');
});
// Non-blocking pattern
const lockAcquired = await pd.lock('db-migrations', { blocking: false });
if (!lockAcquired) {
console.log('Migrations already running, skipping');
} else {
try {
await runMigrations();
} finally {
await pd.unlock('db-migrations');
}
}
Real Scenario: Building a Payment System in Parallel
Here's how 5 agents coordinate to build a complete payments feature:
Chronological Flow
10:00am -- Agent 1 (Database)
$ pd session start "Payment system schema" \
--files "supabase/migrations/*"
$ pd note "Creating payments table, webhook_requests, refunds"
10:02am -- Agent 1 (Database)
$ pd note "Schema ready, importing src/types/Payment.ts"
$ pd pub payments:schema '{"version":1,"tables":["payments","refunds"]}'
$ pd lock db-migrations
# (runs actual migration)
$ pd unlock db-migrations
10:05am -- Agent 2 (Backend API)
$ pd session start "Stripe API integration" \
--files "src/api/payments/*" "src/types/Payment.ts"
# Waits for schema signal
$ pd sub payments:schema
# Receives: version 1, tables ready
$ pd note "Implemented POST /api/payments (charges)"
$ pd note "Ready for integration tests"
$ pd pub payments:api '{"status":"ready","port":3101}'
10:12am -- Agent 4 (Frontend)
$ pd session start "Checkout UI" \
--files "src/components/Checkout/*"
# Waits for API and queries
$ pd sub payments:api,payments:queries
# Receives ready signals
$ PORT=$(pd claim payments:frontend -q)
$ pd note "Integrated with payment API on port 3101"
10:15am -- Agent 5 (Integration Tests)
$ pd session start "Payment integration tests" \
--files "tests/payments/*"
# Waits for all components
$ pd sub payments:*
# Receives ready signals from schema, api, queries, frontend
$ pd note "All tests passing"
$ pd pub payments:complete '{"status":"done"}'
What Happened
- 5 agents worked in parallel (not sequentially)
- Each knew exactly what others were doing via notes
- Database and API coordinated via locks and pub/sub
- Frontend waited for backend readiness before even starting
- Testing waited for everything to be ready
- Total time: 18 minutes instead of 90 minutes
Agent Registration and Heartbeats
For better coordination, register agents with semantic identity:
# Agent registers with project+stack+context
$ pd agent register \
--agent frontend-builder \
--identity payments:web \
--purpose "Building checkout UI" \
--type ai
# Agent sends periodic heartbeats
$ pd agent heartbeat --agent frontend-builder
Check which agents are alive:
$ pd agents
Active frontend-builder (payments:web, 30s ago)
Active backend-builder (payments:api, 15s ago)
Dead database-builder (payments:db, 8m ago) [stale]
When an agent dies before finishing:
$ pd salvage --project payments
# Shows all dead agents in payments:* with their session notes
# Other agents can claim their work and continue
JavaScript SDK Full Pattern
import { PortDaddy } from 'port-daddy/client';
const pd = new PortDaddy({ agentId: 'backend-builder' });
// Register as an agent
await pd.registerAgent({
name: 'Backend Builder',
identity: 'myapp:api',
purpose: 'Building REST API',
type: 'ai',
});
// Start a session
const session = await pd.startSession({
purpose: 'Building payment endpoints',
files: ['src/api/payments/*', 'src/types/Payment.ts'],
});
// Make notes as you work
await pd.note('Started payment API implementation');
// Wait for database schema to be ready
const schemaSub = pd.subscribe('myapp:db');
schemaSub.on('message', async (msg) => {
if (msg.status === 'schema-ready') {
await pd.note('Schema ready, importing types');
}
});
// Claim exclusive access for critical operation
await pd.withLock('payment-config', async () => {
await initializeStripe();
});
// Signal readiness
await pd.publish('myapp:api', {
status: 'healthy',
port: 3101,
});
// When done, end the session
await pd.endSession('Payment API complete');
Common Patterns
Pattern 1: Blocking Chain
Agent B waits for Agent A to signal completion:
# Agent A
$ pd pub build:complete '{"status":"ready"}'
# Agent B
$ pd sub build:complete
# (waits for signal before proceeding)
Pattern 2: Exclusive Access
Multiple agents need the database, only one touches migrations at a time:
// Each agent that touches the DB does this
await pd.withLock('db-schema', async () => {
// Only one agent runs this at a time
await runMigrations();
});
Pattern 3: Status Board
// Each agent publishes status on a schedule
setInterval(() => {
pd.publish('team:status', {
agent: 'frontend-builder',
progress: 75,
blockedOn: 'api-types',
eta: '5m',
});
}, 10000);
// Monitoring tool subscribes
const status = pd.subscribe('team:*');
status.on('message', (msg) => {
updateProgressBoard(msg);
});
Preventing Agent Deadlock
Two agents waiting on each other forever? This breaks coordination:
// Don't do this -- both will hang forever
// Agent A waits for Agent B
pd.sub('agentB:ready')
// Agent B waits for Agent A
pd.sub('agentA:ready')
Instead, define explicit ordering and use timeouts:
// Define explicit ordering:
// Database -> Backend -> Frontend -> Testing
// Use timeouts
const result = await Promise.race([
pd.waitForSignal('backend:ready'),
new Promise((_, reject) =>
setTimeout(() => reject('timeout'), 30000)
),
]);
What's Next
You've learned how to coordinate multiple agents. Now explore:
- Tunneling -- Share agent-built features with stakeholders
- Monorepo Mastery -- Scaling to 50 services
- Debugging -- When coordination goes wrong
The key insight: The bottleneck was never intelligence. It was coordination.
One brilliant agent is powerful. A swarm of coordinated agents is exponentially more powerful.