| name | parallel-dispatch |
| description | Parallel execution engine for dispatching worker agents. Used by conductor-orchestrator to spawn multiple workers simultaneously from DAG parallel groups. Handles dispatch, monitoring, aggregation, and failure recovery. |
Parallel Dispatch Protocol
Engine for executing DAG tasks in parallel using worker agents.
Core Concepts
Parallel Groups
Tasks from the DAG that can execute simultaneously:
- Same topological level (no dependencies between them)
- Either conflict-free (no shared files) or with coordination strategy
Worker Pool
Maximum 5 concurrent workers to prevent context overflow:
- Each worker is an ephemeral agent created by agent-factory
- Workers coordinate via message bus
- 30-minute timeout with heartbeat monitoring
Dispatch Protocol
1. Parse DAG for Parallel Groups
def get_executable_parallel_groups(dag: dict, completed: set) -> list:
"""
Get parallel groups that are ready to execute.
A group is ready if all dependencies are completed.
"""
ready_groups = []
for pg in dag.get("parallel_groups", []):
all_ready = True
for task_id in pg["tasks"]:
task = next((n for n in dag["nodes"] if n["id"] == task_id), None)
if not task:
continue
for dep in task.get("depends_on", []):
if dep not in completed:
all_ready = False
break
if not all_ready:
break
if all_ready:
if not any(t in completed for t in pg["tasks"]):
ready_groups.append(pg)
return ready_groups
2. Create Workers for Parallel Group
def dispatch_parallel_group(
parallel_group: dict,
dag: dict,
track_id: str,
bus_path: str
) -> list:
"""
Dispatch all workers for a parallel group.
Returns list of dispatched worker handles.
"""
from agent_factory import create_workers_for_parallel_group, dispatch_workers
workers = create_workers_for_parallel_group(
parallel_group, dag, track_id, bus_path
)
active_workers = count_active_workers(bus_path)
if active_workers + len(workers) > 5:
batch_size = 5 - active_workers
workers = workers[:batch_size]
handles = dispatch_workers(workers)
for worker in workers:
post_message(bus_path, "WORKER_DISPATCHED", "orchestrator", {
"worker_id": worker["worker_id"],
"task_id": worker["task_id"],
"parallel_group": parallel_group["id"]
})
return handles
3. Monitor Worker Progress
async def monitor_parallel_group(
parallel_group: dict,
workers: list,
bus_path: str,
timeout_minutes: int = 60
) -> dict:
"""
Monitor workers until all complete or fail.
Returns aggregated results.
"""
import asyncio
from datetime import datetime, timedelta
start_time = datetime.utcnow()
timeout = timedelta(minutes=timeout_minutes)
pending_tasks = set(pg["tasks"] for pg in [parallel_group])
completed_tasks = set()
failed_tasks = {}
while pending_tasks and (datetime.utcnow() - start_time) < timeout:
for task_id in list(pending_tasks):
event_file = f"{bus_path}/events/TASK_COMPLETE_{task_id}.event"
if os.path.exists(event_file):
pending_tasks.remove(task_id)
completed_tasks.add(task_id)
msgs = read_messages(bus_path, msg_type="TASK_COMPLETE")
for msg in msgs:
if msg["payload"]["task_id"] == task_id:
break
for task_id in list(pending_tasks):
event_file =
os.path.exists(event_file):
pending_tasks.remove(task_id)
msgs = read_messages(bus_path, msg_type=)
msg msgs:
msg[][] == task_id:
failed_tasks[task_id] = msg[][]
stale = check_stale_workers(bus_path, threshold_minutes=)
stale_worker stale:
task_id = stale_worker[]
task_id pending_tasks:
failed_tasks[task_id] =
pending_tasks.remove(task_id)
deadlock_cycle = detect_deadlock(bus_path)
deadlock_cycle:
worker_id deadlock_cycle:
status = get_worker_status(bus_path, worker_id)
status status[] pending_tasks:
failed_tasks[status[]] =
pending_tasks.remove(status[])
asyncio.sleep()
task_id pending_tasks:
failed_tasks[task_id] =
{
: (completed_tasks),
: failed_tasks,
: (failed_tasks) ==
}
Failure Handling
Failure Isolation
When one worker fails, isolate the failure:
def handle_worker_failure(
failed_task_id: str,
dag: dict,
bus_path: str
) -> dict:
"""
Handle a failed worker. Isolate failure and continue with independent tasks.
Returns impact analysis.
"""
blocked_tasks = []
for node in dag["nodes"]:
if failed_task_id in node.get("depends_on", []):
blocked_tasks.append(node["id"])
def find_all_downstream(task_id, visited=None):
if visited is None:
visited = set()
if task_id in visited:
return []
visited.add(task_id)
downstream = []
for node in dag["nodes"]:
if task_id in node.get("depends_on", []):
downstream.append(node["id"])
downstream.extend(find_all_downstream(node["id"], visited))
return downstream
all_blocked = set(blocked_tasks)
for task in blocked_tasks:
all_blocked.update(find_all_downstream(task))
for task_id in all_blocked:
post_message(bus_path, , , {
: task_id,
: failed_task_id,
:
})
all_tasks = (n[] n dag[])
can_proceed = all_tasks - all_blocked - {failed_task_id}
{
: failed_task_id,
: (all_blocked),
: (can_proceed),
:
}
Recovery Strategy
def attempt_recovery(
failure_result: dict,
dag: dict,
track_id: str,
bus_path: str,
max_retries: int = 2
) -> dict:
"""
Attempt to recover from failure.
"""
failed_task = failure_result["failed_task"]
retry_key = f"retry_{failed_task}"
retries = get_coordination_log_count(bus_path, retry_key)
if retries >= max_retries:
return {
"action": "ESCALATE",
"reason": f"Task {failed_task} failed {retries} times, needs manual intervention"
}
log_coordination(bus_path, {
"type": retry_key,
"attempt": retries + 1,
"timestamp": datetime.utcnow().isoformat() + "Z"
})
task = next((n for n in dag["nodes"] if n["id"] == failed_task), None)
if task:
worker = create_worker_agent(task, track_id, bus_path)
dispatch_workers([worker])
return {
"action": "RETRY",
"task": failed_task,
"attempt": retries + 1
}
{: , : }
Deadlock Detection & Resolution
def resolve_deadlock(
deadlock_cycle: list,
bus_path: str
) -> dict:
"""
Resolve a detected deadlock by releasing locks from oldest worker.
"""
if not deadlock_cycle:
return {"resolved": True, "action": "none"}
oldest_worker = None
oldest_time = None
for worker_id in deadlock_cycle:
status = get_worker_status(bus_path, worker_id)
if status:
started = datetime.fromisoformat(status.get("started_at", "").replace("Z", ""))
if oldest_time is None or started < oldest_time:
oldest_time = started
oldest_worker = worker_id
if oldest_worker:
release_all_locks_for_worker(bus_path, oldest_worker)
post_message(bus_path, "DEADLOCK_RESOLVED", "orchestrator", {
"cycle": deadlock_cycle,
"victim": oldest_worker,
"action": "released_locks"
})
return {
"resolved": True,
"action": "released_locks",
: oldest_worker
}
{: , : }
Aggregating Results
def aggregate_parallel_group_results(
parallel_group: dict,
bus_path: str
) -> dict:
"""
Aggregate results from completed parallel group.
"""
results = {
"parallel_group_id": parallel_group["id"],
"tasks": {},
"files_modified": [],
"commits": []
}
for task_id in parallel_group["tasks"]:
msgs = read_messages(bus_path, msg_type="TASK_COMPLETE")
for msg in msgs:
if msg["payload"]["task_id"] == task_id:
results["tasks"][task_id] = {
"status": "completed",
"commit_sha": msg["payload"].get("commit_sha"),
"files": msg["payload"].get("files_modified", [])
}
results["files_modified"].extend(msg["payload"].get("files_modified", []))
if msg["payload"].get("commit_sha"):
results["commits"].append(msg["payload"]["commit_sha"])
break
else:
fail_msgs = read_messages(bus_path, msg_type=)
msg fail_msgs:
msg[][] == task_id:
results[][task_id] = {
: ,
: msg[].get()
}
results[] = (
t.get() ==
t results[].values()
)
results
Full Parallel Execution Loop
async def execute_parallel_phase(
dag: dict,
track_id: str,
bus_path: str,
metadata: dict
) -> dict:
"""
Execute all parallel groups from a DAG phase.
Main entry point for parallel execution.
"""
completed_tasks = set(metadata.get("completed_tasks", []))
phase_results = {
"parallel_groups_executed": [],
"all_tasks_completed": [],
"failed_tasks": {},
"success": True
}
while True:
ready_groups = get_executable_parallel_groups(dag, completed_tasks)
if not ready_groups:
break
for pg in ready_groups:
if all(t in completed_tasks for t in pg["tasks"]):
continue
workers = dispatch_parallel_group(pg, dag, track_id, bus_path)
result = await monitor_parallel_group(pg, workers, bus_path)
completed_tasks.update(result["completed"])
phase_results["all_tasks_completed"].extend(result["completed"])
result[]:
phase_results[].update(result[])
phase_results[] =
failed_task, error result[].items():
impact = handle_worker_failure(failed_task, dag, bus_path)
recovery = attempt_recovery(impact, dag, track_id, bus_path)
recovery[] == :
phase_results[] =
phase_results[] = recovery[]
phase_results[].append(pg[])
worker workers:
cleanup_worker(worker[])
metadata[][].extend(
[pg[] pg ready_groups]
)
save_metadata(track_id, metadata)
phase_results
Usage in Orchestrator
async def step_parallel_execute(track_id: str, metadata: dict):
dag = parse_dag_from_plan(track_id)
bus_path = init_message_bus(f"conductor/tracks/{track_id}")
result = await execute_parallel_phase(dag, track_id, bus_path, metadata)
metadata["loop_state"]["parallel_state"]["total_workers_spawned"] = ...
metadata["loop_state"]["parallel_state"]["completed_workers"] = len(result["all_tasks_completed"])
metadata["loop_state"]["parallel_state"]["failed_workers"] = len(result["failed_tasks"])
if result["success"]:
return "EVALUATE_EXECUTION"
elif result.get("escalate"):
return "COMPLETE_WITH_WARNINGS"
else:
return "FIX"
Worker Coordination Patterns
File Lock Coordination
For parallel groups with shared files:
if not acquire_lock(bus_path, "src/shared/file.ts", worker_id):
post_message(bus_path, "BLOCKED", worker_id, {
"task_id": task_id,
"waiting_for": "FILE_UNLOCK_src/shared/file.ts",
"resource": "src/shared/file.ts"
})
if wait_for_event(bus_path, "FILE_UNLOCK_*.event", timeout=300):
acquire_lock(bus_path, "src/shared/file.ts", worker_id)
Dependency Notification
Workers notify dependents when complete:
unblocked_tasks = find_tasks_unblocked_by(task_id, dag)
post_message(bus_path, "TASK_COMPLETE", worker_id, {
"task_id": task_id,
"commit_sha": commit_sha,
"files_modified": files,
"unblocks": unblocked_tasks
})
for unblocked in unblocked_tasks:
Path(f"{bus_path}/events/DEP_READY_{unblocked}.event").touch()