Workflow selection — Deep Dive
When a project has more than one workflow, the coordinator must decide which one to apply before it decomposes a goal into a work plan. This page explains the selection algorithm, the override hierarchy, and how all of the above compose into a single deterministic-first decision that is always resilient to model failure.
For the user-facing controls see Submitting and Watching Runs — Workflow selection. For the API reference and override precedence table see Coordinator reference — Workflow selection.
What it is
Workflow selection answers one question: which process shape should this task follow? The built-in default, conformance-checked catalog workflows, and valid project YAML workflows supply the candidate set. Blueprint restrictions apply to normal availability; the built-in default remains available.
Automatic selection is silent when at most one workflow is available: no LLM call or selection event. Available explicit overrides are checked earlier and emit an event even for a singleton. With multiple candidates, the model has bounded authority and a deterministic fallback.
Selection logic
WorkflowSelector.SelectAsync (apps/Agentweaver.Api/Coordinator/WorkflowSelector.cs:93) builds a process-fit prompt that includes:
- the task description (goal)
- the team's role titles
- each available workflow's id, name, description, and whether it is project/custom or built-in/library
The selection rules given to the model are explicit:
- Process fit, not name similarity. A closest-sounding built-in is a bad pick if its process does not fit.
- Prefer project/custom workflows over built-in/library workflows when a custom workflow can perform the requested process.
- Prompt fallback. The prompt asks for the first listed workflow (the project default) when no process fits. The runtime fallback has a more specific rule, below.
The requested response is { "selected": "<id>", "rationale": "<1-2 sentences>" }. The parser also accepts supported string forms, normalizes ids/display names, strips thinking blocks, and can recover an unambiguous candidate named in prose. Malformed output or an unknown choice permits one retry; an exception falls back immediately. Every accepted choice still resolves to a supplied candidate (WorkflowSelector.cs:83–174, :219–235).
The selector's actual ResolveDefault prefers an available default or standard, then the first non-code-review candidate, then the first candidate (WorkflowSelector.cs:193–215). This can differ from the configured project default placed first by the coordinator. The coordinator's outer exception fallback, by contrast, returns its resolved project default (CoordinatorOrchestratorExecutor.cs:389–403). These are distinct fallback sites, not one universal “first candidate” rule.
Override hierarchy
The algorithm runs overrides and availability checks before the LLM is ever invoked. In priority order (highest first):
1. Request-level dialog override
StartOrchestrationRequest.WorkflowOverrideId is set when the user selects a workflow from the Workflow dropdown in the Start task dialog. The endpoint passes it through CoordinatorRunService.StartCoordinatorRunAsync to CoordinatorDraftInput.WorkflowOverrideId.
In SelectWorkflowAsync (apps/Agentweaver.Api/Coordinator/CoordinatorOrchestratorExecutor.cs:299):
var overrideId = input.WorkflowOverrideId
?? await ResolveWorkflowOverrideIdAsync(backlogStore, input.RunId, ct);The dialog override is resolved first. If it is set, it wins over the backlog-task pin without a fallback to the pin.
2. Backlog-task pin
BacklogTask.WorkflowOverrideId is set on the task card via PUT /api/projects/{id}/backlog/tasks/{taskId}/workflow-override. When the heartbeat picks up the task and the dialog override is absent, ResolveWorkflowOverrideIdAsync (apps/Agentweaver.Api/Coordinator/CoordinatorOrchestratorExecutor.cs:515–523) reads the override from the backlog task.
Both the dialog override and the backlog-task pin are subject to the same availability check: the workflow must exist in the available set. An unavailable override is logged and ignored; selection continues with conversational override, candidate count, and then the LLM path as applicable—not an immediate default.
3. Conversational override (use {workflow-id})
Typing use {workflow-id} as revision feedback before confirming the OutcomeSpec triggers WorkflowSelector.TryParseOverride (apps/Agentweaver.Api/Coordinator/WorkflowSelector.cs:182), which matches the pattern:
^\s*use\s+(?<id>[A-Za-z0-9._-]+)\s*$This is checked before singleton handling (apps/Agentweaver.Api/Coordinator/CoordinatorOrchestratorExecutor.cs:331–349). An available conversational choice emits a selection event and wins unless an earlier available request/backlog override already returned.
4. LLM auto-select
The initial WorkflowSelector.SelectAsync call is reached when two or more available workflows exist and no available explicit override resolved. The choice and rationale are surfaced in a coordinator.workflow_selected event.
5. Deterministic fallback
The coordinator resolves the project default first (WorkflowRegistry.ResolveDefault) and orders it first. It does not pre-empt automatic selection. See the distinct selector and coordinator fallback rules above.
available dialog override > available backlog-task pin (when dialog value absent)
> available conversational use {id} > singleton / model selection > deterministic fallbackPost-decomposition compatibility
This is a separate check, not another override priority. If decomposition produces code but the selected workflow lacks build_test, automatic selection reselects among compatible candidates. If none exist, it tries the conformance-checked platform software-delivery, then bug-fix, outside the project's normal allowed-set filter; absence of a valid fallback fails instead of silently dropping the gate. An explicit choice remains selected with a missing-Build/Test warning (CoordinatorOrchestratorExecutor.cs:407–494; WorkflowRegistry.cs:108–119).
Invocation context
RunOrigin records how work started; selection does not filter candidates by origin. There is no current WorkflowInvocationKind or ResolveInvocationKindAsync API in this path (CoordinatorOrchestratorExecutor.cs:297–300).
Event and schedule trigger producers are implemented. Subject to automation activation and durable invocation claims, they publish Ready backlog tasks pinned to the matching workflow; normal pickup then starts the coordinator. See invocation context. Upstream trigger conditions determine when work begins, not which workflows are valid.
End-to-end flow
The workflow selection event
An available explicit override emits coordinator.workflow_selected before the count check. The multi-candidate selector path also emits it, and post-decomposition reselection may emit a second event. The event carries:
| Field | Meaning |
|---|---|
selectedId | The chosen workflow id |
selectedName | The chosen workflow name |
rationale | Why this workflow was selected (or why the default was used) |
wasAutoSelected | true when the LLM (or fallback) picked; false for an explicit user override |
overrideHint | "Reply 'use {other-id}' to change..." with the available list |
available | The full list of available workflows at selection time |
Only the automatic singleton/empty-candidate shortcut is silent. The coordinator's outer exception fallback persists reasoning without emitting this selection event.
Source
| File | Responsibility |
|---|---|
apps/Agentweaver.Api/Coordinator/WorkflowSelector.cs | Selector contracts, prompt construction, override parser, bounded retry, candidate matching and fallback |
apps/Agentweaver.Api/Coordinator/CopilotWorkflowSelectionModel.cs | Production model-completion seam |
apps/Agentweaver.Api/Coordinator/CoordinatorOrchestratorExecutor.cs:271–494 | Override hierarchy, candidate selection and post-decomposition compatibility |
apps/Agentweaver.Api/Coordinator/CoordinatorMessages.cs:15 | CoordinatorDraftInput.WorkflowOverrideId — carries the dialog override into the executor |
apps/Agentweaver.Api/Contracts/Dtos.cs | StartOrchestrationRequest.WorkflowOverrideId — the request DTO field |
apps/Agentweaver.Api/Endpoints/ProjectEndpoints.cs | POST /api/projects/{id}/orchestrations — passes workflow_override_id to CoordinatorRunService |
apps/web/src/api/client.ts | startOrchestration(projectId, goal, workflowOverrideId?) — passes workflow_override_id in the request body |
Diagram details and constraints
| Element | Contract |
|---|---|
| title | Workflow selection |
| subtitle | Trigger-agnostic • explicit choices precede singleton |
| returns-heading | SOURCE / RETURN |
| outcomes-heading | OUTCOMES |
| footer | Post-decomposition Build & Test compatibility is a separate check (executor:407–494). |
| Load candidates | Load candidates |
| Load candidates | Project default ordered first |
| Load candidates | registry.Available |
| Explicit override? | Explicit override? |
| Explicit override? | Dialog value, else backlog pin |
| Explicit override? | must be available |
| Conversational choice? | Conversational choice? |
| Conversational choice? | Revision feedback: use {id} |
| Candidate count | Candidate count |
| Candidate count | Only automatic selection |
| Candidate count | 0 / 1 / multiple |
| Ask selection model | Ask selection model |
| Ask selection model | Goal + roles + process fit |
| Ask selection model | maximum 2 attempts |
| Usable candidate? | Usable candidate? |
| Usable candidate? | Parse / normalize / prose match |
| Usable candidate? | reject unknown choices |
| Selected workflow | Selected workflow |
| Selected workflow | Emit selection + rationale |
| Selected workflow | workflow_selected |
| Explicit choice | Explicit choice |
| Explicit choice | Emit selection |
| Explicit choice | not auto-selected |
| Silent choice | Silent choice |
| Silent choice | One: candidate |
| Silent choice | Zero: project default |
| Model fallback | Model fallback |
| Model fallback | default / standard then non-code-review |
| Model fallback | else first candidate |
| Outer fallback | Outer fallback |
| Outer fallback | Project default |
| Outer fallback | when catch permits |
| edge-02-label | available |
| edge-03-label | absent / invalid |
| edge-06-label | 0 or 1 |
| edge-07-label | 2+ |
| edge-08-label | response |
| edge-09-label | exception |
| edge-10-label | accepted |
| edge-11-label | retry once |
| edge-12-label | 2 unusable |
| edge-13-label | emit choice |
| edge-14-label | outer catch |
| fallback | default / standard |
