Orchestration System
The orchestration system coordinates parallel workstream execution and sequential merging. This document covers the orchestrator and workstream executor.
Overview
Arial's orchestration follows a two-phase approach:
- Parallel execution - All workstreams run simultaneously in isolated Git worktrees
- Sequential merging - Completed workstreams merge one at a time to avoid conflicts
Orchestrator
The orchestrator (lib/orchestrator.ts) is the central coordination engine.
Interface
interface Orchestrator {
executors: Map<string, WorkstreamExecutor>
start(): Promise<void>
stopAll(): Promise<void>
wait(): Promise<boolean>
addContext(workstreamId: string, context: string): Promise<void>
}Lifecycle
// Create orchestrator
const orchestrator = createOrchestrator(state, {
repoRoot,
adapter,
onStateChange,
onWorkstreamEvent,
})
// Start all workstreams
await orchestrator.start()
// Wait for completion
const success = await orchestrator.wait()
// Or stop early
await orchestrator.stopAll()Event Callbacks
The orchestrator emits events via callbacks:
interface OrchestratorOptions {
onStateChange: (state: ArialState) => void
onWorkstreamEvent: (event: WorkstreamEvent) => void
}Events include:
workstream.started- Workstream execution beganworkstream.completed- Workstream finished successfullyworkstream.failed- Workstream encountered an errormerge.started- Merge operation beganmerge.completed- Merge finishedmerge.conflict- Merge conflict detected
Merge Queue
Completed workstreams are queued for merging:
// Pseudocode
const mergeQueue: Workstream[] = []
onWorkstreamComplete(workstream) {
mergeQueue.push(workstream)
processMergeQueue()
}
processMergeQueue() {
while (mergeQueue.length > 0 && !isMerging) {
const next = mergeQueue.shift()
await mergeWorkstream(next)
}
}Sequential merging ensures:
- No concurrent merge conflicts
- Clear ordering of changes
- Ability to retry failed merges
Workstream Executor
Each workstream has its own executor (lib/workstream-executor.ts).
Interface
interface WorkstreamExecutor {
workstream: Workstream
currentActivity: string
outputBuffer: OutputBuffer
start(): Promise<void>
stop(): void
isRunning(): boolean
wait(): Promise<ExecutorResult>
}Execution Flow
1. Resolve Adapter
├── Check workstream.adapter preference
└── Fall back to default adapter
2. Validate Capabilities
├── Check requiredCapabilities from spec
└── Fail if adapter doesn't support them
3. Create Git Worktree
├── Create branch: arial/{workstream-id}
└── Create worktree: .arial/worktrees/{workstream-id}
4. Load Context
├── Read spec file content
└── Load _context.md if present
5. Build Prompt
├── Apply execution.md template
└── Inject spec, context, and instructions
6. Create Runner
├── Get runner from adapter
└── Subscribe to events
7. Execute
├── Start runner
├── Stream tool events to TUI
└── Wait for completion
8. Handle Result
├── Success: stage all, commit
└── Failure: mark failed, save errorOutput Buffering
The executor maintains an output buffer for the TUI:
interface OutputBuffer {
lines: string[]
maxLines: number
add(line: string): void
getRecent(count: number): string[]
clear(): void
}Error Handling
Executors use Result types for error handling:
type ExecutorResult =
| { ok: true; summary: string }
| { ok: false; error: string }Errors are captured and stored in the workstream state:
workstream.status = 'failed'
workstream.lastError = error.messageMerge Logic
Merging is handled by lib/merge.ts.
Simple Merge
async function simpleMerge(
repoRoot: string,
branch: string
): Promise<MergeResult>Attempts a fast-forward or recursive merge. Returns conflict information if merge fails.
Merge with Retry
async function mergeWithRetry(
repoRoot: string,
branch: string,
adapter: Adapter,
maxRetries: number
): Promise<MergeResult>If conflicts occur:
- Get conflicted files
- Run conflict resolution agent
- Stage resolved files
- Complete merge
- Retry up to
maxRetriestimes
AI Conflict Resolution
The conflict agent receives:
- List of conflicted files
- Conflict markers in each file
- Context about the workstream
It produces:
- Resolved file contents
- Summary of resolutions made
Concurrency Model
Arial uses a simple concurrency model:
┌─────────────────────────────────────────────────────────┐
│ Orchestrator │
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │Executor1│ │Executor2│ │Executor3│ ... (parallel) │
│ └────┬────┘ └────┬────┘ └────┬────┘ │
│ │ │ │ │
│ v v v │
│ ┌─────────────────────────────────────┐ │
│ │ Merge Queue (sequential) │ │
│ └─────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘Key points:
- Executors run in parallel (Promise.all)
- Each executor has its own worktree (no file conflicts)
- Merges happen sequentially (one at a time)
- State updates are synchronized via callbacks
Graceful Shutdown
On Ctrl+C or error:
- Stop all running executors
- Wait for current operations to complete
- Save current state
- Report final status
process.on('SIGINT', async () => {
await orchestrator.stopAll()
saveState(repoRoot, state)
process.exit(0)
})