API · Orchestration & Runs¶
The run surface: kicking off agent work, inspecting run history, the scheduler, model benchmarking, and the test/compare endpoints. All endpoints are unauthenticated (see API conventions).
OrchestratorController¶
api/orchestrator — the project-run API. The agent-run kickoff is POST /api/orchestrator/run with a RunRequest body.
| Verb | Route | Request | Response | Async |
|---|---|---|---|---|
| POST | /api/orchestrator/run |
body RunRequest |
200 { Response, KnowledgeUsed } |
yes |
| POST | /api/orchestrator/clear-session/{projectId} |
route projectId:int |
200 |
no |
| POST | /api/orchestrator/end-session/{projectId} |
route projectId:int; query stopContainer:bool=false |
200 { Message, ContainerStopped } |
yes |
| GET | /api/orchestrator/container-status/{projectId} |
route projectId:int |
200 status object |
no |
| POST | /api/orchestrator/test-drive |
body TestDriveRequest |
TestDriveResponse |
yes |
| GET | /api/orchestrator/runs/latest |
— | List<ProjectRunStateDto> (ProjectId, RunGuid, Status, StartedAt, CompletedAt): each project's newest run, one row per project that has run. Status is the run's status, or Interrupted for a run still recorded Running or Queued that started before the server process did (a restart killed it). The Projects page badge reads it. |
yes |
| GET | /api/orchestrator/runs/{projectId} |
route projectId:int; query limit:int=20 (1–100) |
List<BuildRun> |
yes |
| GET | /api/orchestrator/runs/{projectId}/{runGuid:guid} |
route projectId:int, runGuid:guid |
BuildRunDetailResponse |
yes |
| GET | /api/orchestrator/runs/{projectId}/tool-invocations |
route projectId:int; query runGuid:string?, step:int? |
ToolInvocationsResponse |
yes |
| GET | /api/orchestrator/runs/{projectId}/summaries |
route projectId:int; query limit:int |
List<RunSummaryDto> |
yes |
| GET | /api/orchestrator/runs/{projectId}/trajectory |
route projectId:int; query runGuid:string? |
TrajectoryDto / 404 — plus events (firstSeq, lastSeq, count): the run's slice of the durable session log, null for runs that predate it |
yes |
| POST | /api/orchestrator/run-from-template |
body RunFromTemplateRequest (Wait, default true) |
RunFromTemplateResponse; 202 with Status: "started" when Wait is false; 400 when the message is missing or an overrides field names an unknown slot |
yes |
| GET | /api/orchestrator/runs/{projectId}/logs |
route projectId:int; query runGuid:string? |
200 (run meta + steps + messages) |
yes |
| POST | /api/orchestrator/runs/{projectId}/cancel |
route projectId:int |
200 / 404 |
yes |
| POST | /api/orchestrator/run-research |
body RunResearchRequest |
RunResearchResponse (fire-and-forget) |
yes |
| POST | /api/orchestrator/continue-research |
body ContinueResearchRequest |
ContinueResearchResponse; 409 if a build is active |
yes |
| GET | /api/orchestrator/latest-research |
— | 200 / 404 |
yes |
RunRequest¶
The load-bearing request body. Fields:
| Field | Type | Notes |
|---|---|---|
ProjectId |
int | Target project |
Message |
string | User message that drives the run |
ToolDefaults |
Dictionary<string, Dictionary<string,object>>? |
Per-tool argument defaults (toolName → {argName: value}); agent-supplied values always win. Flows to McpSessionContext.ToolDefaults → router. |
SamplingOverrides |
Dictionary<string, Dictionary<string,object>>? |
Per-role sampling params; only seed is honored today |
HandoffChecks |
Dictionary<string,object>? |
Gate conditions (e.g. min_kb_documents) |
SourceAgent |
string? | Who is sending the message, recorded on the build run and in its build.completed / build.failed webhooks; absent means the Studio (ui). run-from-template forwards its own SourceAgent the same way. |
Sync vs. fire-and-forget
run, run-from-template, and test-drive await the full build — except run-from-template with Wait: false, which creates the project, starts the run in the background and answers 202 with the project id and Status: "started" (for a caller with a short request timeout). run-research and continue-research return 200 immediately and run in the background — poll GET /api/orchestrator/runs/{projectId} for progress.
RunRequest, TestDriveRequest, RunResearchRequest/Response, ContinueResearchRequest/Response, and ToolInvocationsResponse are declared inline in the controller. RunFromTemplateRequest/Response and BuildRunDetailResponse are in Aetherium.Shared.DTOs.
TrajectoryEvalController¶
api/trajectoryeval — LLM-as-judge scoring of a completed run's trajectory, and the template-promotion gate built on it.
| Verb | Route | Request | Response | Async |
|---|---|---|---|---|
| POST | /api/trajectoryeval/run/{buildRunId} |
route buildRunId:int |
TrajectoryEvalResult |
yes |
| POST | /api/trajectoryeval/promote |
body { BuildRunId, TemplateId, Threshold? } |
TrajectoryPromotionResult |
yes |
| GET | /api/trajectoryeval/run/{buildRunId} |
route buildRunId:int |
List<TrajectoryEvalResult> (persisted verdicts) |
no |
The judge model resolves from AI_TrajectoryJudgeModelId (→ AI_SupervisorModelId → AI_ModelId); promotion requires the trajectory score to meet AI_PromotionScoreThreshold (default 7). Verdicts persist in interaction_logs (type trajectory_eval). With AI_TrajectoryEvalOnRunComplete set to true, the Engine scores every finalized run (Completed or Failed) in the background after the run is broadcast; it is off by default and never gates a run. The run viewer shows the latest verdict and can request one on demand.
FinishingController¶
api/finishing — the two-phase finishing pass and the human answer between its halves. A gate is one project's pass: Phase 1 researches and writes a brief, the operator answers, Phase 2 applies the answer.
| Verb | Route | Request | Response | Async |
|---|---|---|---|---|
| GET | /api/finishing/gates |
query limit (default 100) |
200 { Gates, Waiting, LongestWaitSeconds } |
no |
| GET | /api/finishing/gates/{projectId} |
route projectId:int |
FinishingGate, 404 when the project has never had one |
no |
| POST | /api/finishing/gates |
body { ProjectId, SourceRunGuid? } |
FinishingGate, 409 with the reason when no gate opened |
no |
| POST | /api/finishing/gates/{projectId}/answer |
body { Answer } |
FinishingGate, 409 when the gate is not awaiting an answer |
no |
The listing puts the gates waiting on a human first and the longest-waiting at the top, which is what makes it usable as a queue. A gate's Phase1RunGuid and Phase2RunGuid name each phase's run from the moment it starts; before that the column holds the claim token claimed. A pass the Engine was running when it restarted cannot finish: on its next poll the finishing runner closes that gate as Failed, with a note naming the stranded run, and fails the run's row if it still reads Running. The pass is not restarted on its own; open a new gate to run it again.
The React client's Finishing page (/app/finishing, under Operations) is the screen for this controller: it lists each project's current gate, opens a pass, shows the brief and the three candidate pages, takes the answer, and shows Phase 2's repair list and delivery gate, with links to both phase runs. The brief ends its three directions with a Recommended: <A, B or C> — <why> line, which the page marks in its answer form; an answer that names no direction gets the recommended one in Phase 2.
Opening a gate by hand skips the eligibility checks — the operator is the decision — but not the recursion guard or the one-live-gate rule, which are about consistency rather than about who asked. Phase 1 is told the status of the build it finishes, a failed one included, and the brief opens on it. A re-opened gate starts clean: before Phase 1, the earlier pass's finishing/ files, except the style record, move to finishing-history/ beside the workspace. When the brief cites a reference that research never captured, the gate's Note says which. Phase 2 repairs before it restyles: at its start the Engine writes finishing/repair-list.md, the build team's own required outputs that fail on disk and every page reference that points at nothing, and the director plans the repair as step 1 from a one-line summary of each item (it plans with up to 10,240 output tokens; the full text stays in the file for the repair step). The director is told to give any step that edits a page the build owed that page's owed checks, and the final step owes finishing/delivery-gate.md verdict PASS. A PASS the Engine's own check_finishing rows refuse is held, and a FAIL goes back to the steps that wrote the failing files, each told what those rows found in its files. When Phase 2 ends, the Note also records what it took away from the pages (pictures, headings, most of a page's words, a removed page), recorded rather than enforced. Automatic opening is the Aetherium:Finishing:AutoTrigger key (Configuration), off by default.
Answering writes the text to finishing/answer.md in the project's workspace before stamping the gate, and the order is deliberate: a gate stamped Phase 2 with no answer file is a pass that cannot start, whereas a file with no stamp is just a file. It is also how the Phase-2 runner gets the prose — the gate table carries timestamps, not text. The shape is in Schema · FinishingGate.
Where to go next¶
- Tools — the tool catalog a run dispatches against.
- Projects, Teams & Agents — creating the project a run targets.
- Knowledge & Memory — the conversation and memory a run reads and writes.