Research · The cheaper-agents series · Part 3 of 3
Stop paying frontier-model prices for every agent step
How to route repetitive coding loops through a cheaper model while reserving a frontier model for the decisions that justify its cost.

The expensive part of an AI agent is not always the difficult part.
Give an autonomous coding agent a task and it may call a language model dozens of times: reading files, proposing edits, interpreting errors, rewriting functions and checking its own work. Only a handful of those calls may require the strongest model available. Yet many systems send every step to the same premium model.
That is convenient. It is also an expensive way to build software.
A better architecture separates routine iteration from high-value judgement. A cheaper model handles the repetitive loop. It reads the immediate context, proposes the next change and responds to test failures. A frontier model enters only at important control points: planning the task, reviewing architecture, resolving an impasse or approving the final result.
We use DeepSeek for the high-volume work and Claude for the decisions where additional intelligence is worth paying for. The objective is not to pretend that the models are interchangeable. They are not. It is to stop buying premium reasoning for work that does not require it.
Routing proprietary source code or client data to a third-party model — DeepSeek included — is a data-governance decision, not a cost decision. Check your retention, regional-processing and contractual obligations first. A cheaper endpoint is not automatically an acceptable endpoint. We return to this at the end — but it gates everything below.
The integration is surprisingly small because DeepSeek exposes an Anthropic-compatible endpoint. You can therefore talk to it with the same SDK you already use for Claude — the provider changes through client configuration rather than by rewriting the surrounding application.
What follows is a minimal routing pattern. It is not a complete replacement for Claude Code or another autonomous development environment. A production agent must also be able to edit files, run commands, collect errors, manage its context and decide when to stop. But this demonstrates the architectural change that matters.
The agents below are ones you build directly against the DeepSeek and Anthropic APIs. They are not Claude Code's built-in subagents.
If you only need Claude models, you do not need any of this code: Claude Code already routes by value natively — a strong model on the main loop, plus subagents pinned to a cheaper one with a single model: line. That is Part 1 of this series. The article you are reading is the cross-provider version — for when your high-volume worker is not a Claude model at all.
The wrong way to think about model choice
Most early agent systems select one model and use it for everything. Every step — however mechanical — is billed at the frontier rate.
In practice, an agent may spend most of its time doing relatively mechanical work:
- applying an already-agreed plan;
- rewriting a small function;
- fixing syntax errors;
- interpreting a test failure;
- converting data between formats;
- checking whether a requested change has been made.
The difficult decisions are less frequent. They include choosing the architecture, identifying why repeated fixes are failing, spotting a security problem or deciding whether the result is safe to deploy. Those are the moments at which a stronger model may justify its price.
The better structure routes the work by value:
The savings do not come merely from substituting one model for another. They come from reducing how often the expensive model sees the task and how much context it must repeatedly process.
Step 1 — Install the clients
This example uses Anthropic's SDK for both providers. DeepSeek exposes an Anthropic-compatible endpoint, so a single client library covers the cheap worker and the frontier reviewer alike.
pip install anthropic
Store API keys as environment variables rather than in source code:
export DEEPSEEK_API_KEY="your-deepseek-key"
export ANTHROPIC_API_KEY="your-anthropic-key"
For Windows PowerShell:
$env:DEEPSEEK_API_KEY="your-deepseek-key"
$env:ANTHROPIC_API_KEY="your-anthropic-key"
Step 2 — Create the two clients
DeepSeek can be reached through the Anthropic SDK by pointing the base URL at its Anthropic-compatible endpoint. Both clients are then the same type — only the credentials and endpoint differ.
import os
from anthropic import Anthropic
deepseek_client = Anthropic(
api_key=os.environ["DEEPSEEK_API_KEY"],
base_url="https://api.deepseek.com/anthropic",
)
claude_client = Anthropic(
api_key=os.environ["ANTHROPIC_API_KEY"],
)
The rest of your application does not need to know much about the provider. Model selection can sit behind a routing layer.
Model identifiers are renamed, deprecated and retired far more often than ordinary software dependencies. Never hard-code them. Read them from configuration, consult the provider's current model list, and test a migration before older versions are retired.
DEEPSEEK_MODEL = os.getenv("DEEPSEEK_MODEL", "deepseek-v4-flash") # cheap, fast worker
CLAUDE_MODEL = os.getenv("CLAUDE_MODEL", "claude-opus-4-8") # frontier reviewer
That makes future migrations considerably easier.
Step 3 — Give the worker a bounded loop
A common mistake is a recursive loop with no clear exit. The model keeps producing text, its previous answers are appended to the next prompt, and the context grows until cost or quality becomes hard to control.
A better loop has four properties:
- a maximum number of iterations;
- a clearly defined completion signal;
- bounded working context;
- an escalation path when progress stalls.
from dataclasses import dataclass
from typing import Optional
@dataclass
class AgentResult:
output: str
completed: bool
iterations: int
needs_escalation: bool
error: Optional[str] = None
def run_deepseek_worker(
task: str,
feedback: str = "",
max_iterations: int = 4,
) -> AgentResult:
"""
Use DeepSeek for bounded, iterative implementation work.
This produces proposed work. It does not itself edit files, execute
code or run tests — those capabilities belong in a separate tool
layer in a production agent.
"""
system_prompt = """
You are the implementation worker in a software-development system.
Complete the requested task as directly as possible.
Rules:
- Do not redesign the project unless the task requires it.
- Preserve existing behaviour unless explicitly asked to change it.
- Return complete replacement code where code is requested.
- Identify assumptions and unresolved risks.
- Do not claim code has been executed unless execution results were
supplied to you.
- End with exactly one status marker:
STATUS: COMPLETE
STATUS: RETRY
STATUS: ESCALATE
""".strip()
working_context = f"""
TASK:
{task}
AVAILABLE FEEDBACK:
{feedback or "No external feedback has been supplied."}
""".strip()
latest_output = ""
for iteration in range(1, max_iterations + 1):
response = deepseek_client.messages.create(
model=DEEPSEEK_MODEL,
max_tokens=4_000,
system=system_prompt,
messages=[
{"role": "user", "content": working_context},
],
)
latest_output = "\n".join(
block.text for block in response.content
if getattr(block, "type", None) == "text"
).strip()
if "STATUS: COMPLETE" in latest_output:
return AgentResult(latest_output, True, iteration, False)
if "STATUS: ESCALATE" in latest_output:
return AgentResult(latest_output, False, iteration, True)
# Keep only the latest proposal rather than appending
# every previous response indefinitely.
working_context = f"""
ORIGINAL TASK:
{task}
LATEST PROPOSAL:
{latest_output}
Review the latest proposal, correct any remaining problems and return a
consolidated answer. Do not repeat discarded alternatives.
""".strip()
return AgentResult(
latest_output, False, max_iterations, True,
error="Maximum worker iterations reached.",
)
This is still only a model loop. It becomes genuinely agentic when the worker is connected to tools. A coding agent might:
- ask the worker to propose an edit;
- write that edit into a temporary workspace;
- run the relevant tests;
- capture the error output;
- send only the relevant failure back to the worker;
- repeat until the tests pass or the iteration limit is reached.
The model should not be asked to imagine whether its code works. The surrounding system should execute the code and return objective evidence.
Step 4 — Reserve Claude for review and escalation
Once DeepSeek has produced a candidate, Claude reviews it.
def review_with_claude(
task: str,
worker_output: str,
execution_feedback: str = "",
) -> str:
"""Use Claude for architectural review, risk detection and final polish."""
prompt = f"""
You are the senior reviewer in a software-development workflow.
ORIGINAL TASK:
{task}
WORKER OUTPUT:
{worker_output}
EXECUTION OR TEST FEEDBACK:
{execution_feedback or "No execution feedback was supplied."}
Review the proposed result. Check specifically for:
- logical errors;
- failure to satisfy the original task;
- unsafe assumptions;
- security or data-loss risks;
- incomplete error handling;
- fabricated claims about execution or testing;
- unnecessary complexity.
Return a corrected, consolidated final answer. Where code is required,
return complete code rather than isolated fragments. Be explicit when the
available evidence is insufficient to confirm that the code works.
""".strip()
response = claude_client.messages.create(
model=CLAUDE_MODEL,
max_tokens=4_000,
messages=[{"role": "user", "content": prompt}],
)
return "\n".join(
block.text for block in response.content
if getattr(block, "type", None) == "text"
).strip()
The reviewer is not there to improve the prose. It should challenge the implementation. Asking a strong model to "polish" an answer can make an incorrect result sound more convincing. A proper review prompt asks it to search for failures, compare the result against the original requirement, and admit when execution evidence is missing.
Step 5 — Put the router together
def run_routed_task(
task: str,
worker_feedback: str = "",
execution_feedback: str = "",
) -> str:
"""Run a task through the DeepSeek worker and the Claude reviewer."""
worker_result = run_deepseek_worker(task, feedback=worker_feedback, max_iterations=4)
review_context = worker_result.output
if worker_result.error:
review_context += f"\n\nROUTER NOTE:\n{worker_result.error}"
if worker_result.needs_escalation:
review_context += (
"\n\nROUTER NOTE:\nThe worker did not reach a reliable "
"completion state. Diagnose the blocker rather than assuming "
"its latest proposal is correct."
)
return review_with_claude(task, worker_output=review_context,
execution_feedback=execution_feedback)
if __name__ == "__main__":
task = """
Create a Python function that reads a CSV file of transaction dates and
values, validates the input, and returns monthly totals. Include type
hints and useful error messages.
""".strip()
print(run_routed_task(task))
Where the largest savings actually come from
Using a cheaper worker helps, but model substitution is only part of the answer. The larger opportunity is usually context control.
A poorly designed agent repeatedly sends the full conversation, the entire repository, every previous model response, complete test logs, and files unrelated to the current change. That makes even a cheap model expensive — and it reduces quality by burying the relevant information inside a growing prompt.
A better agent constructs a fresh context for each step. It retrieves only the files and history relevant to the immediate decision, summarises obsolete work, discards failed alternatives, and keeps persistent state outside the model's context window.
The unit of optimisation is therefore not the model price per token. It is:
model price × tokens supplied × number of calls
A frontier model used twice with precise context may cost less than a cheaper model called forty times with an undisciplined prompt. Routing and context management must be designed together.
When to escalate
The frontier model should not be invoked merely after an arbitrary number of turns. It should be called when the system detects that the task has crossed a decision boundary. Useful triggers include:
- the same test fails repeatedly;
- successive patches reverse earlier changes;
- the worker requests an architectural decision;
- authentication, permissions or cryptography are involved;
- a database migration could destroy data;
- the task affects payments or production infrastructure;
- the worker's confidence falls below a threshold;
- the final diff is much larger than expected;
- the proposed solution changes public interfaces;
- the worker cannot reconcile conflicting requirements.
This is the difference between a router and a simple cascade. A cascade always calls model B after model A. A router decides whether model B is necessary. The objective is not to use the frontier model once per job — it is to use it only when it adds enough value to justify the call.
The practical gotchas
The API is stateless
The provider does not retain your working state between ordinary calls. Your application must supply the context for each request — but do not read that as "append everything forever." Store the full history externally, then construct the smallest useful context for the next decision.
Model names change
The identifier in a tutorial may be obsolete by the time someone copies it. Keep model names in configuration and test migrations before older versions retire.
Rate limits and overloaded services are normal
Agent loops magnify transient API failures. Add timeouts, bounded retries, exponential backoff and jitter. Do not retry invalid requests indefinitely, and do not let several hundred agents enter synchronised retry loops.
Cheap iterations can still become expensive
A low token price does not excuse unbounded recursion. Set limits for calls, tokens, elapsed time and total task cost.
Tests are more valuable than self-reflection
A model reviewing its own answer is useful, but it is not independent verification. Wherever possible, connect the loop to tests, linters, type checkers, schema validators and sandboxed execution.
Sensitive code requires a data-governance decision
Before routing proprietary source code or customer data to another provider, check the relevant security, retention, regional-processing and contractual requirements. A cheaper endpoint is not automatically an acceptable endpoint. For us, this is the gate that decides whether a workload is eligible for routing at all.
"Muscle and brains" is the wrong metaphor
It is tempting to call the cheaper model the "muscle" and the premium model the "brains." That is catchy but misleading. The worker still needs intelligence; the reviewer still performs routine work. The real distinction is between high-volume execution and high-value intervention.
A well-designed agent allocates intelligence dynamically. It spends heavily where mistakes are costly or judgement is scarce, and less where the task is repetitive, testable and reversible.
The future of agent economics will not be settled by whichever provider offers the cheapest token or the highest benchmark score. It will be settled by how effectively developers match each unit of work to the cheapest model capable of completing it reliably.
Do that well, and a cheaper model does not need to replace a frontier one. It simply prevents the frontier model from being used where it was never needed.
The cheaper-agents series: Part 1 — Cheaper models inside Claude Code · Part 2 — Why splitting the work saves money · Part 3 — Roll your own, across providers (this article)
First published on x-trillion.com. A version also appears on Medium.