Anthropic's Claude Sonnet 4.6 can spawn up to eight parallel sub-agents in a single session. OpenAI's GPT-5.2-codex runs flat tool loops. GitHub's Copilot Workspace splits planning from execution. All three architectures create the same operational risk: when multiple parallel tasks modify shared resources, your system needs a concurrency invariant, not a dashboard.
Why parallel sub-agents break production
A parent agent that spawns eight sub-agents is not running eight independent jobs. It is running eight writers that may target the same file, the same database row, or the same deployment target. Without a coordination layer, the last writer wins and the others silently fail.
A deadlock detector you can run locally
This fixture simulates a parent agent dispatching parallel sub-tasks that compete for a shared resource:
# deadlock_detector.py
"""
Simulates parallel agent sub-tasks competing for a shared resource
and detects whether the coordination layer serializes correctly.
"""
import threading
import time
import json
class SharedResource:
def __init__(self):
self.lock = threading.Lock()
self.write_log = []
self.conflict_count = 0
def safe_write(self, task_id, payload):
with self.lock:
self.write_log.append({
"task": task_id,
"payload": payload,
"timestamp": time.time()
})
return True
def unsafe_write(self, task_id, payload):
# No lock - race condition
current_len = len(self.write_log)
time.sleep(0.001) # Simulate processing delay
self.write_log.append({
"task": task_id,
"payload": payload,
"timestamp": time.time()
})
if len(self.write_log) != current_len + 1:
self.conflict_count += 1
return True
def run_parallel_tasks(resource, num_tasks=8, safe=True):
write_fn = resource.safe_write if safe else resource.unsafe_write
threads = []
for i in range(num_tasks):
t = threading.Thread(
target=write_fn,
args=(f"subagent-{i}", f"change-{i}")
)
threads.append(t)
t.start()
for t in threads:
t.join()
return resource.write_log
if __name__ == "__main__":
# Test unsafe path
unsafe_res = SharedResource()
run_parallel_tasks(unsafe_res, safe=False)
print(f"Unsafe: {len(unsafe_res.write_log)} writes, {unsafe_res.conflict_count} conflicts")
# Test safe path
safe_res = SharedResource()
run_parallel_tasks(safe_res, safe=True)
print(f"Safe: {len(safe_res.write_log)} writes, {safe_res.conflict_count} conflicts")
# Invariant: all writes must be present and conflict-free
assert len(safe_res.write_log) == 8, "Missing writes!"
assert safe_res.conflict_count == 0, "Conflicts detected!"
print("PASS: safe coordination serialized all writes")
What this tests
| Property | Unsafe path | Safe path |
|---|---|---|
| All writes present | May lose writes under contention | All writes recorded |
| Write order | Non-deterministic | Serialized by lock |
| Conflict detection | None | Counter increments |
| Replay capability | Unreliable log | Append-only log |
The operational question
When a coding agent spawns parallel sub-tasks, which of these invariants does your system enforce?
- Mutual exclusion: only one sub-task modifies a shared file at a time
- Monotonic log: every write is durably recorded in order
- Conflict detection: the system knows when two sub-tasks targeted the same resource
- Rollback path: a failed sub-task's writes can be identified and reverted
If you cannot answer all four, the parallel sub-agent architecture is running on hope, not a protocol.
Limitations
- This fixture uses threads, not processes. Real agent sub-tasks run in separate containers or sessions with network latency.
- The lock model assumes a single coordinator. Distributed coordination requires a different protocol (e.g., optimistic locking with version vectors).
- The fixture does not test deadlocks caused by circular dependencies between sub-tasks.
What to check
Before enabling an eight-sub-agent workflow on your repository, run this fixture and verify that your CI system can detect a write conflict. If it cannot, reduce the parallelism to one sub-task until it can.
Top comments (0)