Charlie Tolleson
← Work

Orchestrating a Team of Models

Most agent systems pick one model and route everything through it.

That works until the graph gets deep, and then the cost shows up everywhere at once: in latency, in spend, and in how much of the system anyone can actually verify. Working on agent evals at Meta taught me you can only trust what you can see, so I built Hyperion around that idea. It treats model choice as a property of each step, and traces every call to measure what that buys.

1ChallengesOne prompt fans out into dozens of nondeterministic model calls, each compounding the variance of the one before it.
1What I didEvery task planned as an inspectable graph, so any node can be opened, edited, and re-run on its own.
2ChallengesWhen every step runs through one model behind one interface, a bad answer can't be traced to the step that caused it.
2What I didEvery call traced end to end, with per-node timings, token counts, and failure rates I can audit rather than guess at.
3ChallengesRouting every step through one model pays premium latency and cost for work that never needed it.
3What I didModel choice treated as a property of each step, with an ordered fallback chain behind every role.
4ChallengesA pipeline stalls on its slowest step even when most of the work is independent.
4What I didA graph executed in parallel waves, so independent steps run side by side instead of waiting their turn.
Role
Creator & architect: design, build, operations
Project
Hyperion, the orchestration layer of my personal AI workspace
Stack
Python · FastAPI · LiteLLM · Qdrant · Langfuse · Next.js
Background
Led evals at Meta across 7 AI workflows built for thousands of sellers

Why I built it

At Meta I worked on AI agents from the measurement side, setting the bar seven workflows had to clear to launch. The lesson that stuck was simple: you can only evaluate what you can see, and you can only trust what you can evaluate.

A single prompt to a modern agent doesn't trigger one model call. It triggers several, sometimes dozens, of sub-agents working together, and because LLMs are nondeterministic, the variance compounds as each step builds on an already-uncertain upstream output. When all of that runs through one model behind one interface, a bad answer is hard to trace to the step that caused it, and every step pays premium rates for work that often didn't need them.

This is a big reason organizations still don't trust AI for high-stakes work like client-facing documents or reported metrics.

Hyperion is my answer, built with my hands on the whole stack: every step is a node you can open, routed to the model that fits it, and traced end to end.

Workflows you can open up

Point Hyperion at a prompt and it drafts a workflow on its own. But a workflow is never a black box: I can open any of them in a graphical builder, rewire which nodes feed which, swap the model or persona behind a node, and save the result as a reusable template.

Below is the real shape of one: five research angles running in parallel, converging into a synthesis step, splitting into specialist review passes, and resolving into a single verdict. Hover or focus a node to see its persona and the model it ran on.

Market Sizing: Market Analyst · gpt-4oresearcherworkMarket SizingDomain Expert: Domain Expert · gpt-4oresearcherworkDomain ExpertLegal Expert: Legal Analyst · gpt-4oresearcherworkLegal ExpertMarket Trends: Trend Researcher · gpt-4oresearcherworkMarket TrendsCompetitive Landscape: Competitive Analyst · gpt-4oresearcherworkCompetitive LandscapeResearch Synthesis: Synthesizer · gemini-2.5-prosynthesizersynthesizeResearch SynthesisCritic: Critic · gpt-4ocriticworkCriticAdvocate: Advocate · gemini-2.5-prosynthesizerworkAdvocatePlanner: Planner · smartplannerplanPlannerDeveloper: Technical Lead · workerdeveloperplanDeveloperAssessor: Assessor · gpt-4ocriticsynthesizeAssessorVerdict: Verdict Writer · gemini-2.5-prosynthesizersynthesizeVerdict

scroll the diagram sideways →

plan
work
synthesize
The Idea Council workflow open in Hyperion's builder. Twelve nodes are laid out on a canvas connected by dependency arrows, with the advocate node selected and a side panel showing its slug, role, agent, approval gate, and instruction override.
The same workflow in the builder, with the advocate node selected. Nodes are dragged into place and wired by dragging between handles (an edge means the source must finish first), and the panel edits that node's role, agent, approval gate, and instruction. The editor rejects any connection that would close a cycle, so a workflow always stays a DAG the runner can schedule.

A team of specialists

Every node is a persona, not just a model call. The market-sizing node reasons like a market analyst. The critic argues like someone whose only job is finding the hole in the plan. The assessor weighs critique against advocacy like a partner deciding whether to greenlight a deal.

Stacked together, a workflow behaves less like a single assistant and more like a small, focused team: each member narrowly scoped, briefed with just enough context to do its job, and reporting up to whoever synthesizes the final call.

Hyperion's dashboard: a task input at the top with a workflow picker, above a grid of agent cards. Each card shows the agent's role, description, model badge, and tool count, and is marked active or inactive.
The agent registry. Each card carries the model that role runs on (the researcher on gpt-4o, the synthesizer on gemini-2.5-pro, the planner and developer on the smart and worker aliases) plus how many tools it can reach. Agents can be switched off without editing any workflow that references them.

Routing each step to the model that fits

Research that fans out into five parallel angles rewards breadth over depth, so those nodes run on gpt-4o with a capped search-and-reason loop. The steps that have to hold a lot of context at once and produce long prose (research synthesis, the advocate, the final verdict) run on gemini-2.5-pro.

The planner and developer nodes don't name a model at all. They point at smart and worker, logical roles chosen by intent rather than by vendor. Each role resolves to an alias, and an alias is a pool of interchangeable models spanning providers: smart holds claude-opus-4-6, gemini-2.5-pro, and gpt-4o. The proxy shuffles across whichever members are healthy, retries the others when one fails, and falls through to a different pool only if all of them do.

That indirection is what makes the routing claim more than a preference. Changing a pool's membership re-routes every node pointed at it, across every workflow, without touching code, and a provider outage degrades a run instead of ending it: the request lands on another member and keeps going.

The run below is that fallback caught in the act. No Anthropic key was configured, so both alias nodes were served by whichever of the remaining members answered. The trace store records the target a call asked for and not the provider that served it, which is why those two bars read smart and worker rather than a vendor model. Naming one would be a guess pretending to be a measurement.

Hyperion's alias settings. Each alias (smart, worker, cheap) lists the models behind it with controls to reorder, remove, or add entries.
The alias editor. smart pools claude-opus-4-6, gemini-2.5-pro, and gpt-4o; worker and cheap have their own pools. Editing membership here re-routes every node pointed at that alias, across every workflow, without touching a line of code.

Here is what that mix did on the clock across all 4 routing targets in a single run. Bars are colored by target.

Competitive Landscapegpt-4oCompetitive Landscape: gpt-4o, 86.0s, 3 calls, 9,586 tokensDomain Expertgpt-4oDomain Expert: gpt-4o, 101.0s, 2 calls, 7,216 tokensLegal Expertgpt-4oLegal Expert: gpt-4o, 102.0s, 2 calls, 6,952 tokensMarket Sizinggpt-4oMarket Sizing: gpt-4o, 96.9s, 2 calls, 7,472 tokensMarket Trendsgpt-4oMarket Trends: gpt-4o, 135.6s, 2 calls, 8,274 tokensResearch Synthesisgemini-2.5-proResearch Synthesis: gemini-2.5-pro, 35.1s, 1 call, 8,562 tokensCriticgpt-4oCritic: gpt-4o, 5.8s, 1 call, 3,482 tokensAdvocategemini-2.5-proAdvocate: gemini-2.5-pro, 11.5s, 1 call, 4,481 tokensPlannersmartPlanner: smart, 33.7s, 1 call, 7,170 tokensDeveloperworkerDeveloper: worker, 51.7s, 1 call, 8,991 tokensAssessorgpt-4oAssessor: gpt-4o, 4.8s, 1 call, 2,471 tokensVerdictgemini-2.5-proVerdict: gemini-2.5-pro, 32.6s, 1 call, 10,651 tokens0s1:002:003:004:004:20elapsed

scroll the chart sideways →

gpt-4oopenaigemini-2.5-progeminismartaliasworkeralias
One node at a time11:37
As a graph4:20

Same nodes, same models. 2.7× faster because independent work runs at the same time.

Run 47349fd3, recorded 2026-06-23. Timings are measured from Hyperion's trace store, one row per LLM call; each bar is labeled with the routing target that call requested. Two nodes name a role alias rather than a model, and the store does not record which member of the pool served them. Research nodes span more than one call because they run a capped search-and-reason loop, so their bars include tool time between calls.

Why a graph, not a pipeline

A fixed linear pipeline forces every step through the same path in the same order. Building the orchestrator around an arbitrary directed graph instead means a workflow's shape can match the task: the runner groups nodes into execution waves, fires each wave concurrently, and only advances once every node in it has finished, so independent work runs side by side instead of waiting its turn.

The flexibility has real costs. A graph is harder to reason about than a straight line. Mixing models means each node carries a slightly different voice and judgment style that the synthesis step has to reconcile. And more parallelism means more surface area for one slow node to become the bottleneck everyone waits on. In the run above, the whole first wave waits on market-trends at 135.6s while four sibling nodes sit finished and idle.

What happens when a node misbehaves

Fanning work out to a dozen model calls means a dozen things that can hang, loop, or quietly burn budget, and runs here do fail. The per-agent rates in the monitoring view below are coarser than they look, because a failure is charged to every agent the run selected rather than to the one that caused it, so they rank exposure rather than blame. The three mechanisms here are what keep a misbehaving node from taking the run's budget with it, and each was added because something actually went wrong first.

  • A per-request timeout, not just a wall-clock budget. An overall deadline can't interrupt a worker thread already blocked inside a completion call. Each node gets a hard timeout of min(remaining budget, 180s) handed to LiteLLM itself, the only knob that can actually kill a stalled upstream from inside the executor.
  • Per-agent token caps checked before the call, not after. A researcher stuck in a search loop is stopped by a pre-flight check against its accumulated input and output tokens, so a runaway node fails fast instead of finishing expensive.
  • One fallback retry per node. When a primary completion raises, the node retries once against its fallback target. Combined with the alias groups, a provider outage degrades a run instead of ending it.

Every call (model, tokens, cost, duration, and the node it belongs to) is written to a trace store and grouped into one Langfuse session per run. That store is where every number on this page came from.

Hyperion's monitoring view: a run tally at the top, per-agent cards showing run counts, error counts, error rate and token usage against caps, and a table of recent runs with status and a link to each trace.
The monitoring view, reporting against itself: run tallies, per-agent error rates, and token usage against each agent's cap. Every row links out to the full Langfuse trace. This store, and the per-node rows behind it, is where run 47349fd3 and every other number on this page came from.

What's next

Deciding which model belongs on which node is still mostly judgment. I have cost, latency, and token counts per node; I don't yet have quality per node. The next iteration closes that loop by running evals against each node's output to score models against the specific job that node does, so the routing table becomes a measured argument instead of a defensible guess.

Which lands back on the problem this started with: making sub-agent behavior legible enough that someone could trust the output.

The code

Happy to talk about any of this.LinkedInMore work