ADR-0011: AI Run Orchestration Model¶
Status¶
ACCEPTED
Date: 2026-02-14
Changelog¶
| Date | Revision | Description | Author |
|---|---|---|---|
| 2026-02-14 | 1.0 | Initial version | souperk |
| 2026-02-15 | 1.1 | Update status; orchestration loop and real tool execution are implemented | souperk |
Implementation Status¶
The orchestration loop and tool execution are fully implemented. RunOrchestrator executes the complete model-call → tool-call → repeat loop with SSE streaming, cooperative cancellation, and all safety limits (max model calls, max tool calls, max wall time, loop breaker). Real HTTP tool execution is handled by HttpToolExecutor via httpx.
Not yet implemented: - Run queuing/prioritization for multi-tenant fairness - Cost attribution using token counts
See also: ADR-0014 — real LLM provider integrations (OpenAI-compatible, Ollama) are now implemented. The orchestrator resolves a model provider per-run from the space policy's ModelBinding records, falling back to StubModelProvider when no binding is configured.
Context¶
The AI service needs an orchestration model for executing multi-step LLM interactions. A "run" represents a single orchestration cycle: the system takes conversation context, calls an LLM, optionally executes tool calls, and produces a final response. This must support:
- Asynchronous execution (runs may take 10-120 seconds)
- Server-Sent Events (SSE) streaming of intermediate steps to clients
- Cancellation of in-progress runs
- Reproducibility (which tools and model were used)
- Safety limits to prevent runaway loops or excessive resource consumption
Key constraints: - Runs are long-lived relative to HTTP request timeout (may span many model + tool calls) - Clients need real-time visibility into run progress - The system must be resilient to model provider failures mid-run - Tool calls may fail independently and should not necessarily terminate the entire run
Decision¶
We will implement an async run orchestration model with the following design:
-
Run State Machine: Runs progress through states:
pending->running->streaming->completed|failed|cancelled. State transitions are validated on theRunaggregate withRunStateErrorraised for invalid transitions. Terminal states (completed,failed,cancelled) are immutable. -
Run Steps: Each atomic operation within a run (model call, tool call) is recorded as a
RunStepchild entity with its own status (pending->running->completed|failed), timing, token counts, and tool I/O. Steps provide a complete audit trail. -
Pinned Tool Catalog: At run creation time, the current
ToolCatalogRevisionis recorded on the run. All tool resolution during the run uses this pinned revision, ensuring consistent behavior even if tools are refreshed mid-run. -
SSE Streaming: The
GET /runs/{uuid}/streamendpoint returns an SSE event stream. Events includerun.step.created,run.step.completed,run.completed,run.failed,run.cancelled. This gives clients real-time visibility without polling. -
Cancellation:
POST /runs/{uuid}/cancelsets a cancellation flag. The orchestration loop checks this flag between steps and transitions tocancelledstate. In-flight model calls are not interrupted but the loop exits after the current step completes. -
Safety Limits: Enforced per-run via
SpacePolicyconfiguration: max_model_calls(default 12): Maximum LLM invocations per runmax_tool_calls(default 20): Maximum tool executions per runmax_wall_time_seconds(default 120): Wall-clock timeout-
Loop Breaker: Detects 3 consecutive identical tool calls (same tool_id + same arguments) and terminates the run with
RunLimitExceededError -
Pessimistic Locking: State transitions acquire a
SELECT ... FOR UPDATElock on the run row to prevent concurrent modifications (e.g., cancellation racing with completion).
Consequences¶
Positive¶
- Complete audit trail via RunSteps enables debugging and cost attribution
- Pinned catalog revision ensures reproducible runs
- SSE streaming gives clients real-time feedback without polling overhead
- Safety limits prevent unbounded resource consumption
- Loop breaker catches degenerate LLM behavior (repeated identical tool calls)
- Pessimistic locking prevents race conditions in state transitions
Negative¶
- SSE connections are long-lived, consuming server resources proportional to active runs
- Cancellation is cooperative (between steps), not preemptive; a slow model call must complete before cancellation takes effect
- Run step recording adds write amplification to the database during orchestration
- Pessimistic locking may cause brief contention if cancellation and completion race
Neutral¶
- Future work may add run queuing/prioritization for multi-tenant fairness
- Token counts on run steps enable future cost attribution and billing features
Alternatives Considered¶
- Synchronous Request-Response Runs
- Description: Block the HTTP request until the run completes, return full result
- Pros: Simpler client integration, no SSE complexity
- Cons: HTTP timeouts for long runs (30-120s), no intermediate progress, poor UX for multi-step orchestration
-
Why not chosen: Runs routinely exceed typical HTTP timeout windows; users need intermediate feedback
-
Polling-Based Status Checks
- Description: Start run asynchronously, client polls
GET /runs/{uuid}for status - Pros: Simpler server implementation (no SSE), works with all HTTP clients
- Cons: Latency between steps (poll interval), unnecessary load on server, poor real-time experience
-
Why not chosen: SSE provides superior UX with lower overhead; polling can be added as fallback later
-
WebSocket-Based Streaming
- Description: Use WebSocket connections for bidirectional run communication
- Pros: Full-duplex (client can send cancellation inline), lower overhead than SSE for high-frequency updates
- Cons: More complex server and client implementation, harder to load balance, not all proxies handle WebSockets well
- Why not chosen: SSE is sufficient for our unidirectional streaming needs and has better proxy compatibility; cancellation via separate POST is adequate
References¶
- Server-Sent Events (SSE) Specification
- OpenAI Assistants API - Runs
- ADR-0010: AI Tool Registry and Manifest Discovery (catalog pinning)