How PAI Handles Parallel Loop Execution with Configurable Agents
Personal AI Infrastructure (PAI) executes parallel loop iterations by spawning up to 16 configurable claude subprocesses via the -a flag, partitioning PRD criteria across agents using domain-aware load balancing, and aggregating results back to the parent process for real-time state synchronization.
Personal AI Infrastructure (PAI) by Daniel Miessler introduces a sophisticated parallel loop execution with configurable agents capability that transforms how autonomous coding loops handle multiple failing criteria. When running in loop mode, the system can distribute work across multiple agent subprocesses instead of processing criteria sequentially, dramatically reducing iteration time for complex PRD verification tasks.
Activating Parallel Execution via CLI Arguments
Parallel execution is triggered through the command-line interface defined in Releases/v3.0/.claude/skills/PAI/Tools/algorithm.ts. The algorithm accepts an -a or --agents flag that specifies the number of parallel workers to spawn.
// Lines 36-49 and 140-164 in algorithm.ts
const result = parseArgs({
options: {
agents: { type: 'string', short: 'a', default: '1' },
// ... other options
},
}).parseSync();
const agentCount = Math.min(Math.max(parseInt(result.agents, 10), 1), 16);
The validation logic enforces a hard cap of 16 agents and ensures at least one agent is always active. When agentCount exceeds 1 and multiple criteria are failing, the system switches from the single-agent workflow to the parallel partition strategy.
Loop State Management for Configurable Agents
The LoopAlgorithmState interface in algorithm.ts tracks parallel execution metadata. When createLoopState() initializes a new loop session, it stores the requested agent count in the parallelAgents field.
// Lines 430-440 in algorithm.ts
interface LoopAlgorithmState {
parallelAgents?: number;
agents: Array<{
name: string;
agentType: string;
status: 'idle' | 'working' | 'complete';
criteriaIds: string[];
}>;
// ... other fields
}
function createLoopState(agentCount: number) {
return {
parallelAgents: agentCount,
agents: Array.from({ length: agentCount }, (_, i) => ({
name: `Agent-${i + 1}`,
agentType: 'claude-worker',
status: 'idle',
criteriaIds: []
})),
// ...
};
}
This state object is persisted to ~/.claude/MEMORY/STATE/algorithms/<session-uuid>.json after each iteration, enabling the web dashboard to display real-time agent assignments and progress.
Domain-Aware Criteria Partitioning
The partitionCriteria() function implements intelligent load balancing by grouping related criteria before distributing them across agents. This prevents context switching overhead when agents work on unrelated domains.
// Context around lines 960-967 in algorithm.ts
function partitionCriteria(criteria: Criterion[], agentCount: number) {
// Group by domain prefix (e.g., ISC-TIER-1, ISC-SEC-4)
const domainGroups = groupBy(criteria, c => getDomain(c.id));
// Greedy load balancing: assign largest groups to least loaded agents
const sortedDomains = Object.entries(domainGroups).sort((a, b) => b[1].length - a[1].length);
const effectiveAgentCount = Math.min(agentCount, sortedDomains.length);
const assignments: Criterion[][] = Array.from({ length: effectiveAgentCount }, () => []);
for (const [domain, items] of sortedDomains) {
const shortest = assignments.reduce((min, curr, i) =>
curr.length < assignments[min].length ? i : min, 0);
assignments[shortest].push(...items);
}
return assignments.filter(a => a.length > 0);
}
The algorithm caps the effective agent count at the number of domain groups. If only three domains are failing but eight agents are requested, only three agents spawn to avoid idle workers.
Worker Prompt Generation and Isolation
Each parallel agent receives a constrained worker prompt generated by buildWorkerPrompt() that isolates the agent to a single criterion. This prevents workers from interfering with the broader algorithm state.
// Lines 95-124 in algorithm.ts
function buildWorkerPrompt(prdPath: string, criterion: Criterion, iteration: number, agentIndex: number) {
return `
You are a loop worker — a focused executor. Your ONLY job is to make ONE criterion pass.
YOUR CRITERION:
${criterion.id}: ${criterion.description}
PRD: ${prdPath}
Iteration: ${iteration} | Agent: ${agentIndex}
CONTEXT (from PRD):
${extractContextSection(prdPath)}
RULES — READ CAREFULLY:
- You are a WORKER, not the Algorithm. Do NOT run the Algorithm format.
- Do NOT create ISC criteria (TaskCreate). The criteria already exist.
- Do NOT execute voice curls (curl to localhost:8888).
- Do NOT write to the PRD file at all. Report results via stdout only.
- When finished, output exactly: RESULT: ${criterion.id} PASS
`;
}
Workers are explicitly forbidden from writing to the PRD or creating new criteria, ensuring the parent process maintains sole control over state mutations.
Spawning Agents and Collecting Results
The runParallelIteration() function orchestrates the actual parallel execution using Bun.spawn() to launch isolated claude processes. It then parses stdout to determine which criteria passed.
// Lines 78-99 in algorithm.ts (conceptual structure)
async function runParallelIteration(prdPath: string, assignments: Criterion[][], iteration: number) {
const agents = assignments.map((criteria, index) => {
const prompt = buildWorkerPrompt(prdPath, criteria[0], iteration, index + 1);
return Bun.spawn(['claude', '--prompt', prompt], {
stdout: 'pipe',
stderr: 'pipe',
});
});
const results = await Promise.all(agents.map(async (proc, i) => {
const stdout = await new Response(proc.stdout).text();
const stderr = await new Response(proc.stderr).text();
return { assignment: assignments[i], stdout, stderr };
}));
// Parse results and update PRD
const passedIds: string[] = [];
for (const { assignment, stdout } of results) {
const cId = assignment[0].id;
if (stdout.includes(`RESULT: ${cId} PASS`) || stdout.includes(`${cId} PASS`)) {
passedIds.push(cId);
}
}
return passedIds;
}
After collecting results, the parent process updates the PRD checkboxes and rewrites the front matter with the latest verification summary.
State Persistence and Dashboard Synchronization
Parallel execution metadata is persisted to disk via syncCriteriaToState() and the algorithm state management layer defined in Releases/v3.0/.claude/hooks/lib/algorithm-state.ts.
The state file located at ~/.claude/MEMORY/STATE/algorithms/<session-uuid>.json contains:
parallelAgents: The configured number of concurrent workersagents: An array tracking each agent'sname,status,criteriaIds, and timing dataloopHistory: Per-iteration metrics including pass counts and agent utilization rates
This persistence enables the PAI web dashboard to render real-time progress bars, agent assignment visualizations, and historical performance analytics across parallel loop sessions.
Summary
PAI implements parallel loop execution with configurable agents through a sophisticated orchestration layer that balances load while maintaining strict isolation:
- CLI Configuration: The
-aor--agentsflag (validated 1-16) activates parallel mode inalgorithm.ts - Intelligent Partitioning: The
partitionCriteria()function groups related criteria by domain prefix and applies greedy load balancing to prevent context switching overhead - Process Isolation: Each agent runs as a separate
claudesubprocess spawned viaBun.spawn(), receiving a constrained worker prompt that restricts it to a single criterion - Result Aggregation: The parent process parses
RESULT: <criterion-id> PASSstrings from stdout and updates the PRD checkboxes and algorithm state JSON - State Persistence: Execution metadata is written to
~/.claude/MEMORY/STATE/algorithms/<session-uuid>.jsonfor dashboard visualization
Frequently Asked Questions
What is the maximum number of parallel agents PAI supports?
PAI enforces a hard limit of 16 parallel agents through CLI validation logic in algorithm.ts (lines 36-49). This cap prevents system resource exhaustion while still allowing significant parallelization for complex PRD verification tasks. If you specify a number higher than 16, the system automatically clamps it to the maximum.
How does PAI distribute criteria across multiple agents?
The system uses domain-aware partitioning via the partitionCriteria() function. It first extracts domain prefixes from criterion IDs (e.g., grouping all ISC-TIER-* criteria together), then applies a greedy load-balancing algorithm that assigns domain groups to the agent with the fewest current assignments. This ensures related criteria stay together to minimize context switching while keeping workload distribution roughly equal.
What happens if there are fewer failing criteria than configured agents?
PAI automatically adjusts the effective agent count to match the workload. The algorithm caps the number of spawned agents at the number of domain groups available (Math.min(agentCount, sortedDomains.length)). If only three criteria are failing but eight agents were requested, only three agents spawn, preventing idle processes and unnecessary resource consumption.
Where does PAI store the state of parallel loop executions?
Execution state persists to a JSON file located at ~/.claude/MEMORY/STATE/algorithms/<session-uuid>.json. This file stores the parallelAgents configuration, per-agent status arrays, criteria assignments, and loopHistory timing data. The syncCriteriaToState() function in algorithm-state.ts manages these writes, enabling the PAI dashboard to render real-time progress and historical analytics.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →