Orchestration Layer
Last updated on March 25, 2026
Edward Orchestration Workflow - End to End
Simple Technical KT for Engineers
Table of Contents
- What is Orchestration in Edward?
- The 5-Minute Overview
- Step-by-Step Flow
- Deep Dive: Each Layer
- Key Files to Read
- Common Questions
1. What is Orchestration in Edward?
Orchestration = How Edward coordinates all the pieces to turn a user's chat message into working code.
Think of it like a conductor leading an orchestra:
- Conductor = Orchestration layer
- Musicians = LLM, Docker sandbox, file system, package manager, build tools
- Music = The generated code
The orchestration layer makes sure everyone plays at the right time, in the right order.
2. The 5-Minute Overview
The Big Picture
The 3 Main Phases
3. Step-by-Step Flow
Step 1: User Sends Message
File: apps/web/stores/chatStream/useStartStream.ts
Step 2: API Admission
File: apps/api/services/runs/messageOrchestrator.service.ts
Key Points:
- Admission control prevents overload (global + per-user + per-chat limits)
- Run is persisted BEFORE execution (durable)
- Browser gets runId immediately for tracking
Step 3: Worker Picks Up Job
File: apps/api/services/runs/agent-run-worker/processor.ts
Key Points:
- Worker is independent (can restart without losing progress)
- Cancel signal via Redis pub/sub (fast)
- Events persisted to DB as they happen (resumable)
Step 4: Stream Session Setup
File: apps/api/services/chat/session/orchestrator/runStreamSession.orchestrator.ts
Key Points:
- Framework resolved before LLM call (better prompts)
- Token budget checked BEFORE calling LLM (fail fast)
- Post-generation validation + autofix (quality control)
Step 5: Agent Loop (Multi-Turn)
File: apps/api/services/chat/session/loop/agentLoop.runner.ts
Why Multiple Turns?
Loop Continues When:
- Tools were called but no file output yet
- No
<done>tag received - Under turn budget
Loop Stops When:
- Code/file output detected
<done>tag received- Tool budget exceeded
- Max turns reached
- Client aborted
Step 6: Turn Execution (Stream + Parse)
File: apps/api/services/chat/session/loop/agentLoop.stream.ts
Key Points:
- Chunks processed as they arrive (not waiting for full response)
- Parser converts raw text → structured events
- Events trigger immediate side effects
Step 7: Parser State Machine
File: apps/api/lib/llm/parser.ts
Parser States:
| State | Trigger | Exit |
|---|---|---|
| TEXT | Default | <thinking>, <edward_sandbox>, <file> |
| THINKING | <thinking> | </thinking> |
| SANDBOX | <edward_sandbox> | </edward_sandbox> |
Why State Machine?
- Chunks can split tags across boundaries
- Need to handle incomplete output safely
- Can't just regex over full string
Step 8: Event Handler (Side Effects)
File: apps/api/services/chat/session/events/handler.ts
Event Types:
| Event | Action |
|---|---|
| SANDBOX_START | Provision Docker container |
| FILE_START | Prepare file path |
| FILE_CONTENT | Buffer to Redis |
| FILE_END | Sanitize file |
| SANDBOX_END | Flush buffers to disk |
| INSTALL_CONTENT | Queue npm install |
| COMMAND | Run shell command |
| WEB_SEARCH | Search web |
Step 9: Sandbox Write Flow (Buffered)
File: apps/api/services/sandbox/write/buffer.ts + flush.ts
Write (Buffered to Redis)
Flush (Redis → Container)
Why Buffer?
Benefits:
- Resilient to partial failures
- Can batch multiple writes
- Can replay/repair on failure
Step 10: Install Task Queue
File: apps/api/services/chat/session/loop/agentLoop.runner.ts
Why Serialize Installs?
Step 11: Turn Outcome Decision
File: apps/api/services/chat/session/loop/agentLoop.turnOutcome.ts
Decision Tree:
Step 12: Finalize
File: apps/api/services/chat/session/orchestrator/runStreamSession.finalize.ts
4. Deep Dive: Each Layer
Layer 1: Message Orchestrator
Purpose: Admission control + queue + stream handoff
Key Functions:
unifiedSendMessage()- Entry pointcreateAdmittedRun()- Create run with limitsenqueueAdmittedRun()- Queue to workerstreamRunEventsFromPersistence()- SSE to browser
What Could Go Wrong:
- API key decryption fails
- Model/provider mismatch
- Run admission rejected (limits)
- Queue enqueue fails
Layer 2: Stream Session
Purpose: Framework resolve + message prep + token budget + finalize
Key Functions:
resolveFramework()- Detect/prefer frameworkprepareBaseMessages()- Build LLM contextcomposePrompt()- System promptcomputeTokenUsage()- Budget checkfinalizeStreamSession()- Persist results
What Could Go Wrong:
- Context limit exceeded
- Framework detection fails
- Finalize persistence fails
Layer 3: Agent Loop
Purpose: Multi-turn execution + outcome decisions
Key Functions:
runAgentLoop()- Main loopexecuteAgentTurnStream()- Single turnresolveTurnOutcome()- Continue/stop decision
What Could Go Wrong:
- Turn budget exceeded
- Max turns reached
- Abort signal received
- Continuation prompt fails
Layer 4: Parser
Purpose: Chunk → event conversion
Key Functions:
createStreamParser()- State machineprocess()- Parse chunkflush()- Handle incomplete output
What Could Go Wrong:
- Tag split across chunks
- Incomplete output
- State machine stuck
Layer 5: Event Handler
Purpose: Side effect execution
Key Functions:
handleParserEvent()- Dispatch by typehandleFileContent()- Buffer writeshandleInstallContent()- Queue installshandleCommandEvent()- Run commands
What Could Go Wrong:
- Sandbox not provisioned
- File write fails
- Install conflicts
- Command timeout
Layer 6: Sandbox Write
Purpose: Buffered writes to container
Key Functions:
writeSandboxFile()- Buffer to RedisflushSandbox()- Redis → containerscheduleSandboxFlush()- Debounced flush
What Could Go Wrong:
- Redis unavailable
- Docker exec fails
- Lock acquisition fails
- Container stopped
5. Key Files to Read
Core Orchestration
| File | Purpose |
|---|---|
apps/api/services/runs/messageOrchestrator.service.ts | Entry point |
apps/api/services/runs/agent-run-worker/processor.ts | Worker execution |
apps/api/services/chat/session/orchestrator/runStreamSession.orchestrator.ts | Stream session |
apps/api/services/chat/session/loop/agentLoop.runner.ts | Agent loop |
apps/api/services/chat/session/loop/agentLoop.stream.ts |
Parser + Events
| File | Purpose |
|---|---|
apps/api/lib/llm/parser.ts | State machine parser |
apps/api/services/chat/session/events/handler.ts | Event side effects |
apps/api/services/chat/session/loop/events.ts | Event processing |
apps/api/services/chat/session/loop/agentLoop.turnOutcome.ts | Continue/stop logic |
Sandbox
| File | Purpose |
|---|---|
apps/api/services/sandbox/write/buffer.ts | Redis buffering |
apps/api/services/sandbox/write/flush.ts | Flush to container |
apps/api/services/sandbox/write/flush.scheduler.ts | Debounced flush |
apps/api/services/chat/file.handlers.ts | File content handling |
6. Common Questions
Q: Why multi-turn loop instead of one LLM call?
A: Complex tasks need multiple steps:
Q: Why buffer writes to Redis instead of writing directly?
A: Three reasons:
- Resilience: If Docker fails, buffer survives in Redis
- Performance: One flush vs many small writes
- Batching: Multiple chunks → one file write
Q: Why serialize installs?
A: Prevent race conditions:
Q: How does cancellation work?
A: Two mechanisms:
-
Redis pub/sub (fast):
Plain text -
DB polling (backup):
Plain text
Q: What happens if worker crashes mid-turn?
A: Checkpoint system allows resume:
Q: How are tokens budgeted?
A: Multiple levels:
Q: How does framework detection work?
A: Three sources:
Q: What's the difference between run_event and message?
A: Different purposes:
7. Debugging Guide
Trace a Turn
Common Failures
| Symptom | Likely Cause | Fix |
|---|---|---|
| Context limit exceeded | Too much history | Truncate context |
| Tool budget exceeded | Too many tool calls | Reduce per-turn limit |
| Turn stuck in loop | No code output detected | Check parser, tags |
| Files not written | Flush failed | Check Redis, Docker |
| Install conflicts | Concurrent installs | Check queue serialization |
8. Summary
The Orchestration Flow in One Diagram
Key Takeaways
- Orchestration is layered - Each layer has a clear responsibility
- Multi-turn is essential - Complex tasks need iteration
- Buffering matters - Redis buffers make writes resilient
- Budgets prevent runaway - Token, tool, turn limits
- Events are durable - Persisted for replay/resume
- Cancellation is dual - Pub/sub + DB polling
End of Document