source·shift
← all posts

The orchestrator that learns which model to trust

You have five AI models on tap — Claude, GPT, Kimi, GLM, MiniMax — and a steady stream of jobs to run: “research this topic,” “implement this fix,” “review this diff.” Two questions decide your bill and your quality, and you’re probably answering both by guessing:

  1. Which model should handle this job?
  2. Is that still the right answer a month from now?
A job document fans out to five model lanes — Opus, Codex, Kimi, GLM, MiniMax. Their scores fill a bar chart inside a STATE.DB box where the Codex bar is tallest, and a router sends the next job back to the Codex lane.
Every lane runs the same job, the scores land in state.db, and the next job goes to whoever won.

The problem: your orchestrator picks once and never learns

Most setups answer question one by hardcoding a choice — usually the cheapest model, or the one that looked best the day you wired it up — and answer question two never. The config you wrote in March is still routing your jobs in September, even though the models have shifted underneath it and you’ve accumulated thousands of runs of evidence about which one actually wins each kind of work. Nothing reads that evidence. Nothing acts on it.

So you land in one of two bad spots. Either you play it safe and send a frontier model at everything — paying premium rates for jobs a cheap model would have nailed — or you guess cheap and quietly ship worse results on the jobs that needed the strong model. Both are the same underlying failure: you’re routing blind, and the routing never improves.

Two cards under the heading Routing Blind. Left card, Overpay: a frontier model stamped onto every job. Right card, Worse Results: a cheap model with a downward quality arrow.
Route once and never measure, and you land in one of these two: paying too much, or shipping too little.

An orchestrator that always routes the same way isn’t orchestrating. It’s a static config file with extra steps.

mini-ork is a CLI agent orchestrator built to fix exactly this. It treats every model as a contestant, every run as a graded match, and sends the next job to whoever has been winning — automatically, and for reasons you can read off a SQLite file by hand.

How it solves it: make the models compete, then keep score

mini-ork runs your workflow across competing model “lanes” — opus_lens, codex_lens, kimi_lens, glm_lens, minimax_lens. After each run it scores what happened and writes the scores to a local state.db. Before the next run, a router reads those scores and decides whether the evidence justifies moving a job off its default lane.

flowchart TD
    RUN["A run finishes\n(lanes competed in parallel)"] --> SCORE["WRITE HALF\ngrade every run"]
    SCORE --> DB[("state.db\n(plain SQLite)")]
    DB --> ROUTE["READ HALF\nrouter reads the scores"]
    ROUTE --> GATE{"Enough evidence?\nsamples high enough\nAND a lane is clearly ahead?"}
    GATE -->|yes| FLIP["route this job →\nthe winning lane"]
    GATE -->|no| STAY["stay on the\ndefault lane"]
    FLIP -.->|next run| RUN
    STAY -.->|next run| RUN

The grading is deliberately boring — no neural network, no training run, just arithmetic you can audit. Two scores carry the weight:

If you want the actual arithmetic, it stays small enough to fit in your head. Each run earns a process reward r[0,1]r \in [0,1] from six yes/no signals:

r=0.40[success]+min ⁣(0.15, 0.20[tools]+0.10[files])+0.15[approved]+0.10[sane duration]+0.05[real cost]\begin{aligned} r = {} & 0.40\,[\text{success}] + \min\!\big(0.15,\ 0.20\,[\text{tools}] + 0.10\,[\text{files}]\big) \\ & + 0.15\,[\text{approved}] + 0.10\,[\text{sane duration}] + 0.05\,[\text{real cost}] \end{aligned}

Each bracket is 1 when true and 0 when not. The two activity terms are capped together at 0.15, so a noisy run that failed but touched a pile of files can’t outscore a clean success. And the approved term only counts when the run also succeeded, so a generous judge can’t rescue a failure.

Then each lane’s reward on the same job type is turned into a z-score against the group it competed in:

Ai=riμgroupσgroupA_i = \frac{r_i - \mu_{\text{group}}}{\sigma_{\text{group}}}

Three pieces, nothing more. rir_i is lane i’s own reward from the formula above. μgroup\mu_{\text{group}} is the average reward of every lane that ran this same job type. σgroup\sigma_{\text{group}} is how spread out those rewards were. Subtract the average and divide by the spread and you get AiA_i: how many standard deviations above or below the field that lane landed. Positive means it beat the room, negative means it lagged, zero means it was dead average.

Walk one through. Say three lanes take the same research job and earn rewards of 0.95, 0.70, and 0.55. The average is μgroup=(0.95+0.70+0.55)/3=0.73\mu_{\text{group}} = (0.95 + 0.70 + 0.55) / 3 = 0.73. The spread works out to σgroup0.16\sigma_{\text{group}} \approx 0.16. So the top lane’s advantage is A=(0.950.73)/0.16+1.3A = (0.95 - 0.73) / 0.16 \approx +1.3, and the bottom lane’s is (0.550.73)/0.161.1(0.55 - 0.73) / 0.16 \approx -1.1. Same raw rewards, but now they’re on a scale the router can compare across wildly different job types.

That AiA_i is the relative advantage in the table below (mini-ork weights more recent runs a little heavier, so the live numbers won’t match this hand example exactly, but that’s the shape). The σ=0 guard further down is just the denominator talking: if every lane scored the same, σgroup=0\sigma_{\text{group}} = 0, so mini-ork records Ai=0A_i = 0 instead of dividing by zero.

A number line with a bell curve centered on the group mean. Three lanes sit at their z-scores: glm_lens at -1.18 far left, kimi_lens at -0.07 just left of center, codex_lens at +0.90 to the right. Caption strip reads advantage equals reward minus mean over spread.
The bell curve is the group's spread; subtracting the mean and dividing by that spread is what drops each lane onto a shared scale.

Here’s the same three lanes as a real slice straight out of state.db after that research run:

Lanerolerelative advantage
codex_lensresearcher+0.90
kimi_lensresearcher−0.07
glm_lensresearcher−1.18

Read it directly: on this job type codex_lens ran about 0.9 standard deviations better than the field, glm_lens about 1.2 worse. That’s the table that told the router to stop defaulting to the cheap kimi_lens and start sending research jobs to codex_lens.

And there’s one guard that makes the whole thing trustworthy rather than hand-wavy: if every lane scored the same, or only one lane ran, the spread is zero and mini-ork records zero advantage. No flip.

No competition, no learning. The system refuses to manufacture a signal out of a single data point — it would rather learn nothing than learn something fake.

The router only moves a job off its default once there’s enough evidence and a lane is clearly ahead. “Enough evidence” is a minimum sample count on the prompt’s win rate, win rate=wins/(wins+losses)\text{win rate} = \text{wins} / (\text{wins} + \text{losses}), with ties left out of the denominator so a string of vacuous runs can’t fake confidence. “Clearly ahead” is the positive AiA_i from above. Until both clear, it stays on safe defaults. That gate is the difference between “measured improvement” and “a model gambling with your budget.”

How that’s different from LangGraph, CrewAI, AutoGen, and the OpenAI Agents SDK

To see what’s actually new here, ask the popular agent frameworks the same question: when a step needs to run, how do they decide which model or agent handles it? We read the routing code in each. The answers cluster into three patterns — and none of them is “remember who won last time.”

Everyone else decides routing fresh every run — a hardcoded edge, a fixed order, or an LLM picking in-context — and then forgets. mini-ork is the one that keeps score.

LangGraph models a workflow as a graph you wire by hand. Routing happens through add_conditional_edges — developer-written functions that read the current state and return the next node — plus Command(goto=...), which lets a node hand control to another. It has durable, checkpointed state, but that state is the conversation and graph position, never a record of which model performs best on which task. The topology is whatever you drew; it doesn’t move on its own.

CrewAI offers two process modes: sequential (tasks run in a fixed order) and hierarchical, where a manager_llm reads the goal and delegates to worker agents. The manager is smart, but it reasons from scratch in its context window every time; nothing persists about which agent or model actually delivered last week.

AutoGen runs multi-agent conversations, and its SelectorGroupChat picks the next speaker by formatting a selector_prompt and asking an LLM to name one — _select_speaker literally calls model_client.create() each turn. A fresh, prompted choice from the transcript, with no memory of which speaker tends to win this kind of task.

OpenAI Agents SDK — one of the most-adopted frameworks of 2026 — routes through handoffs: each agent exposes a transfer_to_<agent> tool, and the running LLM decides in-context which one to call. It’s clean and provider-agnostic, but the handoff target is chosen fresh from the prompt every time — the SDK keeps no memory of which agent tends to win a given kind of task. (Reading the source confirms it: handoffs are everywhere, and there isn’t a single reward, win-rate, or advantage signal in the routing path.)

SystemHow it picks who runs a stepRouting learns from past outcomes?
mini-orkModel lanes compete; router flips to the empirically-winning lane, evidence-gatedYes — scores persist in state.db and re-route the next run
LangGraphHand-wired graph edges + conditional functions + LLM Command handoffsNo — topology is fixed; state is conversation, not performance
CrewAIFixed sequential order, or a manager_llm delegating in hierarchical modeNo — the manager re-decides in-context and forgets
AutoGenSelectorGroupChat prompts an LLM to name the next speaker each turnNo — a fresh prompted choice from the transcript
OpenAI Agents SDKLLM calls a transfer_to_<agent> handoff tool, chosen in-contextNo — handoff target re-decided each turn, nothing remembered

To be fair: LangGraph, CrewAI, AutoGen, and the OpenAI Agents SDK are broad, mature platforms — durable execution, tooling, human-in-the-loop, huge ecosystems — and mini-ork doesn’t try to replace any of them. The comparison is narrow on purpose, on one axis: does the orchestrator measure which model wins and re-route accordingly? On that axis, the others route statically or ask a model to choose in the moment. mini-ork is the only one that treats routing as something to measure and improve over time rather than decide and discard.

How to use it for your own good

The payoff is concrete, and it’s mostly about money and trust:

One clarification, since people assume it’s all one system: mini-ork pairs with ContextNest, a separate memory service, but ContextNest sits beside the routing loop, not inside it. It prefetches relevant memory to make each run better-informed; it never touches the scores that flip the router. One service makes each run smarter about context, the other makes the next run smarter about routing — two orthogonal wins you can adopt independently.

The takeaway

If you’re running multiple models behind one orchestrator, the question isn’t “which model is best?” — it’s “is anything measuring which model is best, and acting on it?” For LangGraph, CrewAI, AutoGen, and the OpenAI Agents SDK the honest answer is no: they route blind and forget. mini-ork’s answer is a loop you can read end to end — grade each run, compare lanes on the same job, flip only when the evidence is real — and the result is routing that gets cheaper and better while you do nothing.

Point it at your own jobs and watch the table fill in. The first time you see VERDICT: LEARNING OBSERVED — router moved kimi_lens -> codex_lens scroll past, it stops being a config file and starts being an orchestrator.

Further reading

Comments

Sign in with GitHub to leave a comment. Threads live on SourceShift/blog-comments — moderated.