The signal
GitHub reportedly had its “best month ever” in June because demand for AI coding kept growing after Copilot moved to usage-based billing. Business Insider also reported that increased usage has contributed to major outages in 2026 and capacity pressure.
GitHub’s own billing announcement explains the underlying shift: Copilot moved to AI Credits on June 1, and usage is calculated from token consumption, including input, output, and cached tokens. GitHub also described Copilot as evolving into an agentic platform capable of long, multi-step coding sessions across repositories.
That matters for engineering.
A long, multi-step coding session is not a chat message.
It is a loop.
And loops need runtime budgets.
Why this is not just a pricing issue
Usage-based billing makes one thing very clear:
runtime behavior has financial consequences.
A coding agent can spend because it is useful.
It can also spend because it is stuck.
The failure mode usually does not look dramatic.
It looks like this:
inspect files
call a model
edit code
run tests
fail
add context
call the model again
retry with a similar prompt
switch strategy
call another model
run tests again
keep going
Every step can look reasonable.
The whole run can still be waste.
That is why a billing dashboard is not enough.
A dashboard tells you what happened after usage exists.
A runtime budget decides whether the next call should happen.
A naive agent loop
A simple coding agent loop might look like this:
while (!task.done) {
const response = await provider.call({
model: task.model,
messages: task.messages,
});
task = await applyAgentStep(task, response);
}
This is easy to understand.
It is also dangerous.
There is no budget.
No max-step limit.
No retry-storm detection.
No prompt-loop detection.
No known-pricing check.
No no-progress detection.
If this loop gets stuck, it keeps spending until something else stops it.
That “something else” might be a provider limit, a user interruption, an admin cap, or the bill.
None of those are ideal runtime controls.
Add a pre-call decision
A safer pattern puts a guard before the provider call:
const decision = guard.beforeCall({
runId: task.id,
model: task.model,
messages: task.messages,
stepCount: task.steps.length,
retryCount: task.retryCount,
budgetRemaining: task.budgetRemaining,
previousPrompts: task.previousPrompts,
progressState: task.progress,
});
if (!decision.allowed) {
return {
status: "stopped",
reason: decision.reason,
error: decision.error,
};
}
const response = await provider.call({
model: task.model,
messages: task.messages,
});
The exact API does not matter.
The placement matters.
The check happens before the provider call.
That means the runtime can stop unsafe execution before token usage is created.
What should the runtime check?
- Known model pricing
If the runtime does not know the model price, it cannot enforce a reliable budget.
Do not guess.
Fail closed.
if (!pricingCatalog.has(model)) {
throw new Error(Unknown pricing for model: ${model});
}
In usage-based billing, model identity is part of the cost contract.
A typo, alias, fallback, or wrapper mismatch can break assumptions.
- Task-level budget
Monthly limits are useful.
But agent runs also need task-level budgets.
A code review task should not have the same spend ceiling as a multi-hour migration.
if (estimatedNextCallCost > budgetRemaining) {
return {
allowed: false,
reason: "budget_exceeded",
};
}
This lets the agent stop before the next call.
Not after.
- Max-step protection
Agent loops need step limits.
if (stepCount >= maxSteps) {
return {
allowed: false,
reason: "max_steps_exceeded",
};
}
This is basic, but important.
An agent that cannot finish within a reasonable number of steps may be stuck.
- Retry-storm detection
Retries are useful.
Retry storms are not.
if (retryCount >= maxRetries && lastErrorsAreSimilar(task.errors)) {
return {
allowed: false,
reason: "retry_storm_detected",
};
}
The goal is not to remove retries.
The goal is to prevent blind retries.
- Prompt-loop detection
Agents sometimes send nearly the same prompt repeatedly.
Small wording changes can hide the fact that the run is not moving.
if (isSimilarToRecentPrompt(currentPrompt, previousPrompts)) {
return {
allowed: false,
reason: "similar_prompt_loop",
};
}
Prompt-loop detection is not perfect.
But even a simple version can catch obvious waste.
- No-progress detection
A run can be active and still useless.
The agent may be editing files, calling tools, and producing logs.
But the task may not be converging.
A runtime should track progress signals:
tests passing
errors decreasing
files changing meaningfully
plan steps completing
final answer getting closer
user-defined success criteria
If the run consumes steps without progress, stop.
Why admin caps are not enough
GitHub’s announcement says admins can set budgets at enterprise, cost center, and user levels, and decide whether to allow additional usage once included credits are exhausted.
That is useful.
But admin caps operate at a broad level.
They do not know why a single agent run is stuck.
They do not know whether a prompt is repeating.
They do not know whether this specific task is worth another call.
That decision belongs closer to the runtime.
Where AI CostGuard fits
AI CostGuard is the local-first TypeScript runtime layer I’m building for this problem.
It is designed to stop expensive agent failure modes before provider calls execute:
retry storms
prompt loops
max-step explosions
no-progress runs
budget overruns
unknown model pricing
runaway agent behavior
It is not a billing dashboard.
It is not a cloud control plane.
It is not a hard security boundary.
It is a pre-call kill switch for agent cost and loop failures.
The takeaway
Usage-based AI coding changes the engineering model.
You cannot only ask:
“How much does this tool cost per month?”
You have to ask:
“What does my agent do when it gets stuck?”
For agentic coding, cost control belongs in the runtime.
Before the provider call.
Before the bill.
Before the loop becomes waste.
https://github.com/salimassili62-afk/ai-costguard
Top comments (0)