How to build a cheap autonomous coding agent with kRouter
A small TypeScript coding agent on kRouter's OpenAI-compatible endpoint, with free and cheap models behind one combo name, and what kRouter handles for it.
Cline, Cursor, and Aider are great. They are also overkill if you just want a script that reads a file, calls an LLM, writes the result, and commits.
You can build a usable coding agent in about 50 lines of TypeScript, point it at kRouter, and run it mostly on free and cheap models.
The skeleton
import OpenAI from "openai";
import { readFile, writeFile } from "fs/promises";
import { execFileSync } from "child_process";
const client = new OpenAI({
baseURL: "http://localhost:20128/v1",
// Any value works from the same machine; from elsewhere,
// use a key from the dashboard's Endpoint page.
apiKey: process.env.KROUTER_KEY ?? "local",
});
async function agent(file: string, instruction: string) {
const original = await readFile(file, "utf-8");
const response = await client.chat.completions.create({
model: "cheap-agent", // a combo name, see below
messages: [
{
role: "system",
content:
"You are a code editor. Return ONLY the complete new file content. No prose, no markdown fences.",
},
{
role: "user",
content: `File: ${file}\n\n${original}\n\nInstruction: ${instruction}`,
},
],
});
const updated = response.choices[0].message.content!;
await writeFile(file, updated);
// execFileSync passes arguments directly, so quotes in the
// instruction cannot break (or inject into) a shell command.
execFileSync("git", ["add", file]);
execFileSync("git", ["commit", "-m", `agent: ${instruction}`]);
console.log(`Updated ${file}`);
}
agent(process.argv[2], process.argv[3]);That is it. Save as agent.ts. Run it:
npx tsx agent.ts src/utils.ts "add JSDoc to every exported function"The model is cheap-agent, which is not a model at all: it is a combo, a named list of models you create on the dashboard's Combos page. A name without a provider/ prefix is looked up as a combo (or a model alias), so the script never needs to know which provider answered.
Why this works
kRouter handles the parts that would otherwise be most of the code:
- Auth. The script uses one endpoint and, from the same machine, no real key. kRouter keeps each provider's credentials: OAuth token refresh for Kiro or Copilot, API keys for metered providers.
- Fallback. When an entry is rate limited, kRouter cools that account down, tries the provider's other accounts, then moves to the combo's next entry. Your script sees an error only when every entry has failed, or when the error is one that every model would give (a request that is too long, for example).
- Format translation. Your script speaks OpenAI Chat Completions. kRouter translates to whatever the provider behind each entry speaks.
One thing it does not do for this first version: RTK, kRouter's default-on token saver, compresses tool results only. This script sends the file in a user message, so RTK leaves it alone. It starts to matter in the agentic version below, where file reads come back as tool messages.
Making it agentic
Add a loop and a tool definition:
const tools = [
{
type: "function",
function: {
name: "read_file",
description: "Read a file from the workspace",
parameters: {
type: "object",
properties: { path: { type: "string" } },
required: ["path"],
},
},
},
{
type: "function",
function: {
name: "write_file",
description: "Write a file to the workspace",
parameters: {
type: "object",
properties: {
path: { type: "string" },
content: { type: "string" },
},
required: ["path", "content"],
},
},
},
];
const messages = [{ role: "user", content: "Add a logout button to the navbar" }];
while (true) {
const res = await client.chat.completions.create({
model: "cheap-agent",
tools,
messages,
});
const msg = res.choices[0].message;
messages.push(msg);
if (!msg.tool_calls?.length) break;
for (const call of msg.tool_calls) {
const args = JSON.parse(call.function.arguments);
let result = "";
if (call.function.name === "read_file") {
result = await readFile(args.path, "utf-8");
} else if (call.function.name === "write_file") {
await writeFile(args.path, args.content);
result = "written";
}
messages.push({
role: "tool",
tool_call_id: call.id,
content: result,
});
}
}That is a working agentic loop, about 80 lines in total. Before you leave it running unattended, restrict write_file to your project directory: as written, it writes anywhere the process can.
The combo behind it
A combo for a background agent might look like this:
Combo "cheap-agent":
1. kr/claude-sonnet-4.5 # Kiro free plan, 50 credits a month
2. gh/claude-haiku-4.5 # Copilot credits, if you already pay for Copilot
3. ds/deepseek-v4-flash # DeepSeek API, meteredWhat a month costs depends on how many tokens the agent moves, so no fixed figure is honest here. The shape is what matters: free and already-paid entries first, a metered one last. Kiro's free plan runs out quickly under a daily agent, and when Kiro reports its monthly limit kRouter locks that account for about 30 days and the combo moves on. Copilot entries spend credits at the model's API rate. Everything that spills past them is billed by the metered provider; DeepSeek lists its Flash model at $0.15 to $0.30 per million input tokens and $0.60 to $1.20 per million output, with off-peak hours at half the peak rate (October 2026).
What kRouter adds for DIY agents
Most "build your own agent" tutorials skip the hard parts: auth rotation, rate-limit retries, format translation, and token cost control. They assume you have one API key and unlimited budget.
- Credential lifecycle. Kiro and Copilot use OAuth with refresh; DeepSeek and GLM use API keys. Your agent does not care -- it sends one request to one endpoint, and kRouter handles the credentials behind it.
- Backoff instead of hammering. A 429 puts the account on an exponential cooldown, from 2 seconds up to 5 minutes, or until the reset time the provider names. Among several accounts of one provider, the Zenith routing engine picks the healthiest. Your loop still has to handle the final error when everything is exhausted.
- RTK compression on tool outputs. Agentic loops are token-heavy because every round trip resends the conversation. RTK trims tool results such as command output and file reads before they are sent. The project's README claims 20-40% fewer input tokens per request; treat that as a claim, not a measurement of your workload.
- Format translation. Your agent speaks OpenAI format. kRouter translates to Anthropic Messages, the Responses API, Gemini, Vertex, Kiro, Antigravity, Cursor and Ollama formats. One agent, many providers.
Scaling up: multi-file agent with streaming
The skeleton above works for single-file edits. For agents that touch multiple files, add streaming so you see progress in real time:
const stream = await client.chat.completions.create({
model: "cheap-agent",
tools,
messages,
stream: true,
});
for await (const chunk of stream) {
const delta = chunk.choices[0]?.delta;
if (delta?.content) process.stdout.write(delta.content);
if (delta?.tool_calls) {
// Handle streaming tool calls
}
}kRouter handles the stream translation regardless of which provider serves the request: Kiro's event stream, Gemini's chunked JSON and Anthropic's SSE all reach your script as OpenAI-compatible data: {...} lines.
What you give up
Cline and Cursor give you UI, diff review, command approval, and dozens of refined details. A 50-line agent gives you raw power but you babysit it.
For background automation (nightly code cleanups, weekly dependency upgrades, CI-driven refactors), the 50-line agent is great.
For interactive coding, use Cline through kRouter. Same backend, better UX.
The real advantage
Hosted agent platforms add orchestration, memory and approval flows on top of the same model calls you are making here. For a nightly cleanup job you may not need any of that, and then the cost is just the tokens.
With kRouter, the expensive-to-build part -- multi-provider routing, token compression and fallback -- is already there and free to run. The orchestration layer is your 50-80 lines of TypeScript. The total cost is whatever the entries in your combo charge for the tokens that reach them.
npm install -g @sifxprime/krouter
krouter -tThe full provider catalog is at /providers, and the request formats kRouter accepts are in the API reference.
Common questions
Do I need an API key to call kRouter from my agent?
Not from the same machine: kRouter exempts local callers unless you switch on Require API key on the Endpoint page or set REQUIRE_API_KEY=true. An agent on another machine always needs a key, sent as Authorization: Bearer <key>.
Why did my agent get an error instead of falling back?
Either every entry in the combo failed, or the error was one every model would give, such as a request that is too long. kRouter does not fail over on those, because the next model would reject the same request.
Does RTK compress the file my agent sends?
Only when it arrives as a tool result. A file pasted into a user message, as in the first skeleton, is sent as-is.
Related
Published by Kodelyth, the team that builds kRouter. Posts are drafted with AI assistance and reviewed by a person before they go out. kRouter is free and MIT licensed.
Install kRouterRelated posts
- How kRouter worksXcode with any model: chat, Claude Agent and CodexXcode 26 and 27 can run chat, Claude Agent and Codex on other models, but each reads its own config. How to point all three at one local router.
- How kRouter workskRouter Agent Skills: one link teaches an agent an endpointkRouter ships 8 Agent Skills: Markdown files an AI agent reads to call chat, TTS, STT, images, embeddings, web search and fetch on your own router.
- How kRouter worksClaude Cowork with other models: third-party inference setupClaude Desktop can run Cowork through any Messages API gateway. Setting it up with kRouter, what the app assumes about your models, and what you give up.
Relevant docs
- Core conceptsProviders, combos, account routing, token savers, MITM mode, quota tracking, the response cache and remote access: the eight ideas behind kRouter.
- The Zenith Routing EngineHow kRouter picks which of your accounts serves a request: Zenith scoring, conversation stickiness, cooldowns, and the round-robin, P2C and random options.
- Token savers: RTK, Caveman, Ponytail, Headroom, PXPIPEFive ways kRouter can shrink a request before it reaches the provider. Only RTK is on by default. What each saver does, what it needs, and its limits.