Why we retired the agent auction
Cambrian used to pick an agent for each step by asking every candidate to bid. The bids carried no information and cost a process boot each, so we deleted the auction. The comparison that was meant to prove it right never ran, and this post says why.
By Doruk Eray, Founder ·
For most of its life, Cambrian chose which agent did each step of a task by auction. The planner broke a request into steps. For each step, the kernel asked every agent that could take it how confident it was, then weighed those bids against each agent's track record and picked a winner.
It is an appealing design. It reads like a market: agents compete for work, good agents earn more of it, and nobody has to write a routing table. On 7 August 2026 we deleted it. This post explains what we found when we looked inside the bids, what replaced them, and what we did not manage to measure before the old code was gone.
What a bid actually contained
An agent bid through a method called propose(). When we read every propose() that shipped with the reference agents, each one was a hand-written keyword table. A calculator agent scanned the task text for maths words and returned 0.9 if it found one and 0.1 if it did not. Agents that did not override the method bid a flat 0.5.
Across the whole reference roster, the set of possible bids had six values: 0.9, 0.85, 0.5, 0.3, 0.2 and 0.1. No model call was involved and nothing was learned. The auction was a routing table, just one that lived in each agent's Python, where the kernel could not audit it, version it, benchmark it or improve it.
Asking for that bid was not free either. To answer propose(), an agent's process had to be running, so soliciting bids started agents. A single step could cold-start up to five agent processes and then throw four of them away before any work began. That was the cause of a symptom we had been chasing for a while: agents that seemed to restart on every query.
Why bidding works for robots and not for agents
Task auctions come from multi-robot systems, and there they make sense. A robot's bid is a computed physical cost: how far it is from the job, how much battery it has left, what it can carry. The auctioneer cannot compute those numbers itself, so it has to ask.
A language-model agent's confidence is a different kind of number. It is an opinion about itself, and the kernel can already estimate the same thing better from the agent's verified history: which tasks it took, and whether a checker accepted the result. Asking the agent adds a round trip and a process boot to get a worse answer.
None of the production agent frameworks we surveyed choose agents by market either.
What replaced it
The auction blurred three separate decisions together. We pulled them apart, and the kernel now makes each one from data it already holds, with no remote calls and no model calls:
- Eligibility. Each agent declares its capabilities in its manifest. The planner tags each step with the capabilities it needs. An agent is eligible only if it declares all of them.
- Ranking. Eligible agents are ranked by merit, a score built from their verified outcomes, latency and cost.
- Recovery. When a checker rejects a result, the step should go to the next agent in the ranking, carrying the failure with it. This part is designed but not built yet.
Only the chosen agent's process starts. By default the kernel takes the top-ranked agent. Two refinements are built but switched off until we have the data to justify them: ranking by merit on the specific capability rather than overall, and, for a cheap step whose result will be checked anyway, trying the cheapest agent that clears a merit floor first.
| Auction | Capability dispatch | |
|---|---|---|
| Remote calls to choose an agent | 3–5 | 0 |
| Agent processes started to choose | up to 5 | only the one chosen |
| Where routing rules live | keyword tables in agent code | capabilities declared in manifests |
| Can be replayed and audited | reconstructed from events | yes, offline from stored data |
| Gets better with data | no | yes, from verified outcomes |
This moves all the judgement in routing to one place: the planner, which already runs a model and already sees the live list of capabilities. That has a cost I want to state plainly. Routing can now be no better than the planner's capability tags. If the planner tags a step vaguely, nothing downstream can recover. That makes the planner's output the most valuable thing for us to improve next.
No fuzzy matching
When the planner asks for a capability that no agent declares, the kernel tries, in order: a deterministic spelling normaliser, a reviewed alias list, and agents that declare themselves generalists. If all three fail, the step fails with an error that names the missing capability and lists the ones that exist.
We deliberately did not add a fallback that picks the nearest capability by embedding similarity. In embedding space, file-read sits right next to file-write, and read next to delete. Synonyms belong in a list a person has reviewed, not in a cosine distance.
The comparison that never ran
The plan in the design record was to measure first and delete second. We had built the harness: an orchestration benchmark of 120 tasks with gold routing labels, and counters for agent boots and selection time on both the auction and the new dispatcher. The record says outright that deleting the auction is the point of no return, because afterwards there is nothing left to compare against.
The deletion landed before the full comparison ran. That comparison can now never be run, and the design record lists it as not performed, not as passed.
So the decision rests on other things, and I would rather list them than imply a benchmark result we do not have:
- The structural argument, which needs no benchmark: the bids had no content to learn from, and asking for them cost process boots.
- Our survey of how production frameworks select agents.
- A small, weak signal: on an early run, the bids we did observe had zero spread, which is what the keyword tables predicted. But that run observed only five auctions, and 112 of its 120 tasks errored for unrelated reasons. That corroborates the argument. It does not prove it.
What replaces the comparison is an absolute baseline: the same 120-task benchmark run on the dispatcher alone, which every later routing change has to beat or explain. We have not published a number from it yet, and we will not until the run is frozen and reproducible.
What running it live taught us
Before deletion we ran the new dispatcher on a three-task live probe. The unit tests were all passing. The probe found three real defects anyway:
- Fleet coverage is not eligibility. The code checked each required capability against the union of every agent's capabilities. A step that the fleet covered together, but no single agent covered alone, passed that check and then matched nobody.
- Spelling. One layer normalised capability names and the next compared them verbatim, so a tag that differed only in spelling was accepted and then rejected.
- A fuzzy gate overruled a hard one. A semantic-similarity filter could remove the only agent the capability check had approved, killing the step.
All three were fixed before the auction was removed. The lesson I took is the same one this whole change taught: a test that passes against the code you meant to write is not a test of the system you are running.
What we gave up
The auction had one channel the dispatcher lacks: an agent could report its own expected latency. In practice that number was a constant someone had typed in, so nothing real was lost, but the channel is gone. If measurement ever shows live load matters, we will add a declarative quote that involves no model call.
We also gave up a story. "A market of agents" is a good line, and until this week our own website still described agents bidding for work. The accurate version is less catchy: agents build a track record through verified outcomes, and the kernel routes on that record. I prefer the accurate one.
Sources
- ADR-0100: Capability-typed dispatch, including the 2026-08-07 amendment recording the comparison as not performed, the live probe, and the vocabulary review.
- The dispatcher today in Cambrian Core, including the per-step selection policy.
- ADR-0067: Capability vocabulary canonicalisation, which rejected embedding-based capability merging.
- ADR-0069: Per-capability merit.
- Design records are linked at Core commit
1fdad89, the last commit where they sit in the repository tree.