<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Assili Salim</title>
    <description>The latest articles on DEV Community by Assili Salim (@assili_salim_e3c07f9954de).</description>
    <link>https://dev.to/assili_salim_e3c07f9954de</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3976311%2F67c79ee9-ba0f-4524-b372-e3e745e4dab4.png</url>
      <title>DEV Community: Assili Salim</title>
      <link>https://dev.to/assili_salim_e3c07f9954de</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/assili_salim_e3c07f9954de"/>
    <language>en</language>
    <item>
      <title>MCP 2026-07-28 shipped today. Here's what it means for agent cost tracking.</title>
      <dc:creator>Assili Salim</dc:creator>
      <pubDate>Wed, 29 Jul 2026 03:28:19 +0000</pubDate>
      <link>https://dev.to/assili_salim_e3c07f9954de/mcp-2026-07-28-shipped-today-heres-what-it-means-for-agent-cost-tracking-4455</link>
      <guid>https://dev.to/assili_salim_e3c07f9954de/mcp-2026-07-28-shipped-today-heres-what-it-means-for-agent-cost-tracking-4455</guid>
      <description>&lt;p&gt;The MCP 2026-07-28 specification went final today. &lt;br&gt;
The maintainers call it the largest revision since launch. &lt;br&gt;
Most coverage is focusing on the stateless core and the new Extensions framework.&lt;/p&gt;

&lt;p&gt;Two changes in this release affect how you track agent costs — and both push responsibility into your application layer.&lt;/p&gt;

&lt;p&gt;The session primitive is gone&lt;/p&gt;

&lt;p&gt;Mcp-Session-Id and the initialize/initialized handshake are removed. &lt;br&gt;
Every request is now self-describing. &lt;br&gt;
Correct call for scalability: MCP servers can run behind a plain round-robin load balancer, no sticky sessions, no shared session store.&lt;/p&gt;

&lt;p&gt;The side effect: there's no longer a session scope at the protocol level to group calls under.&lt;/p&gt;

&lt;p&gt;Before today, teams anchoring budget tracking to the session ID had a natural grouping unit:&lt;/p&gt;

&lt;p&gt;typescript&lt;br&gt;
// Before 2026-07-28: session ID was a protocol primitive&lt;br&gt;
const sessionId = request.headers['mcp-session-id'];&lt;br&gt;
const budget = await store.get(&lt;code&gt;budget:${sessionId}&lt;/code&gt;);&lt;/p&gt;

&lt;p&gt;That's gone. &lt;br&gt;
The spec's own guidance is direct: "If your server needs to carry state across calls, mint an explicit handle from a tool and have the model pass it back as an argument."&lt;/p&gt;

&lt;p&gt;Which means your budget scope is now something you define, not something the protocol gives you:&lt;/p&gt;

&lt;p&gt;typescript&lt;br&gt;
// After 2026-07-28: you own the scope&lt;br&gt;
const agentRunId = context.runId; // your construct, not the protocol's&lt;br&gt;
const budget = await store.get(&lt;code&gt;budget:${agentRunId}&lt;/code&gt;);&lt;/p&gt;

&lt;p&gt;This is a cleaner model. &lt;br&gt;
It's also opt-in work. &lt;br&gt;
If you weren't tracking session-scoped cost before, you definitely don't get it automatically now.&lt;/p&gt;

&lt;p&gt;Tasks shift cost accumulation async&lt;/p&gt;

&lt;p&gt;The Tasks extension is now first-class under io.modelcontextprotocol/tasks. &lt;br&gt;
A tools/call can return a task handle instead of a synchronous result. &lt;br&gt;
The client drives the task forward with tasks/get, tasks/update, and tasks/cancel.&lt;/p&gt;

&lt;p&gt;typescript&lt;br&gt;
const result = await mcp.callTool({ name: 'deep-research', params });&lt;/p&gt;

&lt;p&gt;if (result.type === 'task') {&lt;br&gt;
  // Pre-call budget check has already run and exited.&lt;br&gt;
  // LLM work is happening async from here.&lt;br&gt;
  let taskResult;&lt;br&gt;
  do {&lt;br&gt;
    taskResult = await mcp.tasks.get(result.taskId);&lt;br&gt;
  } while (taskResult.status !== 'complete');&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;The cost tracking problem: your pre-call guard ran before tools/call went out. &lt;br&gt;
It saw the call, checked the session budget, let it through. &lt;br&gt;
The server converted it to a Task. &lt;br&gt;
The actual LLM work — potentially many internal calls, potentially expensive models — runs async while your polling loop waits.&lt;/p&gt;

&lt;p&gt;The guard did its job. &lt;br&gt;
It just had no visibility into what happened inside the Task.&lt;/p&gt;

&lt;p&gt;Tasks are the right primitive for long-running agent work. &lt;br&gt;
But they move cost accumulation to a point that call-level-only guards can't reach.&lt;/p&gt;

&lt;p&gt;What to do about it&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Define session scope explicitly — don't assume the protocol gives it to you.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Use your agent run ID, a user session ID, or a handle the model threads between tool calls. &lt;br&gt;
Make the budget scope an explicit construct in your application:&lt;/p&gt;

&lt;p&gt;typescript&lt;br&gt;
const session = {&lt;br&gt;
  id: crypto.randomUUID(),&lt;br&gt;
  budgetCents: 50,&lt;br&gt;
  spentCents: 0,&lt;br&gt;
  reservedCents: 0,&lt;br&gt;
};&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Carry budget tracking through the Task polling lifecycle.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Check on each poll. &lt;br&gt;
If the session is over budget, cancel the task — tasks/cancel is part of the spec:&lt;/p&gt;

&lt;p&gt;typescript&lt;br&gt;
while (taskResult.status !== 'complete') {&lt;br&gt;
  await updateSpend(session, taskResult.progressTokens);&lt;/p&gt;

&lt;p&gt;if (session.spentCents + session.reservedCents &amp;gt;= session.budgetCents) {&lt;br&gt;
    await mcp.tasks.cancel(taskResult.taskId);&lt;br&gt;
    throw new BudgetExceededError(session.id);&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;taskResult = await mcp.tasks.get(taskResult.taskId);&lt;br&gt;
}&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Reserve budget at task start, not just at task check.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Estimate the maximum cost of the task before you let the tools/call through. Reserve that amount. &lt;br&gt;
If the reservation would exceed the session limit, block it before the task starts:&lt;/p&gt;

&lt;p&gt;typescript&lt;br&gt;
const estimatedMaxCost = estimateTaskCost(toolName, params);&lt;/p&gt;

&lt;p&gt;if (session.spentCents + estimatedMaxCost &amp;gt; session.budgetCents) {&lt;br&gt;
  throw new BudgetExceededError(&lt;code&gt;Task would exceed session budget&lt;/code&gt;);&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;session.reservedCents += estimatedMaxCost;&lt;br&gt;
// Now let the tools/call through&lt;/p&gt;

&lt;p&gt;Estimation will be imperfect. &lt;br&gt;
A reasonable ceiling is still better than no guard against a task that runs to completion at full cost.&lt;/p&gt;

&lt;p&gt;The broader pattern&lt;/p&gt;

&lt;p&gt;Supabase's note in the spec blog is worth reading: they're using the new MRTR (Multi Round-Trip Requests) to ask the user for confirmation before executing a tool that has a real cost — like creating a new project or running a destructive query. &lt;br&gt;
The tool pauses mid-execution and gets explicit sign-off.&lt;/p&gt;

&lt;p&gt;That's the right instinct. The same confirmation pattern applies to budget: a tool that's about to start an expensive Task should check whether the session can absorb the cost before it commits, not trust that the pre-call check was sufficient.&lt;/p&gt;

&lt;p&gt;This is the architectural pattern AI CostGuard is built around — session-scoped budget with a pre-call decision point, not just a per-call token counter. &lt;br&gt;
The MCP spec today made that distinction explicit at the protocol level by removing the session primitive and making async Task execution first-class at the same time.&lt;/p&gt;

&lt;p&gt;If you were relying on Mcp-Session-Id for budget scope, migrate now. &lt;br&gt;
If you weren't tracking session-level cost at all, this release is a good moment to start.&lt;br&gt;
&lt;a href="https://github.com/salimassili62-afk/ai-costguard" rel="noopener noreferrer"&gt;https://github.com/salimassili62-afk/ai-costguard&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>javascript</category>
      <category>api</category>
      <category>llm</category>
    </item>
    <item>
      <title>GhostApproval: Why Agent Permissions Need Resolved Runtime State</title>
      <dc:creator>Assili Salim</dc:creator>
      <pubDate>Mon, 13 Jul 2026 03:47:49 +0000</pubDate>
      <link>https://dev.to/assili_salim_e3c07f9954de/ghostapproval-why-agent-permissions-need-resolved-runtime-state-hlh</link>
      <guid>https://dev.to/assili_salim_e3c07f9954de/ghostapproval-why-agent-permissions-need-resolved-runtime-state-hlh</guid>
      <description>&lt;p&gt;A malicious repo makes an AI coding assistant appear to edit project_settings.json while the actual write lands on ~/.ssh/authorized_keys. The approval UI shows the harmless path. The filesystem follows the symlink.&lt;br&gt;
The bug isn't symlinks. It's this:&lt;br&gt;
An approval prompt is only strong if it shows the resolved operation, not the requested one.&lt;/p&gt;

&lt;p&gt;The approval showed a lie&lt;br&gt;
Common agent mistake: approve the visible request instead of what the runtime actually does.&lt;br&gt;
Visible request: Edit project_settings.json&lt;br&gt;
Resolved operation: Write to ~/.ssh/authorized_keys&lt;br&gt;
If the human sees only the first, approval is theater.&lt;/p&gt;

&lt;p&gt;Fix: resolve before you approve&lt;br&gt;
Before showing an approval prompt, the runtime should resolve the actual target:&lt;br&gt;
typescriptimport path from "node:path";&lt;br&gt;
import fs from "node:fs/promises";&lt;/p&gt;

&lt;p&gt;async function resolveWriteTarget(workspaceRoot: string, requestedPath: string) {&lt;br&gt;
  const workspace = await fs.realpath(workspaceRoot);&lt;br&gt;
  const absolute = path.resolve(workspace, requestedPath);&lt;br&gt;
  const parent = await fs.realpath(path.dirname(absolute));&lt;br&gt;
  const resolved = path.join(parent, path.basename(absolute));&lt;/p&gt;

&lt;p&gt;return {&lt;br&gt;
    requested: absolute,&lt;br&gt;
    resolved,&lt;br&gt;
    safe: resolved.startsWith(workspace + path.sep),&lt;br&gt;
  };&lt;br&gt;
}&lt;br&gt;
Don't approve the string. Approve the canonical target.&lt;br&gt;
Then add a gate:&lt;br&gt;
typescriptasync function beforeFileWrite(workspace, path, content) {&lt;br&gt;
  const target = await resolveWriteTarget(workspace, path);&lt;/p&gt;

&lt;p&gt;if (!target.safe) {&lt;br&gt;
    return { allowed: false, reason: "write_outside_workspace", target };&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;return { allowed: true, target };&lt;br&gt;
}&lt;br&gt;
The check happens before execution. Not after. Not in an audit log.&lt;/p&gt;

&lt;p&gt;Same pattern for provider calls&lt;br&gt;
GhostApproval is about files. The design applies to every agent operation.&lt;br&gt;
Before a provider call executes, resolve:&lt;/p&gt;

&lt;p&gt;Which model?&lt;br&gt;
Known price?&lt;br&gt;
Which run?&lt;br&gt;
Retry count?&lt;br&gt;
Step count?&lt;br&gt;
Prompt loop?&lt;br&gt;
Budget remaining?&lt;br&gt;
Making progress?&lt;/p&gt;

&lt;p&gt;Naive:&lt;br&gt;
typescriptconst result = await provider.call({ model, messages });&lt;br&gt;
Safer:&lt;br&gt;
typescriptconst decision = guard.beforeCall({&lt;br&gt;
  runId, model, messages, stepCount, retryCount, budgetRemaining, progressState,&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;if (!decision.allowed) return { status: "stopped", reason: decision.reason };&lt;/p&gt;

&lt;p&gt;const result = await provider.call({ model, messages });&lt;br&gt;
The key is placement. Decide before.&lt;/p&gt;

&lt;p&gt;Human approval alone isn't enough&lt;br&gt;
GhostApproval is a reminder that formal approval can be practically weak.&lt;br&gt;
If the human sees a simplified story, approval is not informed. If the operation already executed, approval is not a gate. If the runtime hides resolved details, the human approved the wrong thing.&lt;br&gt;
A real approval prompt shows what matters:&lt;br&gt;
typescripttype ApprovalPrompt = {&lt;br&gt;
  action: "file_write" | "shell_command" | "provider_call";&lt;br&gt;
  requested?: string;&lt;br&gt;
  resolved?: string;  // The actual target&lt;br&gt;
  model?: string;&lt;br&gt;
  estimatedCost?: number;&lt;br&gt;
  budgetRemaining?: number;&lt;br&gt;
};&lt;br&gt;
Users shouldn't infer the real operation. The runtime should expose it.&lt;/p&gt;

&lt;p&gt;The pattern&lt;br&gt;
Resolve the real operation.&lt;br&gt;
Check the policy.&lt;br&gt;
Then decide.&lt;br&gt;
Then execute.&lt;br&gt;
This is what AI CostGuard does for provider calls—pre-call guards that catch retry storms, prompt loops, budget overruns, and step explosions before they happen. Not after. Not in a dashboard. Before.&lt;/p&gt;

&lt;p&gt;Takeaway&lt;br&gt;
GhostApproval isn't just a symlink story. It's a runtime design story.&lt;br&gt;
Don't ask humans to approve abstractions. Show them what's actually happening. Make the decision before execution. Then run.&lt;br&gt;
&lt;a href="https://github.com/salimassili62-afk/ai-costguard" rel="noopener noreferrer"&gt;https://github.com/salimassili62-afk/ai-costguard&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>javascript</category>
      <category>typescript</category>
      <category>coding</category>
    </item>
    <item>
      <title>Agent Telemetry Is Not Agent Control</title>
      <dc:creator>Assili Salim</dc:creator>
      <pubDate>Sun, 12 Jul 2026 20:51:59 +0000</pubDate>
      <link>https://dev.to/assili_salim_e3c07f9954de/agent-telemetry-is-not-agent-control-3f65</link>
      <guid>https://dev.to/assili_salim_e3c07f9954de/agent-telemetry-is-not-agent-control-3f65</guid>
      <description>&lt;p&gt;GitHub's Copilot updates added session streaming, cost tracking, OpenTelemetry exports. Useful. But here's the thing: seeing what went wrong and stopping it before it happens are not the same layer.&lt;br&gt;
You can log that an agent retried 14 times. You can't prevent call 15 from your dashboard.&lt;br&gt;
This is the gap.&lt;br&gt;
Telemetry = evidence. Control = prevention.&lt;br&gt;
Telemetry answers: "What happened?"&lt;br&gt;
Runtime control answers: "Should this next call happen?"&lt;br&gt;
Both matter. They just happen at different times.&lt;/p&gt;

&lt;p&gt;Why agents break this worse than normal LLM calls&lt;br&gt;
A normal model call is done in one roundtrip. Request → response → log it → move on.&lt;br&gt;
An agent runs in a loop. It calls the model, reads results, retries, delegates, switches strategies, keeps going. One bad call might look fine. The whole sequence wastes money.&lt;br&gt;
Telemetry will show you this beautifully after it happens. A runtime guard stops it before.&lt;/p&gt;

&lt;p&gt;The basic agent loop (vulnerable)&lt;br&gt;
javascriptwhile (!task.done) {&lt;br&gt;
  const response = await provider.call({&lt;br&gt;
    model: task.model,&lt;br&gt;
    messages: task.messages,&lt;br&gt;
  });&lt;/p&gt;

&lt;p&gt;telemetry.record({ runId: task.id, model, response });&lt;br&gt;
  task = await applyAgentStep(task, response);&lt;br&gt;
}&lt;br&gt;
You get evidence. Not protection.&lt;br&gt;
Add a decision before the call (safer)&lt;br&gt;
javascriptconst decision = guard.beforeCall({&lt;br&gt;
  runId: task.id,&lt;br&gt;
  model: task.model,&lt;br&gt;
  stepCount: task.steps.length,&lt;br&gt;
  retryCount: task.retryCount,&lt;br&gt;
  budgetRemaining: task.budgetRemaining,&lt;br&gt;
  progressState: task.progress,&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;if (!decision.allowed) {&lt;br&gt;
  telemetry.record({ runId: task.id, status: "stopped", reason: decision.reason });&lt;br&gt;
  return { status: "stopped", reason: decision.reason };&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;const response = await provider.call({ model, messages });&lt;br&gt;
Placement matters. The guard decides before. Telemetry records after.&lt;/p&gt;

&lt;p&gt;Six checks that belong before every provider call&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Known model pricing
If you can't price it, you can't budget it. Model names matter—aliases and rewrites break cost assumptions.&lt;/li&gt;
&lt;li&gt;Task-level budget
User is under monthly limit? Great. This agent run can still be wasting work. Both levels matter.&lt;/li&gt;
&lt;li&gt;Max-step limits
Agents run until something stops them. A step limit isn't sophisticated. That's why it works.&lt;/li&gt;
&lt;li&gt;Retry storms
Retries are good. Repeated failure loops are not. Stop similar errors after N retries.&lt;/li&gt;
&lt;li&gt;Prompt loops
Agents get stuck asking nearly the same question again. The text changes. The task doesn't. Detect it.&lt;/li&gt;
&lt;li&gt;No-progress detection
Track: tests passing, errors changing, tool results adding info, checklist items completing. If none move after several steps, stop.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Better telemetry includes stop reasons&lt;br&gt;
Pre-call control doesn't replace telemetry. It makes telemetry better.&lt;br&gt;
Record blocked calls, not just executed ones:&lt;br&gt;
javascripttype AgentDecisionEvent = {&lt;br&gt;
  runId: string;&lt;br&gt;
  model: string;&lt;br&gt;
  stepCount: number;&lt;br&gt;
  allowed: boolean;&lt;br&gt;
  reason?: "budget_exceeded" | "retry_storm" | "prompt_loop" | "no_progress";&lt;br&gt;
};&lt;br&gt;
Now you can ask: Which workflows trigger prompt loops? Which teams need tighter limits? Which models have unknown pricing? That's more useful than total spend.&lt;/p&gt;

&lt;p&gt;The takeaway&lt;br&gt;
Agent observability is table stakes now. GitHub's got you there.&lt;br&gt;
The next layer is admission control. Let the runtime say no before execution, not in the dashboard after.&lt;br&gt;
Record what happened. Before the next call, decide if it should happen at all.&lt;/p&gt;

&lt;p&gt;This is what AI CostGuard does—pre-call guards for production agent applications. Catches retry storms, prompt loops, budget overruns before they execute.&lt;br&gt;
&lt;a href="https://github.com/salimassili62-afk/ai-costguard" rel="noopener noreferrer"&gt;https://github.com/salimassili62-afk/ai-costguard&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>javascript</category>
      <category>api</category>
      <category>llm</category>
    </item>
    <item>
      <title>AI Agents Need Runtime State Checks, Not Just Better Prompts</title>
      <dc:creator>Assili Salim</dc:creator>
      <pubDate>Sat, 11 Jul 2026 04:46:23 +0000</pubDate>
      <link>https://dev.to/assili_salim_e3c07f9954de/ai-agents-need-runtime-state-checks-not-just-better-prompts-5cdp</link>
      <guid>https://dev.to/assili_salim_e3c07f9954de/ai-agents-need-runtime-state-checks-not-just-better-prompts-5cdp</guid>
      <description>&lt;p&gt;Claude Code’s July 8 changelog is a useful reminder of what production agent engineering actually looks like.&lt;br&gt;
The interesting parts are not model benchmarks.&lt;br&gt;
They are state-management fixes.&lt;br&gt;
Claude Code 2.1.205 fixed a message sent while Claude was working being silently lost when the turn ended at the --max-turns limit. It also fixed background agents staying shown as “failed” or “completed” after being resumed, background jobs flipping from “needs input” back to “working” with no readable text, and stale “Running” status in web and mobile Remote Control panels. �&lt;br&gt;
Claude&lt;br&gt;
The same changelog added a safety detail: background task notifications now explicitly state that no human input occurred, preventing fabricated in-transcript approvals from being acted on. �&lt;br&gt;
Claude&lt;br&gt;
These are not flashy issues.&lt;br&gt;
They are the issues that show up when agents become real runtime systems.&lt;br&gt;
Agents are state machines&lt;br&gt;
A coding agent is not only a model call.&lt;br&gt;
It has state:&lt;br&gt;
current step&lt;br&gt;
current task status&lt;br&gt;
previous messages&lt;br&gt;
tool results&lt;br&gt;
approvals&lt;br&gt;
turn limits&lt;br&gt;
retry count&lt;br&gt;
background job state&lt;br&gt;
user input state&lt;br&gt;
stop reason&lt;br&gt;
If that state is wrong, the agent can behave incorrectly even if the model response is good.&lt;br&gt;
A stale “Running” state can mislead the user.&lt;br&gt;
A lost message can cause the agent to continue without new context.&lt;br&gt;
A fake approval inside a transcript can be dangerous if treated as real input.&lt;br&gt;
A max-turn limit can stop the model while leaving the surrounding runtime ambiguous.&lt;br&gt;
This is why agent reliability is not just prompt quality.&lt;br&gt;
It is runtime correctness.&lt;br&gt;
The dangerous pattern: activity without permission&lt;br&gt;
A common agent failure is not a crash.&lt;br&gt;
It is continuation.&lt;br&gt;
The agent keeps moving because nothing clearly told it to stop.&lt;br&gt;
That can happen when:&lt;br&gt;
the task status is stale&lt;br&gt;
the stop condition is unclear&lt;br&gt;
the agent hits a max-turn limit but the runtime still continues&lt;br&gt;
a retry policy ignores the reason for failure&lt;br&gt;
generated text is mistaken for user approval&lt;br&gt;
background state is out of sync&lt;br&gt;
For cost-sensitive agents, this matters.&lt;br&gt;
Every unnecessary continuation can become another provider call.&lt;br&gt;
The invoice will show the usage later.&lt;br&gt;
The runtime should prevent the obviously wrong continuation before it happens.&lt;br&gt;
Treat every provider call as an admission decision&lt;br&gt;
A safer pattern is to check runtime state before every provider call.&lt;br&gt;
type AgentRuntimeState = {&lt;br&gt;
  runId: string;&lt;br&gt;
  status: "working" | "needs_input" | "stopped" | "failed" | "completed";&lt;br&gt;
  stepCount: number;&lt;br&gt;
  maxSteps: number;&lt;br&gt;
  retryCount: number;&lt;br&gt;
  budgetRemaining: number;&lt;br&gt;
  model: string;&lt;br&gt;
  modelPriceKnown: boolean;&lt;br&gt;
  hasRealUserApproval: boolean;&lt;br&gt;
  lastStopReason?: string;&lt;br&gt;
  recentProgress: boolean;&lt;br&gt;
};&lt;br&gt;
Then make a decision before calling the provider.&lt;br&gt;
function beforeProviderCall(state: AgentRuntimeState) {&lt;br&gt;
  if (state.status === "needs_input") {&lt;br&gt;
    return { allowed: false, reason: "needs_user_input" };&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;if (!state.hasRealUserApproval) {&lt;br&gt;
    return { allowed: false, reason: "missing_real_approval" };&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;if (state.stepCount &amp;gt;= state.maxSteps) {&lt;br&gt;
    return { allowed: false, reason: "max_steps_exceeded" };&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;if (!state.modelPriceKnown) {&lt;br&gt;
    return { allowed: false, reason: "unknown_model_pricing" };&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;if (state.budgetRemaining &amp;lt;= 0) {&lt;br&gt;
    return { allowed: false, reason: "budget_exceeded" };&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;if (!state.recentProgress &amp;amp;&amp;amp; state.retryCount &amp;gt; 0) {&lt;br&gt;
    return { allowed: false, reason: "no_progress_retry" };&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;return { allowed: true };&lt;br&gt;
}&lt;br&gt;
The exact API is not important.&lt;br&gt;
The placement is important.&lt;br&gt;
The check happens before the provider call.&lt;br&gt;
Generated approval is not approval&lt;br&gt;
The approval issue is especially important.&lt;br&gt;
An agent transcript may contain text that looks like permission.&lt;br&gt;
That does not mean a human gave permission.&lt;br&gt;
For production systems, approval should be an explicit runtime event.&lt;br&gt;
Not a string inside generated text.&lt;br&gt;
A safer shape:&lt;br&gt;
type ApprovalEvent = {&lt;br&gt;
  runId: string;&lt;br&gt;
  approvedBy: "human" | "policy";&lt;br&gt;
  approvedAt: number;&lt;br&gt;
  scope: "tool_call" | "provider_call" | "file_edit";&lt;br&gt;
};&lt;br&gt;
Then the runtime checks the approval event, not the transcript.&lt;br&gt;
function hasValidApproval(events: ApprovalEvent[], scope: ApprovalEvent["scope"]) {&lt;br&gt;
  return events.some(&lt;br&gt;
    event =&amp;gt;&lt;br&gt;
      event.scope === scope &amp;amp;&amp;amp;&lt;br&gt;
      (event.approvedBy === "human" || event.approvedBy === "policy")&lt;br&gt;
  );&lt;br&gt;
}&lt;br&gt;
This prevents a model-generated sentence from becoming operational permission.&lt;br&gt;
Max-turns should produce a stop reason&lt;br&gt;
A turn limit should not be a vague failure.&lt;br&gt;
It should create a structured stop reason.&lt;br&gt;
if (turnCount &amp;gt;= maxTurns) {&lt;br&gt;
  return {&lt;br&gt;
    status: "stopped",&lt;br&gt;
    reason: "max_turns_exceeded",&lt;br&gt;
    nextAction: "requires_human_review",&lt;br&gt;
  };&lt;br&gt;
}&lt;br&gt;
This makes the runtime easier to debug.&lt;br&gt;
It also prevents accidental continuation.&lt;br&gt;
If the agent stopped because it hit a limit, the next provider call should not happen automatically.&lt;br&gt;
Status should control execution&lt;br&gt;
Background agents make status accuracy critical.&lt;br&gt;
If the UI says “Running,” but the task is actually blocked, the user may assume progress is happening.&lt;br&gt;
If the runtime says “working,” but the agent needs input, the system may continue incorrectly.&lt;br&gt;
A simple rule helps:&lt;br&gt;
The runtime status should be the source of truth for execution.&lt;br&gt;
if (task.status !== "working") {&lt;br&gt;
  return {&lt;br&gt;
    allowed: false,&lt;br&gt;
    reason: &lt;code&gt;task_status_${task.status}&lt;/code&gt;,&lt;br&gt;
  };&lt;br&gt;
}&lt;br&gt;
Do not let the provider call path ignore the task state machine.&lt;br&gt;
Where AI CostGuard fits&lt;br&gt;
This is the kind of failure mode I’m building AI CostGuard around.&lt;br&gt;
AI CostGuard is a local-first TypeScript/Node.js pre-call runtime guard for AI-agent applications.&lt;br&gt;
It focuses on risky provider calls before execution:&lt;br&gt;
retry storms&lt;br&gt;
prompt loops&lt;br&gt;
max-step explosions&lt;br&gt;
runaway agent execution&lt;br&gt;
unknown model pricing&lt;br&gt;
budget overruns&lt;br&gt;
uncontrolled provider calls&lt;br&gt;
It is not a billing ledger.&lt;br&gt;
It is not a hard security boundary.&lt;br&gt;
It does not replace provider dashboards.&lt;br&gt;
The goal is narrower: give the runtime a way to say “this next call should not execute.”&lt;br&gt;
Takeaway&lt;br&gt;
Agent failures are becoming runtime failures.&lt;br&gt;
Not just bad prompts.&lt;br&gt;
Not just weak models.&lt;br&gt;
State bugs.&lt;br&gt;
Approval bugs.&lt;br&gt;
Stale status.&lt;br&gt;
Lost messages.&lt;br&gt;
Unclear stop reasons.&lt;br&gt;
If your agent can run in the background, it needs a real state machine.&lt;br&gt;
And before every provider call, that state machine should decide whether the call is still allowed.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>api</category>
      <category>llm</category>
      <category>agents</category>
    </item>
    <item>
      <title>GPT-5.6 Ultra Mode Means Agent Budgets Need to Handle Subagents</title>
      <dc:creator>Assili Salim</dc:creator>
      <pubDate>Fri, 10 Jul 2026 08:13:53 +0000</pubDate>
      <link>https://dev.to/assili_salim_e3c07f9954de/gpt-56-ultra-mode-means-agent-budgets-need-to-handle-subagents-565i</link>
      <guid>https://dev.to/assili_salim_e3c07f9954de/gpt-56-ultra-mode-means-agent-budgets-need-to-handle-subagents-565i</guid>
      <description>&lt;p&gt;OpenAI released GPT-5.6 across ChatGPT, Codex, and the API on July 9.&lt;/p&gt;

&lt;p&gt;The model family has three tiers: Sol, Terra, and Luna. OpenAI lists API pricing at $5 input / $30 output per million tokens for Sol, $2.50 / $15 for Terra, and $1 / $6 for Luna.&lt;/p&gt;

&lt;p&gt;The pricing matters.&lt;/p&gt;

&lt;p&gt;But if you build AI agents, the bigger detail is this:&lt;/p&gt;

&lt;p&gt;GPT-5.6 adds max reasoning effort and an ultra mode that uses subagents to accelerate complex work. In the API, OpenAI says multi-agent support can run concurrent subagents and synthesize their work in a single request.&lt;/p&gt;

&lt;p&gt;That changes how runtime budgets should work.&lt;/p&gt;

&lt;p&gt;A single-call budget is not enough when the task can fan out.&lt;/p&gt;

&lt;p&gt;The naive budget&lt;/p&gt;

&lt;p&gt;A simple budget guard might look like this:&lt;/p&gt;

&lt;p&gt;const estimatedCost =&lt;br&gt;
  inputTokens * inputPrice +&lt;br&gt;
  maxOutputTokens * outputPrice;&lt;/p&gt;

&lt;p&gt;if (estimatedCost &amp;gt; budgetRemaining) {&lt;br&gt;
  throw new Error("Budget exceeded");&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;This is a decent start.&lt;/p&gt;

&lt;p&gt;But it assumes the next call is the whole unit of risk.&lt;/p&gt;

&lt;p&gt;That assumption breaks down when an agent can create subagents.&lt;/p&gt;

&lt;p&gt;A parent agent may start several branches.&lt;/p&gt;

&lt;p&gt;Each branch may call a model.&lt;/p&gt;

&lt;p&gt;Each branch may use tools.&lt;/p&gt;

&lt;p&gt;Each branch may retry.&lt;/p&gt;

&lt;p&gt;The parent may then call the model again to synthesize results.&lt;/p&gt;

&lt;p&gt;The real budget is not only the next call.&lt;/p&gt;

&lt;p&gt;It is the execution tree.&lt;/p&gt;

&lt;p&gt;Think in execution trees&lt;/p&gt;

&lt;p&gt;A multi-agent workflow can be modeled like this:&lt;/p&gt;

&lt;p&gt;type AgentNode = {&lt;br&gt;
  id: string;&lt;br&gt;
  parentId?: string;&lt;br&gt;
  model: string;&lt;br&gt;
  effort: "low" | "medium" | "high" | "max" | "ultra";&lt;br&gt;
  stepCount: number;&lt;br&gt;
  retryCount: number;&lt;br&gt;
  budgetRemaining: number;&lt;br&gt;
};&lt;/p&gt;

&lt;p&gt;The parent task needs a shared budget.&lt;/p&gt;

&lt;p&gt;Each subagent needs a local budget.&lt;/p&gt;

&lt;p&gt;type WorkflowBudget = {&lt;br&gt;
  workflowId: string;&lt;br&gt;
  totalRemaining: number;&lt;br&gt;
  perAgentLimit: number;&lt;br&gt;
  perCallLimit: number;&lt;br&gt;
};&lt;/p&gt;

&lt;p&gt;This gives you three useful boundaries:&lt;/p&gt;

&lt;p&gt;per-call budget&lt;/p&gt;

&lt;p&gt;per-subagent budget&lt;/p&gt;

&lt;p&gt;whole-workflow budget&lt;/p&gt;

&lt;p&gt;You need all three.&lt;/p&gt;

&lt;p&gt;A call can be safe locally while the subagent is wasting work.&lt;/p&gt;

&lt;p&gt;A subagent can be safe locally while the whole workflow is burning too much in parallel.&lt;/p&gt;

&lt;p&gt;Add a pre-call decision&lt;/p&gt;

&lt;p&gt;Before every provider call, make the runtime decide whether the call should happen.&lt;/p&gt;

&lt;p&gt;type BeforeCallInput = {&lt;br&gt;
  workflowId: string;&lt;br&gt;
  agentId: string;&lt;br&gt;
  parentAgentId?: string;&lt;br&gt;
  model: string;&lt;br&gt;
  effort: string;&lt;br&gt;
  prompt: string;&lt;br&gt;
  estimatedInputTokens: number;&lt;br&gt;
  maxOutputTokens: number;&lt;br&gt;
  stepCount: number;&lt;br&gt;
  retryCount: number;&lt;br&gt;
  budgetRemainingForAgent: number;&lt;br&gt;
  budgetRemainingForWorkflow: number;&lt;br&gt;
  previousPrompts: string[];&lt;br&gt;
  progressState: {&lt;br&gt;
    filesChanged?: boolean;&lt;br&gt;
    testsImproved?: boolean;&lt;br&gt;
    errorsChanged?: boolean;&lt;br&gt;
    objectiveCompleted?: boolean;&lt;br&gt;
  };&lt;br&gt;
};&lt;/p&gt;

&lt;p&gt;type GuardDecision =&lt;br&gt;
  | { allowed: true }&lt;br&gt;
  | {&lt;br&gt;
      allowed: false;&lt;br&gt;
      reason:&lt;br&gt;
        | "unknown_model_pricing"&lt;br&gt;
        | "call_budget_exceeded"&lt;br&gt;
        | "agent_budget_exceeded"&lt;br&gt;
        | "workflow_budget_exceeded"&lt;br&gt;
        | "max_steps_exceeded"&lt;br&gt;
        | "retry_storm"&lt;br&gt;
        | "prompt_loop"&lt;br&gt;
        | "no_progress";&lt;br&gt;
    };&lt;/p&gt;

&lt;p&gt;Then use it before the provider call:&lt;/p&gt;

&lt;p&gt;const decision = guard.beforeCall({&lt;br&gt;
  workflowId,&lt;br&gt;
  agentId,&lt;br&gt;
  parentAgentId,&lt;br&gt;
  model,&lt;br&gt;
  effort,&lt;br&gt;
  prompt,&lt;br&gt;
  estimatedInputTokens,&lt;br&gt;
  maxOutputTokens,&lt;br&gt;
  stepCount,&lt;br&gt;
  retryCount,&lt;br&gt;
  budgetRemainingForAgent,&lt;br&gt;
  budgetRemainingForWorkflow,&lt;br&gt;
  previousPrompts,&lt;br&gt;
  progressState,&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;if (!decision.allowed) {&lt;br&gt;
  return {&lt;br&gt;
    status: "stopped",&lt;br&gt;
    reason: decision.reason,&lt;br&gt;
  };&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;const result = await provider.call({&lt;br&gt;
  model,&lt;br&gt;
  prompt,&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;The exact API is not the point.&lt;/p&gt;

&lt;p&gt;The placement is the point.&lt;/p&gt;

&lt;p&gt;The decision happens before the provider call.&lt;/p&gt;

&lt;p&gt;Check model pricing&lt;/p&gt;

&lt;p&gt;GPT-5.6 has multiple model tiers with different prices.&lt;/p&gt;

&lt;p&gt;That means the runtime should not treat model names as harmless strings.&lt;/p&gt;

&lt;p&gt;if (!pricingCatalog.has(model)) {&lt;br&gt;
  return {&lt;br&gt;
    allowed: false,&lt;br&gt;
    reason: "unknown_model_pricing",&lt;br&gt;
  };&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;Do not guess.&lt;/p&gt;

&lt;p&gt;Unknown pricing should fail closed.&lt;/p&gt;

&lt;p&gt;This matters even more when agents can escalate, fallback, or fan out.&lt;/p&gt;

&lt;p&gt;Check effort level&lt;/p&gt;

&lt;p&gt;Effort level is not only a quality setting.&lt;/p&gt;

&lt;p&gt;It is an execution-policy setting.&lt;/p&gt;

&lt;p&gt;if (effort === "ultra" &amp;amp;&amp;amp; !taskPolicy.allowUltra) {&lt;br&gt;
  return {&lt;br&gt;
    allowed: false,&lt;br&gt;
    reason: "effort_not_allowed",&lt;br&gt;
  };&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;A small task should not silently use the most expensive execution shape.&lt;/p&gt;

&lt;p&gt;A high-value task might deserve it.&lt;/p&gt;

&lt;p&gt;The runtime should know the difference.&lt;/p&gt;

&lt;p&gt;Check workflow budget&lt;/p&gt;

&lt;p&gt;For subagents, local checks are not enough.&lt;/p&gt;

&lt;p&gt;if (estimatedNextCallCost &amp;gt; budgetRemainingForWorkflow) {&lt;br&gt;
  return {&lt;br&gt;
    allowed: false,&lt;br&gt;
    reason: "workflow_budget_exceeded",&lt;br&gt;
  };&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;This prevents many individually reasonable branches from consuming the shared ceiling.&lt;/p&gt;

&lt;p&gt;Parallel waste is still waste.&lt;/p&gt;

&lt;p&gt;Check subagent budget&lt;/p&gt;

&lt;p&gt;Each branch also needs a local ceiling.&lt;/p&gt;

&lt;p&gt;if (estimatedNextCallCost &amp;gt; budgetRemainingForAgent) {&lt;br&gt;
  return {&lt;br&gt;
    allowed: false,&lt;br&gt;
    reason: "agent_budget_exceeded",&lt;br&gt;
  };&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;If one subagent is stuck, it should stop without consuming the parent workflow.&lt;/p&gt;

&lt;p&gt;Check max steps&lt;/p&gt;

&lt;p&gt;Subagents need step limits too.&lt;/p&gt;

&lt;p&gt;if (stepCount &amp;gt;= maxSteps) {&lt;br&gt;
  return {&lt;br&gt;
    allowed: false,&lt;br&gt;
    reason: "max_steps_exceeded",&lt;br&gt;
  };&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;A branch that cannot produce useful work in a reasonable number of steps should stop cleanly.&lt;/p&gt;

&lt;p&gt;Check prompt loops&lt;/p&gt;

&lt;p&gt;If a subagent keeps asking nearly the same thing, the runtime should treat that as a loop.&lt;/p&gt;

&lt;p&gt;if (similarToRecentPrompt(prompt, previousPrompts)) {&lt;br&gt;
  return {&lt;br&gt;
    allowed: false,&lt;br&gt;
    reason: "prompt_loop",&lt;br&gt;
  };&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;This is especially useful in coding agents.&lt;/p&gt;

&lt;p&gt;A stuck agent often rephrases the same failing approach.&lt;/p&gt;

&lt;p&gt;Check no progress&lt;/p&gt;

&lt;p&gt;A subagent can be active without being useful.&lt;/p&gt;

&lt;p&gt;Track signals like:&lt;/p&gt;

&lt;p&gt;tests improved&lt;br&gt;
error changed&lt;br&gt;
relevant files changed&lt;br&gt;
objective completed&lt;br&gt;
retrieved context changed&lt;br&gt;
final answer got closer&lt;/p&gt;

&lt;p&gt;If none of those move, stop.&lt;/p&gt;

&lt;p&gt;if (!madeProgress(progressState, recentSteps)) {&lt;br&gt;
  return {&lt;br&gt;
    allowed: false,&lt;br&gt;
    reason: "no_progress",&lt;br&gt;
  };&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;A stopped branch is better than a branch that looks busy while wasting calls.&lt;/p&gt;

&lt;p&gt;Where AI CostGuard fits&lt;/p&gt;

&lt;p&gt;AI CostGuard is the local-first TypeScript/Node.js runtime safety layer I’m building for AI-agent projects.&lt;/p&gt;

&lt;p&gt;It focuses on pre-call checks for:&lt;/p&gt;

&lt;p&gt;retry storms&lt;br&gt;
prompt loops&lt;br&gt;
max-step explosions&lt;br&gt;
runaway agent execution&lt;br&gt;
unknown model pricing&lt;br&gt;
budget overruns&lt;br&gt;
uncontrolled provider calls&lt;br&gt;
invisible AI-agent cost risk&lt;/p&gt;

&lt;p&gt;It is not a billing ledger.&lt;/p&gt;

&lt;p&gt;It is not a hard security boundary.&lt;/p&gt;

&lt;p&gt;It does not replace provider dashboards.&lt;/p&gt;

&lt;p&gt;The goal is narrow:&lt;/p&gt;

&lt;p&gt;help the runtime decide whether the next provider call should execute.&lt;/p&gt;

&lt;p&gt;Takeaway&lt;/p&gt;

&lt;p&gt;Multi-agent execution changes the cost-control problem.&lt;/p&gt;

&lt;p&gt;You are no longer guarding one prompt.&lt;/p&gt;

&lt;p&gt;You are guarding an execution tree.&lt;/p&gt;

&lt;p&gt;That means budgets need to exist at three levels:&lt;/p&gt;

&lt;p&gt;per call&lt;/p&gt;

&lt;p&gt;per subagent&lt;/p&gt;

&lt;p&gt;per workflow&lt;/p&gt;

&lt;p&gt;Once agents can fan out, the invoice is even later than it used to be.&lt;br&gt;
&lt;a href="https://github.com/salimassili62-afk/ai-costguard" rel="noopener noreferrer"&gt;https://github.com/salimassili62-afk/ai-costguard&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>developer</category>
      <category>tooling</category>
      <category>chatgpt</category>
    </item>
    <item>
      <title>Always-Running AI Agents Need Pre-Call Stop Conditions</title>
      <dc:creator>Assili Salim</dc:creator>
      <pubDate>Wed, 08 Jul 2026 21:02:58 +0000</pubDate>
      <link>https://dev.to/assili_salim_e3c07f9954de/always-running-ai-agents-need-pre-call-stop-conditions-3pag</link>
      <guid>https://dev.to/assili_salim_e3c07f9954de/always-running-ai-agents-need-pre-call-stop-conditions-3pag</guid>
      <description>&lt;p&gt;Claude Cowork can now keep working after you close your laptop.&lt;/p&gt;

&lt;p&gt;WIRED reports that Anthropic expanded Cowork beyond the desktop app, so users do not need an active desktop session to keep tasks running. Users can interact with limited versions through the Claude smartphone app or web browser, and Cowork can continue tasks after the user clocks out.&lt;/p&gt;

&lt;p&gt;That is a useful feature.&lt;/p&gt;

&lt;p&gt;It also creates a runtime engineering problem.&lt;/p&gt;

&lt;p&gt;If agents can run unattended, they need stop conditions before provider calls execute.&lt;/p&gt;

&lt;p&gt;A chatbot waits. An agent continues.&lt;/p&gt;

&lt;p&gt;A chatbot is mostly reactive.&lt;/p&gt;

&lt;p&gt;The user sends a message.&lt;/p&gt;

&lt;p&gt;The model responds.&lt;/p&gt;

&lt;p&gt;The interaction pauses.&lt;/p&gt;

&lt;p&gt;An agent is different.&lt;/p&gt;

&lt;p&gt;It may:&lt;/p&gt;

&lt;p&gt;call a model&lt;br&gt;
call tools&lt;br&gt;
inspect results&lt;br&gt;
add context&lt;br&gt;
retry&lt;br&gt;
call another tool&lt;br&gt;
generate a document&lt;br&gt;
send another provider request&lt;br&gt;
keep going while the user is away&lt;/p&gt;

&lt;p&gt;That means the agent runtime needs controls.&lt;/p&gt;

&lt;p&gt;Not only logs.&lt;/p&gt;

&lt;p&gt;Not only dashboards.&lt;/p&gt;

&lt;p&gt;Controls before execution.&lt;/p&gt;

&lt;p&gt;The naive loop&lt;/p&gt;

&lt;p&gt;A simple agent loop might look like this:&lt;/p&gt;

&lt;p&gt;while (!task.done) {&lt;br&gt;
  const response = await provider.call({&lt;br&gt;
    model: task.model,&lt;br&gt;
    messages: task.messages,&lt;br&gt;
  });&lt;/p&gt;

&lt;p&gt;task = await applyAgentStep(task, response);&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;This is easy to write.&lt;/p&gt;

&lt;p&gt;It is also unsafe for unattended workflows.&lt;/p&gt;

&lt;p&gt;There is no max-step limit.&lt;/p&gt;

&lt;p&gt;No budget check.&lt;/p&gt;

&lt;p&gt;No retry-storm detection.&lt;/p&gt;

&lt;p&gt;No prompt-loop detection.&lt;/p&gt;

&lt;p&gt;No known-pricing check.&lt;/p&gt;

&lt;p&gt;No no-progress stop.&lt;/p&gt;

&lt;p&gt;If the agent gets stuck, it keeps creating provider calls until something external stops it.&lt;/p&gt;

&lt;p&gt;That “something” might be a provider error, user intervention, an account limit, or a bill.&lt;/p&gt;

&lt;p&gt;None of those are ideal runtime controls.&lt;/p&gt;

&lt;p&gt;Add a pre-call decision&lt;/p&gt;

&lt;p&gt;A safer loop puts a guard before every provider call.&lt;/p&gt;

&lt;p&gt;const decision = guard.beforeCall({&lt;br&gt;
  runId: task.id,&lt;br&gt;
  model: task.model,&lt;br&gt;
  messages: task.messages,&lt;br&gt;
  stepCount: task.steps.length,&lt;br&gt;
  retryCount: task.retryCount,&lt;br&gt;
  budgetRemaining: task.budgetRemaining,&lt;br&gt;
  previousPrompts: task.previousPrompts,&lt;br&gt;
  progressState: task.progress,&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;if (!decision.allowed) {&lt;br&gt;
  return {&lt;br&gt;
    status: "stopped",&lt;br&gt;
    reason: decision.reason,&lt;br&gt;
    error: decision.error,&lt;br&gt;
  };&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;const response = await provider.call({&lt;br&gt;
  model: task.model,&lt;br&gt;
  messages: task.messages,&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;The exact API does not matter.&lt;/p&gt;

&lt;p&gt;The placement matters.&lt;/p&gt;

&lt;p&gt;The runtime checks the call before the provider sees it.&lt;/p&gt;

&lt;p&gt;What should the runtime check?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Known model pricing&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the runtime cannot price the model, it cannot enforce a reliable budget.&lt;/p&gt;

&lt;p&gt;if (!pricingCatalog.has(model)) {&lt;br&gt;
  return {&lt;br&gt;
    allowed: false,&lt;br&gt;
    reason: "unknown_model_pricing",&lt;br&gt;
  };&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;Do not guess inside an unattended loop.&lt;/p&gt;

&lt;p&gt;A typo, fallback, gateway rewrite, or model alias can break cost assumptions.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Task budget&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Every unattended run should have a task-level budget.&lt;/p&gt;

&lt;p&gt;if (estimatedNextCallCost &amp;gt; budgetRemaining) {&lt;br&gt;
  return {&lt;br&gt;
    allowed: false,&lt;br&gt;
    reason: "budget_exceeded",&lt;br&gt;
  };&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;Monthly dashboards are useful, but they are late.&lt;/p&gt;

&lt;p&gt;A task budget stops the next call before spend is created.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Max steps&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Agents need explicit step limits.&lt;/p&gt;

&lt;p&gt;if (stepCount &amp;gt;= maxSteps) {&lt;br&gt;
  return {&lt;br&gt;
    allowed: false,&lt;br&gt;
    reason: "max_steps_exceeded",&lt;br&gt;
  };&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;A run that cannot finish inside a reasonable number of steps should stop cleanly.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Retry storms&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Retries are normal.&lt;/p&gt;

&lt;p&gt;Retry storms are not.&lt;/p&gt;

&lt;p&gt;if (retryCount &amp;gt;= maxRetries &amp;amp;&amp;amp; recentErrorsAreSimilar(errors)) {&lt;br&gt;
  return {&lt;br&gt;
    allowed: false,&lt;br&gt;
    reason: "retry_storm_detected",&lt;br&gt;
  };&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;The goal is not to remove retries.&lt;/p&gt;

&lt;p&gt;The goal is to prevent repeated failure from becoming the workload.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Prompt loops&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Agents often get stuck by asking almost the same thing again.&lt;/p&gt;

&lt;p&gt;if (similarToRecentPrompt(currentPrompt, previousPrompts)) {&lt;br&gt;
  return {&lt;br&gt;
    allowed: false,&lt;br&gt;
    reason: "similar_prompt_loop",&lt;br&gt;
  };&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;This catches a common pattern:&lt;/p&gt;

&lt;p&gt;the agent looks active, but it is not exploring a new path.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;No progress&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A run can consume steps while producing no useful movement.&lt;/p&gt;

&lt;p&gt;Track progress signals:&lt;/p&gt;

&lt;p&gt;errors decreasing&lt;br&gt;
files changing meaningfully&lt;br&gt;
tests improving&lt;br&gt;
checklist items completing&lt;br&gt;
retrieved information changing&lt;br&gt;
user-defined success criteria improving&lt;/p&gt;

&lt;p&gt;If progress does not change after several steps, stop.&lt;/p&gt;

&lt;p&gt;if (!madeProgress(recentSteps)) {&lt;br&gt;
  return {&lt;br&gt;
    allowed: false,&lt;br&gt;
    reason: "no_progress",&lt;br&gt;
  };&lt;br&gt;
}&lt;br&gt;
Why this matters more for always-running agents&lt;/p&gt;

&lt;p&gt;When the user is watching, some failures are caught manually.&lt;/p&gt;

&lt;p&gt;When the agent runs overnight, the runtime becomes the first line of control.&lt;/p&gt;

&lt;p&gt;That changes the design requirement.&lt;/p&gt;

&lt;p&gt;An unattended agent needs:&lt;/p&gt;

&lt;p&gt;local budgets&lt;br&gt;
max-step policies&lt;br&gt;
retry budgets&lt;br&gt;
prompt-loop detection&lt;br&gt;
known model pricing&lt;br&gt;
structured stop reasons&lt;/p&gt;

&lt;p&gt;These are not advanced features.&lt;/p&gt;

&lt;p&gt;They are basic operating rules.&lt;/p&gt;

&lt;p&gt;Where AI CostGuard fits&lt;/p&gt;

&lt;p&gt;AI CostGuard is the local-first TypeScript/Node.js runtime safety layer I’m building for AI-agent projects.&lt;/p&gt;

&lt;p&gt;It focuses on pre-call protection for:&lt;/p&gt;

&lt;p&gt;retry storms&lt;br&gt;
prompt loops&lt;br&gt;
max-step explosions&lt;br&gt;
runaway agent execution&lt;br&gt;
unknown model pricing&lt;br&gt;
budget overruns&lt;br&gt;
uncontrolled provider calls&lt;br&gt;
invisible AI-agent cost risk&lt;/p&gt;

&lt;p&gt;It is not a billing ledger.&lt;/p&gt;

&lt;p&gt;It is not a hard security boundary.&lt;/p&gt;

&lt;p&gt;It does not replace provider dashboards.&lt;/p&gt;

&lt;p&gt;The goal is to help the runtime decide whether the next provider call should execute.&lt;/p&gt;

&lt;p&gt;Takeaway&lt;/p&gt;

&lt;p&gt;Always-running agents are useful because they remove friction.&lt;/p&gt;

&lt;p&gt;That same lack of friction is the risk.&lt;/p&gt;

&lt;p&gt;If an agent can keep working after the laptop closes, it needs runtime rules for when to stop.&lt;br&gt;
&lt;a href="https://github.com/salimassili62-afk/ai-costguard" rel="noopener noreferrer"&gt;https://github.com/salimassili62-afk/ai-costguard&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>javascript</category>
      <category>opensource</category>
      <category>api</category>
    </item>
    <item>
      <title>Tokenizer Changes Can Break AI-Agent Budget Assumptions</title>
      <dc:creator>Assili Salim</dc:creator>
      <pubDate>Sat, 04 Jul 2026 08:41:45 +0000</pubDate>
      <link>https://dev.to/assili_salim_e3c07f9954de/tokenizer-changes-can-break-ai-agent-budget-assumptions-1cj9</link>
      <guid>https://dev.to/assili_salim_e3c07f9954de/tokenizer-changes-can-break-ai-agent-budget-assumptions-1cj9</guid>
      <description>&lt;p&gt;Claude Sonnet 5 is a good reminder that AI-agent cost control is not only about model pricing.&lt;/p&gt;

&lt;p&gt;Anthropic says Sonnet 5 launches at $2 per million input tokens and $10 per million output tokens through August 31, 2026, then moves to $3 per million input tokens and $15 per million output tokens.&lt;/p&gt;

&lt;p&gt;Vercel’s AI Gateway changelog adds an important implementation detail: Sonnet 5 uses an updated tokenizer, and the same input can map to more tokens.&lt;/p&gt;

&lt;p&gt;That matters if you build agents.&lt;/p&gt;

&lt;p&gt;A model can have attractive pricing and still change your runtime cost assumptions.&lt;/p&gt;

&lt;p&gt;The problem&lt;/p&gt;

&lt;p&gt;Many agent systems estimate cost like this:&lt;/p&gt;

&lt;p&gt;const estimatedCost =&lt;br&gt;
  inputTokens * inputPrice +&lt;br&gt;
  maxOutputTokens * outputPrice;&lt;/p&gt;

&lt;p&gt;That is fine as a start.&lt;/p&gt;

&lt;p&gt;But it assumes the runtime knows:&lt;/p&gt;

&lt;p&gt;which model is being used&lt;br&gt;
how the input is tokenized&lt;br&gt;
the correct input price&lt;br&gt;
the correct output price&lt;br&gt;
how many retries are allowed&lt;br&gt;
how many steps are allowed&lt;br&gt;
whether the agent is making progress&lt;/p&gt;

&lt;p&gt;If any of those assumptions drift, the estimate becomes weaker.&lt;/p&gt;

&lt;p&gt;A tokenizer update is one way this happens.&lt;/p&gt;

&lt;p&gt;The prompt text may be the same.&lt;/p&gt;

&lt;p&gt;The token count may not be.&lt;/p&gt;

&lt;p&gt;Agents amplify small estimation errors&lt;/p&gt;

&lt;p&gt;For a single request, a token-count surprise is usually manageable.&lt;/p&gt;

&lt;p&gt;For an agent, it can compound.&lt;/p&gt;

&lt;p&gt;An agent may:&lt;/p&gt;

&lt;p&gt;call the model&lt;br&gt;
inspect files&lt;br&gt;
add context&lt;br&gt;
run tools&lt;br&gt;
retry&lt;br&gt;
ask a similar prompt&lt;br&gt;
switch models&lt;br&gt;
continue for more steps&lt;/p&gt;

&lt;p&gt;Small cost drift per call becomes bigger across a run.&lt;/p&gt;

&lt;p&gt;Now add parallel agents, fallback paths, or long-context workflows.&lt;/p&gt;

&lt;p&gt;The problem is no longer “What does this model cost?”&lt;/p&gt;

&lt;p&gt;The problem is:&lt;/p&gt;

&lt;p&gt;Should this next provider call be allowed?&lt;/p&gt;

&lt;p&gt;Add a pre-call decision&lt;/p&gt;

&lt;p&gt;Before the provider call, add a guard decision.&lt;/p&gt;

&lt;p&gt;type BeforeCallInput = {&lt;br&gt;
  runId: string;&lt;br&gt;
  model: string;&lt;br&gt;
  prompt: string;&lt;br&gt;
  estimatedInputTokens: number;&lt;br&gt;
  maxOutputTokens: number;&lt;br&gt;
  stepCount: number;&lt;br&gt;
  retryCount: number;&lt;br&gt;
  budgetRemaining: number;&lt;br&gt;
  previousPrompts: string[];&lt;br&gt;
};&lt;/p&gt;

&lt;p&gt;type GuardDecision =&lt;br&gt;
  | { allowed: true }&lt;br&gt;
  | {&lt;br&gt;
      allowed: false;&lt;br&gt;
      reason:&lt;br&gt;
        | "unknown_model_pricing"&lt;br&gt;
        | "budget_exceeded"&lt;br&gt;
        | "max_steps_exceeded"&lt;br&gt;
        | "retry_storm"&lt;br&gt;
        | "prompt_loop";&lt;br&gt;
    };&lt;/p&gt;

&lt;p&gt;Then use it before execution:&lt;/p&gt;

&lt;p&gt;const decision = guard.beforeCall({&lt;br&gt;
  runId,&lt;br&gt;
  model,&lt;br&gt;
  prompt,&lt;br&gt;
  estimatedInputTokens,&lt;br&gt;
  maxOutputTokens,&lt;br&gt;
  stepCount,&lt;br&gt;
  retryCount,&lt;br&gt;
  budgetRemaining,&lt;br&gt;
  previousPrompts,&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;if (!decision.allowed) {&lt;br&gt;
  return {&lt;br&gt;
    status: "stopped",&lt;br&gt;
    reason: decision.reason,&lt;br&gt;
  };&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;const result = await provider.call({&lt;br&gt;
  model,&lt;br&gt;
  prompt,&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;The important part is placement.&lt;/p&gt;

&lt;p&gt;The check happens before the provider call.&lt;/p&gt;

&lt;p&gt;Not after the bill.&lt;/p&gt;

&lt;p&gt;Fail closed on unknown pricing&lt;/p&gt;

&lt;p&gt;If the runtime does not know the model price, it cannot enforce a reliable budget.&lt;/p&gt;

&lt;p&gt;if (!pricingCatalog.has(model)) {&lt;br&gt;
  return {&lt;br&gt;
    allowed: false,&lt;br&gt;
    reason: "unknown_model_pricing",&lt;br&gt;
  };&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;Do not guess.&lt;/p&gt;

&lt;p&gt;A model alias, fallback, gateway rewrite, or typo can break your assumptions.&lt;/p&gt;

&lt;p&gt;Unknown pricing should stop the run before the call executes.&lt;/p&gt;

&lt;p&gt;Recalculate tokens after model migration&lt;/p&gt;

&lt;p&gt;When changing models, do not assume the same prompt has the same token count.&lt;/p&gt;

&lt;p&gt;A basic migration checklist:&lt;/p&gt;

&lt;p&gt;run representative prompts through the new tokenizer&lt;br&gt;
compare input token counts&lt;br&gt;
compare output length behavior&lt;br&gt;
update pricing metadata&lt;br&gt;
retest max-step limits&lt;br&gt;
retest retry behavior&lt;br&gt;
check fallback paths&lt;br&gt;
measure cost per successful task&lt;/p&gt;

&lt;p&gt;Cost per token is useful.&lt;/p&gt;

&lt;p&gt;Cost per successful task is more useful.&lt;/p&gt;

&lt;p&gt;Watch retry and prompt-loop behavior&lt;/p&gt;

&lt;p&gt;A smarter model may finish tasks in fewer steps.&lt;/p&gt;

&lt;p&gt;It may also continue longer because it can pursue more complex plans.&lt;/p&gt;

&lt;p&gt;Your runtime should still stop obvious waste.&lt;/p&gt;

&lt;p&gt;if (stepCount &amp;gt;= maxSteps) {&lt;br&gt;
  return { allowed: false, reason: "max_steps_exceeded" };&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;if (retryCount &amp;gt;= maxRetries) {&lt;br&gt;
  return { allowed: false, reason: "retry_storm" };&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;if (similarToRecentPrompt(prompt, previousPrompts)) {&lt;br&gt;
  return { allowed: false, reason: "prompt_loop" };&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;These checks do not make the model better.&lt;/p&gt;

&lt;p&gt;They make the system safer to operate.&lt;/p&gt;

&lt;p&gt;Where AI CostGuard fits&lt;/p&gt;

&lt;p&gt;AI CostGuard is the local-first TypeScript/Node.js runtime safety layer I’m building for AI-agent projects.&lt;/p&gt;

&lt;p&gt;It focuses on pre-call safety checks for:&lt;/p&gt;

&lt;p&gt;retry storms&lt;br&gt;
prompt loops&lt;br&gt;
max-step explosions&lt;br&gt;
runaway execution&lt;br&gt;
unknown model pricing&lt;br&gt;
budget overruns&lt;br&gt;
uncontrolled provider calls&lt;/p&gt;

&lt;p&gt;It is not a billing ledger.&lt;/p&gt;

&lt;p&gt;It is not a hard security boundary.&lt;/p&gt;

&lt;p&gt;It does not replace provider dashboards.&lt;/p&gt;

&lt;p&gt;The goal is to catch obviously risky calls before they execute.&lt;/p&gt;

&lt;p&gt;Takeaway&lt;/p&gt;

&lt;p&gt;Model pricing pages tell you the unit price.&lt;/p&gt;

&lt;p&gt;They do not tell you whether your agent should make the next call.&lt;/p&gt;

&lt;p&gt;Tokenizer changes, fallback models, retries, and prompt loops all affect real runtime cost.&lt;/p&gt;

&lt;p&gt;For AI agents, cost safety belongs before the provider call.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>typescript</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Gateway Routing Helps AI Apps. Agent Runtimes Still Need Pre-Call Guards.</title>
      <dc:creator>Assili Salim</dc:creator>
      <pubDate>Fri, 03 Jul 2026 07:22:32 +0000</pubDate>
      <link>https://dev.to/assili_salim_e3c07f9954de/gateway-routing-helps-ai-apps-agent-runtimes-still-need-pre-call-guards-4i9i</link>
      <guid>https://dev.to/assili_salim_e3c07f9954de/gateway-routing-helps-ai-apps-agent-runtimes-still-need-pre-call-guards-4i9i</guid>
      <description>&lt;p&gt;Vercel added routing rules to AI Gateway on July 2.&lt;/p&gt;

&lt;p&gt;Routing rules let teams rewrite or deny model requests at the gateway level. A rewrite rule serves a request for one model using another model. A deny rule blocks a model and returns a 403. Vercel lists use cases like rerouting when a model is down or retired, standardizing on one model, routing an expensive model to a cheaper one, or keeping a team off unapproved models.&lt;/p&gt;

&lt;p&gt;That is useful infrastructure.&lt;/p&gt;

&lt;p&gt;But if you are building AI agents, it is not the whole safety layer.&lt;/p&gt;

&lt;p&gt;A gateway can decide where a request goes.&lt;/p&gt;

&lt;p&gt;Your runtime still needs to decide whether the request should happen.&lt;/p&gt;

&lt;p&gt;The difference&lt;/p&gt;

&lt;p&gt;Gateway routing answers:&lt;/p&gt;

&lt;p&gt;"Which model should serve this request?"&lt;/p&gt;

&lt;p&gt;Runtime guarding answers:&lt;/p&gt;

&lt;p&gt;"Should this request be allowed at all?"&lt;/p&gt;

&lt;p&gt;Those are different problems.&lt;/p&gt;

&lt;p&gt;A gateway may rewrite:&lt;/p&gt;

&lt;p&gt;anthropic/claude-opus-4.8 -&amp;gt; anthropic/claude-haiku-4.5&lt;/p&gt;

&lt;p&gt;That can keep traffic moving.&lt;/p&gt;

&lt;p&gt;But the gateway may not know:&lt;/p&gt;

&lt;p&gt;the agent already retried 12 times&lt;br&gt;
the current prompt is nearly identical to previous failed prompts&lt;br&gt;
the run exceeded its task budget&lt;br&gt;
the agent passed its max-step limit&lt;br&gt;
tool calls are happening without progress&lt;br&gt;
the fallback model keeps the loop alive but does not improve the task&lt;/p&gt;

&lt;p&gt;That context usually lives inside the agent runtime.&lt;/p&gt;

&lt;p&gt;A naive agent loop&lt;/p&gt;

&lt;p&gt;Many agent loops start like this:&lt;/p&gt;

&lt;p&gt;while (!task.done) {&lt;br&gt;
  const response = await provider.call({&lt;br&gt;
    model: task.model,&lt;br&gt;
    messages: task.messages,&lt;br&gt;
  });&lt;/p&gt;

&lt;p&gt;task = await applyAgentStep(task, response);&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;This is simple.&lt;/p&gt;

&lt;p&gt;It is also missing the controls that matter in production.&lt;/p&gt;

&lt;p&gt;There is no budget check.&lt;/p&gt;

&lt;p&gt;No max-step check.&lt;/p&gt;

&lt;p&gt;No retry-storm detection.&lt;/p&gt;

&lt;p&gt;No prompt-loop detection.&lt;/p&gt;

&lt;p&gt;No unknown-pricing block.&lt;/p&gt;

&lt;p&gt;No no-progress stop.&lt;/p&gt;

&lt;p&gt;If a gateway rewrites the model, this loop may keep running.&lt;/p&gt;

&lt;p&gt;That is not always what you want.&lt;/p&gt;

&lt;p&gt;Add a pre-call guard&lt;/p&gt;

&lt;p&gt;A safer pattern puts a local decision before the provider call:&lt;/p&gt;

&lt;p&gt;const decision = guard.beforeCall({&lt;br&gt;
  runId: task.id,&lt;br&gt;
  model: task.model,&lt;br&gt;
  messages: task.messages,&lt;br&gt;
  stepCount: task.steps.length,&lt;br&gt;
  retryCount: task.retryCount,&lt;br&gt;
  previousPrompts: task.previousPrompts,&lt;br&gt;
  budgetRemaining: task.budgetRemaining,&lt;br&gt;
  progressState: task.progress,&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;if (!decision.allowed) {&lt;br&gt;
  return {&lt;br&gt;
    status: "stopped",&lt;br&gt;
    reason: decision.reason,&lt;br&gt;
    error: decision.error,&lt;br&gt;
  };&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;const response = await provider.call({&lt;br&gt;
  model: task.model,&lt;br&gt;
  messages: task.messages,&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;The exact API does not matter.&lt;/p&gt;

&lt;p&gt;The placement matters.&lt;/p&gt;

&lt;p&gt;The guard runs before the provider call.&lt;/p&gt;

&lt;p&gt;What should the runtime check?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Known model pricing&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the runtime cannot price the model, it cannot enforce a reliable budget.&lt;/p&gt;

&lt;p&gt;if (!pricingCatalog.has(model)) {&lt;br&gt;
  return {&lt;br&gt;
    allowed: false,&lt;br&gt;
    reason: "unknown_model_pricing",&lt;br&gt;
  };&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;This matters even more when routing and fallback rules exist.&lt;/p&gt;

&lt;p&gt;A rewritten model still has a cost profile.&lt;/p&gt;

&lt;p&gt;The runtime should know it.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Budget remaining&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Task-level budgets are different from account-level limits.&lt;/p&gt;

&lt;p&gt;if (estimatedNextCallCost &amp;gt; budgetRemaining) {&lt;br&gt;
  return {&lt;br&gt;
    allowed: false,&lt;br&gt;
    reason: "budget_exceeded",&lt;br&gt;
  };&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;A monthly dashboard can show spend later.&lt;/p&gt;

&lt;p&gt;A runtime budget can stop the next call now.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Max steps&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Agents should have explicit stopping rules.&lt;/p&gt;

&lt;p&gt;if (stepCount &amp;gt;= maxSteps) {&lt;br&gt;
  return {&lt;br&gt;
    allowed: false,&lt;br&gt;
    reason: "max_steps_exceeded",&lt;br&gt;
  };&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;A model route can change.&lt;/p&gt;

&lt;p&gt;The step limit should still apply.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Retry storms&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Retries are useful.&lt;/p&gt;

&lt;p&gt;Blind retries are not.&lt;/p&gt;

&lt;p&gt;if (retryCount &amp;gt; maxRetries &amp;amp;&amp;amp; recentErrorsAreSimilar(errors)) {&lt;br&gt;
  return {&lt;br&gt;
    allowed: false,&lt;br&gt;
    reason: "retry_storm_detected",&lt;br&gt;
  };&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;A fallback model can hide a retry storm by keeping the run alive.&lt;/p&gt;

&lt;p&gt;The runtime should detect the pattern.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Prompt loops&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Agents sometimes ask almost the same thing repeatedly.&lt;/p&gt;

&lt;p&gt;if (similarToRecentPrompt(currentPrompt, previousPrompts)) {&lt;br&gt;
  return {&lt;br&gt;
    allowed: false,&lt;br&gt;
    reason: "similar_prompt_loop",&lt;br&gt;
  };&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;If the prompt is not changing meaningfully, the model route may not be the main issue.&lt;/p&gt;

&lt;p&gt;The agent may be stuck.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;No progress&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A run can be active without improving.&lt;/p&gt;

&lt;p&gt;Useful progress signals include:&lt;/p&gt;

&lt;p&gt;tests passing&lt;br&gt;
errors decreasing&lt;br&gt;
files changing meaningfully&lt;br&gt;
task checklist items completing&lt;br&gt;
user-defined success criteria improving&lt;/p&gt;

&lt;p&gt;If the agent consumes steps without progress, stop.&lt;/p&gt;

&lt;p&gt;Layer the controls&lt;/p&gt;

&lt;p&gt;A good AI-agent architecture can use both gateway policy and runtime guards.&lt;/p&gt;

&lt;p&gt;One possible flow:&lt;/p&gt;

&lt;p&gt;agent wants next call&lt;br&gt;
        ↓&lt;br&gt;
local runtime guard checks run state&lt;br&gt;
        ↓&lt;br&gt;
gateway applies model routing or deny rules&lt;br&gt;
        ↓&lt;br&gt;
provider executes request&lt;br&gt;
        ↓&lt;br&gt;
logs and dashboards record result&lt;/p&gt;

&lt;p&gt;The order matters.&lt;/p&gt;

&lt;p&gt;The runtime has run context.&lt;/p&gt;

&lt;p&gt;The gateway has team-level model policy.&lt;/p&gt;

&lt;p&gt;The provider executes.&lt;/p&gt;

&lt;p&gt;The dashboard explains.&lt;/p&gt;

&lt;p&gt;Do not ask one layer to do all four jobs.&lt;/p&gt;

&lt;p&gt;Where AI CostGuard fits&lt;/p&gt;

&lt;p&gt;AI CostGuard is the local-first TypeScript / Node.js runtime safety layer I’m building for this exact class of problem.&lt;/p&gt;

&lt;p&gt;It focuses on pre-call checks for AI-agent projects:&lt;/p&gt;

&lt;p&gt;retry storms&lt;br&gt;
prompt loops&lt;br&gt;
max-step explosions&lt;br&gt;
runaway agent execution&lt;br&gt;
unknown model pricing&lt;br&gt;
budget overruns&lt;br&gt;
uncontrolled provider calls&lt;/p&gt;

&lt;p&gt;It is not a billing ledger.&lt;/p&gt;

&lt;p&gt;It is not a hard security boundary.&lt;/p&gt;

&lt;p&gt;It does not replace provider dashboards or gateway routing.&lt;/p&gt;

&lt;p&gt;The goal is narrower:&lt;/p&gt;

&lt;p&gt;help the agent runtime decide whether the next provider call should execute.&lt;/p&gt;

&lt;p&gt;Takeaway&lt;/p&gt;

&lt;p&gt;Gateway routing is useful.&lt;/p&gt;

&lt;p&gt;It centralizes model policy.&lt;/p&gt;

&lt;p&gt;It helps teams move traffic when models are down, retired, too expensive, or not approved.&lt;/p&gt;

&lt;p&gt;But routing does not replace runtime safety.&lt;/p&gt;

&lt;p&gt;A cheaper fallback can still waste money.&lt;/p&gt;

&lt;p&gt;A policy-approved model can still be part of a prompt loop.&lt;/p&gt;

&lt;p&gt;A valid request can still exceed the task budget.&lt;/p&gt;

&lt;p&gt;For AI agents, the critical question happens before the call:&lt;/p&gt;

&lt;p&gt;Should this request exist?&lt;br&gt;
&lt;a href="https://github.com/salimassili62-afk/ai-costguard" rel="noopener noreferrer"&gt;https://github.com/salimassili62-afk/ai-costguard&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7awcer8w5l1lc2ox153g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7awcer8w5l1lc2ox153g.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>api</category>
      <category>llm</category>
      <category>agents</category>
    </item>
    <item>
      <title>Reusable Agent Skills Need Pre-Call Runtime Checks</title>
      <dc:creator>Assili Salim</dc:creator>
      <pubDate>Sun, 28 Jun 2026 14:10:34 +0000</pubDate>
      <link>https://dev.to/assili_salim_e3c07f9954de/reusable-agent-skills-need-pre-call-runtime-checks-hne</link>
      <guid>https://dev.to/assili_salim_e3c07f9954de/reusable-agent-skills-need-pre-call-runtime-checks-hne</guid>
      <description>&lt;p&gt;OpenAI’s recent Codex research includes one detail that matters for developers building agents:&lt;/p&gt;

&lt;p&gt;26.6% of users use skills to share instructions for complex workflows, and more than 10% manage three or more concurrent Codex agents at some point each week.&lt;/p&gt;

&lt;p&gt;That means agent usage is moving from one-off prompts toward reusable workflows.&lt;/p&gt;

&lt;p&gt;That is good.&lt;/p&gt;

&lt;p&gt;It also means failures can become reusable.&lt;/p&gt;

&lt;p&gt;The problem&lt;/p&gt;

&lt;p&gt;A bad prompt can waste one model call.&lt;/p&gt;

&lt;p&gt;A bad agent skill can waste many runs.&lt;/p&gt;

&lt;p&gt;A skill might encode:&lt;/p&gt;

&lt;p&gt;how to retry&lt;br&gt;
how to call tools&lt;br&gt;
how to inspect files&lt;br&gt;
how to recover from errors&lt;br&gt;
how much context to add&lt;br&gt;
when to continue&lt;br&gt;
when to stop&lt;/p&gt;

&lt;p&gt;If those rules are loose, every run inherits the looseness.&lt;/p&gt;

&lt;p&gt;This is the part developers need to treat carefully.&lt;/p&gt;

&lt;p&gt;Reusable agent behavior needs reusable runtime boundaries.&lt;/p&gt;

&lt;p&gt;A naive agent skill&lt;/p&gt;

&lt;p&gt;Imagine a coding-agent skill for fixing failing tests.&lt;/p&gt;

&lt;p&gt;The instruction might be:&lt;/p&gt;

&lt;p&gt;const skill = {&lt;br&gt;
  name: "fix-failing-tests",&lt;br&gt;
  instructions: &lt;code&gt;&lt;br&gt;
    Inspect the failing test.&lt;br&gt;
    Find the relevant files.&lt;br&gt;
    Apply a fix.&lt;br&gt;
    Run the tests again.&lt;br&gt;
    Repeat until the tests pass.&lt;br&gt;
&lt;/code&gt;,&lt;br&gt;
};&lt;/p&gt;

&lt;p&gt;This sounds fine.&lt;/p&gt;

&lt;p&gt;But “repeat until the tests pass” is dangerous without runtime limits.&lt;/p&gt;

&lt;p&gt;What if the test failure is environmental?&lt;/p&gt;

&lt;p&gt;What if the agent keeps editing unrelated files?&lt;/p&gt;

&lt;p&gt;What if the prompt barely changes across attempts?&lt;/p&gt;

&lt;p&gt;What if each retry adds more context?&lt;/p&gt;

&lt;p&gt;What if the fallback model has unknown pricing?&lt;/p&gt;

&lt;p&gt;The skill is useful.&lt;/p&gt;

&lt;p&gt;The runtime is under-specified.&lt;/p&gt;

&lt;p&gt;Add a pre-call decision&lt;/p&gt;

&lt;p&gt;Before every provider call, the runtime should decide whether the call is still allowed.&lt;/p&gt;

&lt;p&gt;type BeforeCallInput = {&lt;br&gt;
  runId: string;&lt;br&gt;
  workflowId?: string;&lt;br&gt;
  model: string;&lt;br&gt;
  prompt: string;&lt;br&gt;
  stepCount: number;&lt;br&gt;
  retryCount: number;&lt;br&gt;
  budgetRemaining: number;&lt;br&gt;
  previousPrompts: string[];&lt;br&gt;
  progressState: {&lt;br&gt;
    testsImproved?: boolean;&lt;br&gt;
    errorsChanged?: boolean;&lt;br&gt;
    filesChanged?: boolean;&lt;br&gt;
  };&lt;br&gt;
};&lt;/p&gt;

&lt;p&gt;type GuardDecision =&lt;br&gt;
  | { allowed: true }&lt;br&gt;
  | {&lt;br&gt;
      allowed: false;&lt;br&gt;
      reason:&lt;br&gt;
        | "unknown_model_pricing"&lt;br&gt;
        | "budget_exceeded"&lt;br&gt;
        | "max_steps_exceeded"&lt;br&gt;
        | "retry_storm_detected"&lt;br&gt;
        | "similar_prompt_loop"&lt;br&gt;
        | "no_progress";&lt;br&gt;
      error: Error;&lt;br&gt;
    };&lt;/p&gt;

&lt;p&gt;Then use it before the provider call:&lt;/p&gt;

&lt;p&gt;const decision = guard.beforeCall({&lt;br&gt;
  runId,&lt;br&gt;
  workflowId,&lt;br&gt;
  model,&lt;br&gt;
  prompt,&lt;br&gt;
  stepCount,&lt;br&gt;
  retryCount,&lt;br&gt;
  budgetRemaining,&lt;br&gt;
  previousPrompts,&lt;br&gt;
  progressState,&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;if (!decision.allowed) {&lt;br&gt;
  return {&lt;br&gt;
    status: "stopped",&lt;br&gt;
    reason: decision.reason,&lt;br&gt;
    error: decision.error,&lt;br&gt;
  };&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;const response = await provider.call({&lt;br&gt;
  model,&lt;br&gt;
  prompt,&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;The exact API does not matter.&lt;/p&gt;

&lt;p&gt;The placement matters.&lt;/p&gt;

&lt;p&gt;The check happens before the provider call.&lt;/p&gt;

&lt;p&gt;What should the guard check?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Known model pricing&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the runtime cannot price the model, it cannot enforce a budget.&lt;/p&gt;

&lt;p&gt;if (!pricingCatalog.has(model)) {&lt;br&gt;
  return {&lt;br&gt;
    allowed: false,&lt;br&gt;
    reason: "unknown_model_pricing",&lt;br&gt;
    error: new Error(&lt;code&gt;Unknown pricing for model: ${model}&lt;/code&gt;),&lt;br&gt;
  };&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;Do not guess.&lt;/p&gt;

&lt;p&gt;Fail closed.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Budget remaining&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Agent workflows should have task-level budgets.&lt;/p&gt;

&lt;p&gt;if (estimatedNextCallCost &amp;gt; budgetRemaining) {&lt;br&gt;
  return {&lt;br&gt;
    allowed: false,&lt;br&gt;
    reason: "budget_exceeded",&lt;br&gt;
    error: new Error("Agent run budget exceeded"),&lt;br&gt;
  };&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;A small bug fix and a long migration should not share the same budget.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Max steps&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Agents need step limits.&lt;/p&gt;

&lt;p&gt;if (stepCount &amp;gt;= maxSteps) {&lt;br&gt;
  return {&lt;br&gt;
    allowed: false,&lt;br&gt;
    reason: "max_steps_exceeded",&lt;br&gt;
    error: new Error("Maximum agent steps exceeded"),&lt;br&gt;
  };&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;This is basic production hygiene.&lt;/p&gt;

&lt;p&gt;If a workflow cannot complete inside a reasonable number of steps, it should stop.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Retry storms&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Retries are useful.&lt;/p&gt;

&lt;p&gt;Blind retries are expensive.&lt;/p&gt;

&lt;p&gt;if (retryCount &amp;gt;= maxRetries &amp;amp;&amp;amp; recentErrorsAreSimilar(errors)) {&lt;br&gt;
  return {&lt;br&gt;
    allowed: false,&lt;br&gt;
    reason: "retry_storm_detected",&lt;br&gt;
    error: new Error("Retry storm detected"),&lt;br&gt;
  };&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;The goal is not to ban retries.&lt;/p&gt;

&lt;p&gt;The goal is to stop repeated failure.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Prompt loops&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A prompt loop happens when the agent keeps asking nearly the same thing.&lt;/p&gt;

&lt;p&gt;if (similarToRecentPrompt(prompt, previousPrompts)) {&lt;br&gt;
  return {&lt;br&gt;
    allowed: false,&lt;br&gt;
    reason: "similar_prompt_loop",&lt;br&gt;
    error: new Error("Similar prompt loop detected"),&lt;br&gt;
  };&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;Even a simple similarity check can catch obvious loops.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;No progress&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A run can be active and still not improve.&lt;/p&gt;

&lt;p&gt;Track progress signals:&lt;/p&gt;

&lt;p&gt;did tests improve?&lt;br&gt;
did the error change?&lt;br&gt;
did files change meaningfully?&lt;br&gt;
did a checklist item complete?&lt;br&gt;
did the agent reduce uncertainty?&lt;/p&gt;

&lt;p&gt;If several steps pass without progress, stop.&lt;/p&gt;

&lt;p&gt;if (!madeProgress(progressState, recentSteps)) {&lt;br&gt;
  return {&lt;br&gt;
    allowed: false,&lt;br&gt;
    reason: "no_progress",&lt;br&gt;
    error: new Error("Agent run is not making progress"),&lt;br&gt;
  };&lt;br&gt;
}&lt;br&gt;
Why concurrency makes this more important&lt;/p&gt;

&lt;p&gt;OpenAI’s Codex research says more than 10% of users manage three or more concurrent agents at some point each week.&lt;/p&gt;

&lt;p&gt;That changes the risk.&lt;/p&gt;

&lt;p&gt;One agent wasting a few calls is visible.&lt;/p&gt;

&lt;p&gt;Several agents each wasting a few calls can look normal.&lt;/p&gt;

&lt;p&gt;The local loop becomes a global budget problem.&lt;/p&gt;

&lt;p&gt;For parallel workflows, add shared budget checks:&lt;/p&gt;

&lt;p&gt;if (estimatedNextCallCost &amp;gt; workflowBudgetRemaining) {&lt;br&gt;
  return {&lt;br&gt;
    allowed: false,&lt;br&gt;
    reason: "workflow_budget_exceeded",&lt;br&gt;
    error: new Error("Workflow budget exceeded"),&lt;br&gt;
  };&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;Each agent needs its own limit.&lt;/p&gt;

&lt;p&gt;The workflow needs a shared limit.&lt;/p&gt;

&lt;p&gt;Both matter.&lt;/p&gt;

&lt;p&gt;Where AI CostGuard fits&lt;/p&gt;

&lt;p&gt;AI CostGuard is the local-first TypeScript / Node.js runtime safety layer I’m building for this problem.&lt;/p&gt;

&lt;p&gt;It focuses on pre-call protection for AI-agent projects:&lt;/p&gt;

&lt;p&gt;retry storms&lt;br&gt;
prompt loops&lt;br&gt;
max-step explosions&lt;br&gt;
runaway execution&lt;br&gt;
unknown model pricing&lt;br&gt;
budget overruns&lt;br&gt;
uncontrolled provider calls&lt;/p&gt;

&lt;p&gt;It is not a billing ledger.&lt;/p&gt;

&lt;p&gt;It is not a hard security boundary.&lt;/p&gt;

&lt;p&gt;It does not replace provider dashboards.&lt;/p&gt;

&lt;p&gt;The goal is to stop obviously risky calls before they execute.&lt;/p&gt;

&lt;p&gt;Takeaway&lt;/p&gt;

&lt;p&gt;Reusable agent skills are a good abstraction.&lt;/p&gt;

&lt;p&gt;But they should not only package instructions.&lt;/p&gt;

&lt;p&gt;They should also inherit runtime policy.&lt;/p&gt;

&lt;p&gt;Before every provider call, ask:&lt;/p&gt;

&lt;p&gt;Should this call still happen?&lt;/p&gt;

&lt;p&gt;That one question catches many expensive agent failures before they become API usage.&lt;/p&gt;

&lt;p&gt;Tags: ai, agents, typescript, devtools&lt;br&gt;
&lt;a href="https://github.com/salimassili62-afk/ai-costguard" rel="noopener noreferrer"&gt;https://github.com/salimassili62-afk/ai-costguard&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>javascript</category>
      <category>api</category>
      <category>llm</category>
    </item>
    <item>
      <title>If AI Agents Run in Parallel, Budget Checks Need to Happen Before Every Provider Call</title>
      <dc:creator>Assili Salim</dc:creator>
      <pubDate>Sat, 27 Jun 2026 10:39:23 +0000</pubDate>
      <link>https://dev.to/assili_salim_e3c07f9954de/if-ai-agents-run-in-parallel-budget-checks-need-to-happen-before-every-provider-call-47jf</link>
      <guid>https://dev.to/assili_salim_e3c07f9954de/if-ai-agents-run-in-parallel-budget-checks-need-to-happen-before-every-provider-call-47jf</guid>
      <description>&lt;p&gt;OpenAI’s latest Codex usage data shows a clear shift from short assistant interactions to longer, delegated agent work.&lt;/p&gt;

&lt;p&gt;By May 2026, 80.6% of sampled individual Codex users had made at least one request estimated to exceed 30 minutes of human work. 70.2% had made one estimated to exceed one hour.&lt;/p&gt;

&lt;p&gt;The more interesting detail:&lt;/p&gt;

&lt;p&gt;By June 2026, the 99th percentile of daily active OpenAI users regularly generated more than 60 hours of Codex agent turns per day, distributed across multiple parallel agents.&lt;/p&gt;

&lt;p&gt;That is the engineering lesson.&lt;/p&gt;

&lt;p&gt;A parallel agent workflow is not a prompt.&lt;/p&gt;

&lt;p&gt;It is a runtime system.&lt;/p&gt;

&lt;p&gt;Runtime systems need budgets.&lt;/p&gt;

&lt;p&gt;The naive version&lt;/p&gt;

&lt;p&gt;A simple agent loop often looks like this:&lt;/p&gt;

&lt;p&gt;while (!task.done) {&lt;br&gt;
  const result = await provider.call({&lt;br&gt;
    model: task.model,&lt;br&gt;
    messages: task.messages,&lt;br&gt;
  });&lt;/p&gt;

&lt;p&gt;task = await applyAgentStep(task, result);&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;This is easy to write.&lt;/p&gt;

&lt;p&gt;It is also missing the controls that matter in production.&lt;/p&gt;

&lt;p&gt;No max-step limit.&lt;/p&gt;

&lt;p&gt;No budget check.&lt;/p&gt;

&lt;p&gt;No retry-storm detection.&lt;/p&gt;

&lt;p&gt;No prompt-loop detection.&lt;/p&gt;

&lt;p&gt;No unknown-pricing block.&lt;/p&gt;

&lt;p&gt;No no-progress stop.&lt;/p&gt;

&lt;p&gt;Now imagine running many of these in parallel.&lt;/p&gt;

&lt;p&gt;await Promise.all(tasks.map(runAgent));&lt;/p&gt;

&lt;p&gt;This is where the failure mode changes.&lt;/p&gt;

&lt;p&gt;One agent overspending is visible.&lt;/p&gt;

&lt;p&gt;Ten agents each overspending slightly can look like normal usage until the bill or queue pressure shows up.&lt;/p&gt;

&lt;p&gt;The better shape&lt;/p&gt;

&lt;p&gt;Before every provider call, the runtime should make a decision.&lt;/p&gt;

&lt;p&gt;const decision = guard.beforeCall({&lt;br&gt;
  runId: task.id,&lt;br&gt;
  workflowId: task.workflowId,&lt;br&gt;
  model: task.model,&lt;br&gt;
  messages: task.messages,&lt;br&gt;
  stepCount: task.steps.length,&lt;br&gt;
  retryCount: task.retryCount,&lt;br&gt;
  budgetRemaining: task.budgetRemaining,&lt;br&gt;
  sharedBudgetRemaining: workflow.budgetRemaining,&lt;br&gt;
  previousPrompts: task.previousPrompts,&lt;br&gt;
  progressState: task.progress,&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;if (!decision.allowed) {&lt;br&gt;
  return {&lt;br&gt;
    status: "stopped",&lt;br&gt;
    reason: decision.reason,&lt;br&gt;
    error: decision.error,&lt;br&gt;
  };&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;const result = await provider.call({&lt;br&gt;
  model: task.model,&lt;br&gt;
  messages: task.messages,&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;The API shape does not matter.&lt;/p&gt;

&lt;p&gt;The placement matters.&lt;/p&gt;

&lt;p&gt;The guard runs before the provider call.&lt;/p&gt;

&lt;p&gt;That gives the runtime a chance to stop the next unit of spend before it exists.&lt;/p&gt;

&lt;p&gt;What should be checked?&lt;br&gt;
Known model pricing&lt;/p&gt;

&lt;p&gt;If the runtime does not know the model price, it cannot enforce a reliable budget.&lt;/p&gt;

&lt;p&gt;if (!pricingCatalog.has(model)) {&lt;br&gt;
  return {&lt;br&gt;
    allowed: false,&lt;br&gt;
    reason: "unknown_model_pricing",&lt;br&gt;
  };&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;Do not guess.&lt;/p&gt;

&lt;p&gt;Fail closed.&lt;/p&gt;

&lt;p&gt;Per-run budget&lt;/p&gt;

&lt;p&gt;Each agent run needs its own budget.&lt;/p&gt;

&lt;p&gt;if (estimatedNextCallCost &amp;gt; runBudgetRemaining) {&lt;br&gt;
  return {&lt;br&gt;
    allowed: false,&lt;br&gt;
    reason: "run_budget_exceeded",&lt;br&gt;
  };&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;This stops one confused task from consuming more than it should.&lt;/p&gt;

&lt;p&gt;Shared workflow budget&lt;/p&gt;

&lt;p&gt;Parallel agents also need a shared ceiling.&lt;/p&gt;

&lt;p&gt;if (estimatedNextCallCost &amp;gt; workflowBudgetRemaining) {&lt;br&gt;
  return {&lt;br&gt;
    allowed: false,&lt;br&gt;
    reason: "workflow_budget_exceeded",&lt;br&gt;
  };&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;This matters because parallel waste is harder to notice.&lt;/p&gt;

&lt;p&gt;Each worker may look reasonable locally while the workflow burns too much globally.&lt;/p&gt;

&lt;p&gt;Max-step limit&lt;/p&gt;

&lt;p&gt;Agents should not run forever.&lt;/p&gt;

&lt;p&gt;if (stepCount &amp;gt;= maxSteps) {&lt;br&gt;
  return {&lt;br&gt;
    allowed: false,&lt;br&gt;
    reason: "max_steps_exceeded",&lt;br&gt;
  };&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;Simple controls are often the most valuable.&lt;/p&gt;

&lt;p&gt;Retry-storm detection&lt;/p&gt;

&lt;p&gt;Retries are useful until they become the workload.&lt;/p&gt;

&lt;p&gt;if (retryCount &amp;gt; maxRetries &amp;amp;&amp;amp; recentErrorsAreSimilar(errors)) {&lt;br&gt;
  return {&lt;br&gt;
    allowed: false,&lt;br&gt;
    reason: "retry_storm_detected",&lt;br&gt;
  };&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;The goal is not to ban retries.&lt;/p&gt;

&lt;p&gt;The goal is to prevent blind retries.&lt;/p&gt;

&lt;p&gt;Prompt-loop detection&lt;/p&gt;

&lt;p&gt;If the current prompt is too similar to earlier failed prompts, the agent may be stuck.&lt;/p&gt;

&lt;p&gt;if (similarToRecentPrompt(currentPrompt, previousPrompts)) {&lt;br&gt;
  return {&lt;br&gt;
    allowed: false,&lt;br&gt;
    reason: "similar_prompt_loop",&lt;br&gt;
  };&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;This catches a common agent failure:&lt;/p&gt;

&lt;p&gt;the system looks active, but it is asking the same question again.&lt;/p&gt;

&lt;p&gt;No-progress detection&lt;/p&gt;

&lt;p&gt;A run can consume steps without improving the outcome.&lt;/p&gt;

&lt;p&gt;Track progress signals:&lt;/p&gt;

&lt;p&gt;tests passing&lt;br&gt;
errors decreasing&lt;br&gt;
files changing meaningfully&lt;br&gt;
plan items completing&lt;br&gt;
user-defined success criteria improving&lt;/p&gt;

&lt;p&gt;If nothing improves after several steps, stop.&lt;/p&gt;

&lt;p&gt;Why this matters&lt;/p&gt;

&lt;p&gt;OpenAI’s post says agents change the unit of knowledge work from single interactions to delegated, long-horizon tasks. Agents can operate independently for minutes or hours while using tools, interacting with environments, and iterating toward solutions.&lt;/p&gt;

&lt;p&gt;That is exactly why runtime control matters.&lt;/p&gt;

&lt;p&gt;A chatbot can fail and wait for the next user message.&lt;/p&gt;

&lt;p&gt;An agent can fail and continue.&lt;/p&gt;

&lt;p&gt;That continuation is the risk.&lt;/p&gt;

&lt;p&gt;Where AI CostGuard fits&lt;/p&gt;

&lt;p&gt;AI CostGuard is the local-first TypeScript / Node.js runtime safety layer I’m building for this problem.&lt;/p&gt;

&lt;p&gt;It is designed to stop agent failure modes before provider calls execute:&lt;/p&gt;

&lt;p&gt;retry storms&lt;br&gt;
prompt loops&lt;br&gt;
max-step explosions&lt;br&gt;
no-progress runs&lt;br&gt;
budget overruns&lt;br&gt;
unknown model pricing&lt;br&gt;
runaway agent behavior&lt;/p&gt;

&lt;p&gt;The core question is:&lt;/p&gt;

&lt;p&gt;Should this next provider call be allowed?&lt;/p&gt;

&lt;p&gt;If no, the runtime should stop with a structured reason.&lt;/p&gt;

&lt;p&gt;Not after the invoice.&lt;/p&gt;

&lt;p&gt;Before the call.&lt;/p&gt;

&lt;p&gt;When agents run for minutes or hours, cost control becomes runtime control.&lt;/p&gt;

&lt;p&gt;When agents run in parallel, cost control becomes coordination.&lt;/p&gt;

&lt;p&gt;Start with one practical rule:&lt;/p&gt;

&lt;p&gt;Never call the provider before asking whether the next call is still allowed.&lt;/p&gt;

&lt;p&gt;Add a pre-call decision object to your agent loop before adding another dashboard.&lt;br&gt;
&lt;a href="https://github.com/salimassili62-afk/ai-costguard" rel="noopener noreferrer"&gt;https://github.com/salimassili62-afk/ai-costguard&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>javascript</category>
      <category>api</category>
      <category>agents</category>
    </item>
    <item>
      <title>AI Coding Agents Need Runtime Telemetry Before Commit Telemetry</title>
      <dc:creator>Assili Salim</dc:creator>
      <pubDate>Fri, 26 Jun 2026 13:52:33 +0000</pubDate>
      <link>https://dev.to/assili_salim_e3c07f9954de/ai-coding-agents-need-runtime-telemetry-before-commit-telemetry-38i2</link>
      <guid>https://dev.to/assili_salim_e3c07f9954de/ai-coding-agents-need-runtime-telemetry-before-commit-telemetry-38i2</guid>
      <description>&lt;p&gt;A new arXiv paper published on June 23, 2026 scanned more than 180 million Git repositories to detect traces of AI coding agents in open source. The authors used multiple signals, including configuration-file scanning, commit-message analysis, author-identity matching, and bot-signature lookup.&lt;/p&gt;

&lt;p&gt;The most useful result for developers is the visibility gap.&lt;/p&gt;

&lt;p&gt;In one snapshot, multi-method detection found 850,157 Claude Code commits.&lt;/p&gt;

&lt;p&gt;Bot-account lookup found only 28,154.&lt;/p&gt;

&lt;p&gt;That is 3.3%, or a 30x relative recall gap.&lt;/p&gt;

&lt;p&gt;The paper also reports more than 320,000 commit-attributed agent commits per month across snapshots from December 2024 to April 2026.&lt;/p&gt;

&lt;p&gt;The immediate takeaway:&lt;/p&gt;

&lt;p&gt;AI coding agents are being used heavily.&lt;/p&gt;

&lt;p&gt;The engineering takeaway:&lt;/p&gt;

&lt;p&gt;Single-signal observability is weak.&lt;/p&gt;

&lt;p&gt;Commit telemetry is too late&lt;/p&gt;

&lt;p&gt;A commit is the end of an agent run.&lt;/p&gt;

&lt;p&gt;It does not tell you enough about the run itself.&lt;/p&gt;

&lt;p&gt;A commit may not show:&lt;/p&gt;

&lt;p&gt;how many model calls happened&lt;br&gt;
how many retries happened&lt;br&gt;
whether prompts repeated&lt;br&gt;
whether tools failed&lt;br&gt;
whether the model price was known&lt;br&gt;
whether the run exceeded budget&lt;br&gt;
whether the agent made progress&lt;br&gt;
whether fallback models were used&lt;br&gt;
whether the agent stopped safely&lt;/p&gt;

&lt;p&gt;If you only inspect the repository after the fact, you are observing the artifact.&lt;/p&gt;

&lt;p&gt;You are not observing the execution.&lt;/p&gt;

&lt;p&gt;For agent systems, execution is where many failures happen.&lt;/p&gt;

&lt;p&gt;Agents are loops&lt;/p&gt;

&lt;p&gt;A coding agent is usually some version of this:&lt;/p&gt;

&lt;p&gt;while (!task.done) {&lt;br&gt;
  const response = await model.call(task.context);&lt;/p&gt;

&lt;p&gt;const action = parseAction(response);&lt;/p&gt;

&lt;p&gt;const result = await runTool(action);&lt;/p&gt;

&lt;p&gt;task = updateTask(task, result);&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;This is useful.&lt;/p&gt;

&lt;p&gt;It is also incomplete.&lt;/p&gt;

&lt;p&gt;There is no budget.&lt;/p&gt;

&lt;p&gt;No max-step limit.&lt;/p&gt;

&lt;p&gt;No retry control.&lt;/p&gt;

&lt;p&gt;No prompt-loop detection.&lt;/p&gt;

&lt;p&gt;No known-pricing check.&lt;/p&gt;

&lt;p&gt;No no-progress stop.&lt;/p&gt;

&lt;p&gt;A safer runtime shape puts a decision before the provider call.&lt;/p&gt;

&lt;p&gt;const decision = guard.beforeCall({&lt;br&gt;
  runId: task.id,&lt;br&gt;
  model: task.model,&lt;br&gt;
  prompt: task.currentPrompt,&lt;br&gt;
  stepCount: task.steps.length,&lt;br&gt;
  retryCount: task.retryCount,&lt;br&gt;
  previousPrompts: task.previousPrompts,&lt;br&gt;
  budgetRemaining: task.budgetRemaining,&lt;br&gt;
  progressState: task.progress,&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;if (!decision.allowed) {&lt;br&gt;
  return {&lt;br&gt;
    status: "stopped",&lt;br&gt;
    reason: decision.reason,&lt;br&gt;
    error: decision.error,&lt;br&gt;
  };&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;const response = await model.call(task.context);&lt;/p&gt;

&lt;p&gt;The important part is not the exact API.&lt;/p&gt;

&lt;p&gt;The important part is timing.&lt;/p&gt;

&lt;p&gt;The check happens before the provider call.&lt;/p&gt;

&lt;p&gt;That means the runtime can stop unsafe execution before more cost is created.&lt;/p&gt;

&lt;p&gt;What to log before the call&lt;/p&gt;

&lt;p&gt;A useful agent runtime should log decision inputs, not only final outputs.&lt;/p&gt;

&lt;p&gt;For each provider call, consider recording:&lt;/p&gt;

&lt;p&gt;type AgentCallDecision = {&lt;br&gt;
  runId: string;&lt;br&gt;
  model: string;&lt;br&gt;
  modelPriceKnown: boolean;&lt;br&gt;
  stepCount: number;&lt;br&gt;
  maxSteps: number;&lt;br&gt;
  retryCount: number;&lt;br&gt;
  budgetRemaining: number;&lt;br&gt;
  estimatedNextCallCost: number;&lt;br&gt;
  promptSimilarityScore?: number;&lt;br&gt;
  progressScore?: number;&lt;br&gt;
  allowed: boolean;&lt;br&gt;
  stopReason?: string;&lt;br&gt;
};&lt;/p&gt;

&lt;p&gt;This gives you data that a commit cannot provide.&lt;/p&gt;

&lt;p&gt;You can now ask:&lt;/p&gt;

&lt;p&gt;Which tasks hit max steps?&lt;/p&gt;

&lt;p&gt;Which runs stopped because pricing was unknown?&lt;/p&gt;

&lt;p&gt;Which prompts repeated?&lt;/p&gt;

&lt;p&gt;Which models caused budget pressure?&lt;/p&gt;

&lt;p&gt;Which agent workflows produced commits only after many failed attempts?&lt;/p&gt;

&lt;p&gt;Which agents consumed budget without progress?&lt;/p&gt;

&lt;p&gt;That is runtime telemetry.&lt;/p&gt;

&lt;p&gt;Guardrails to implement first&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Max-step limits&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Agents should not run forever.&lt;/p&gt;

&lt;p&gt;if (stepCount &amp;gt;= maxSteps) {&lt;br&gt;
  return {&lt;br&gt;
    allowed: false,&lt;br&gt;
    reason: "max_steps_exceeded",&lt;br&gt;
  };&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;This is basic.&lt;/p&gt;

&lt;p&gt;It is also one of the highest-value controls.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Unknown pricing blocks&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the runtime cannot price the model, it cannot enforce a budget.&lt;/p&gt;

&lt;p&gt;if (!pricingCatalog[model]) {&lt;br&gt;
  return {&lt;br&gt;
    allowed: false,&lt;br&gt;
    reason: "unknown_model_pricing",&lt;br&gt;
  };&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;Do not guess.&lt;/p&gt;

&lt;p&gt;Fail closed.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Budget guards&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Budgets should exist at the task level, not only at the account level.&lt;/p&gt;

&lt;p&gt;if (estimatedNextCallCost &amp;gt; budgetRemaining) {&lt;br&gt;
  return {&lt;br&gt;
    allowed: false,&lt;br&gt;
    reason: "budget_exceeded",&lt;br&gt;
  };&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;A small refactor and a multi-hour migration should not share the same ceiling.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Retry-storm detection&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Retries are normal.&lt;/p&gt;

&lt;p&gt;Retry storms are not.&lt;/p&gt;

&lt;p&gt;if (retryCount &amp;gt; maxRetries &amp;amp;&amp;amp; recentErrorsAreSimilar(errors)) {&lt;br&gt;
  return {&lt;br&gt;
    allowed: false,&lt;br&gt;
    reason: "retry_storm_detected",&lt;br&gt;
  };&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;The goal is not to ban retries.&lt;/p&gt;

&lt;p&gt;The goal is to stop blind repetition.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Prompt-loop detection&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the current prompt is almost the same as previous failed prompts, the agent may be stuck.&lt;/p&gt;

&lt;p&gt;if (similarToRecentPrompt(currentPrompt, previousPrompts)) {&lt;br&gt;
  return {&lt;br&gt;
    allowed: false,&lt;br&gt;
    reason: "similar_prompt_loop",&lt;br&gt;
  };&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;Even a simple similarity check can catch obvious waste.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;No-progress detection&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A run can be active and still not moving.&lt;/p&gt;

&lt;p&gt;Track progress signals:&lt;/p&gt;

&lt;p&gt;tests passing&lt;br&gt;
errors decreasing&lt;br&gt;
files changing meaningfully&lt;br&gt;
checklist items completing&lt;br&gt;
user-defined success criteria improving&lt;/p&gt;

&lt;p&gt;If those signals do not change after several steps, stop.&lt;/p&gt;

&lt;p&gt;Why this matters now&lt;/p&gt;

&lt;p&gt;GitHub has already said Copilot moved to usage-based billing on June 1, 2026, with usage calculated from token consumption including input, output, and cached tokens. GitHub also described Copilot as moving from an in-editor assistant into an agentic platform capable of long, multi-step coding sessions across repositories.&lt;/p&gt;

&lt;p&gt;That means agent runtime behavior increasingly has direct cost impact.&lt;/p&gt;

&lt;p&gt;A loop is no longer just a UX problem.&lt;/p&gt;

&lt;p&gt;It is a billing problem.&lt;/p&gt;

&lt;p&gt;A retry storm is not just noisy.&lt;/p&gt;

&lt;p&gt;It is spend.&lt;/p&gt;

&lt;p&gt;A prompt loop is not just inefficient.&lt;/p&gt;

&lt;p&gt;It is measurable waste.&lt;/p&gt;

&lt;p&gt;Where AI CostGuard fits&lt;/p&gt;

&lt;p&gt;AI CostGuard is the local-first TypeScript / Node.js runtime safety layer I’m building for this problem.&lt;/p&gt;

&lt;p&gt;It focuses on stopping agent failures before provider calls execute:&lt;/p&gt;

&lt;p&gt;retry storms&lt;br&gt;
prompt loops&lt;br&gt;
max-step explosions&lt;br&gt;
no-progress runs&lt;br&gt;
budget overruns&lt;br&gt;
unknown model pricing&lt;br&gt;
runaway agent behavior&lt;/p&gt;

&lt;p&gt;The key design question is simple:&lt;/p&gt;

&lt;p&gt;Should this next provider call be allowed?&lt;/p&gt;

&lt;p&gt;If the answer is no, the runtime should return a structured stop reason before the call happens.&lt;/p&gt;

&lt;p&gt;Takeaway&lt;/p&gt;

&lt;p&gt;The new arXiv paper shows that even detecting AI coding-agent activity in repositories requires multiple signals.&lt;/p&gt;

&lt;p&gt;That lesson applies directly to runtime engineering.&lt;/p&gt;

&lt;p&gt;Do not wait for the commit.&lt;/p&gt;

&lt;p&gt;Do not wait for the dashboard.&lt;/p&gt;

&lt;p&gt;Do not wait for the invoice.&lt;/p&gt;

&lt;p&gt;Instrument the loop.&lt;br&gt;
Add one pre-call decision log to your agent runtime before adding another dashboard.&lt;br&gt;
&lt;a href="https://github.com/salimassili62-afk/ai-costguard" rel="noopener noreferrer"&gt;https://github.com/salimassili62-afk/ai-costguard&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>javascript</category>
      <category>api</category>
      <category>llm</category>
    </item>
    <item>
      <title>Usage-Based AI Coding Needs Runtime Budgets, Not Just Billing Dashboards</title>
      <dc:creator>Assili Salim</dc:creator>
      <pubDate>Thu, 25 Jun 2026 08:29:05 +0000</pubDate>
      <link>https://dev.to/assili_salim_e3c07f9954de/usage-based-ai-coding-needs-runtime-budgets-not-just-billing-dashboards-50d3</link>
      <guid>https://dev.to/assili_salim_e3c07f9954de/usage-based-ai-coding-needs-runtime-budgets-not-just-billing-dashboards-50d3</guid>
      <description>&lt;p&gt;The signal&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;That matters for engineering.&lt;/p&gt;

&lt;p&gt;A long, multi-step coding session is not a chat message.&lt;/p&gt;

&lt;p&gt;It is a loop.&lt;/p&gt;

&lt;p&gt;And loops need runtime budgets.&lt;/p&gt;

&lt;p&gt;Why this is not just a pricing issue&lt;/p&gt;

&lt;p&gt;Usage-based billing makes one thing very clear:&lt;/p&gt;

&lt;p&gt;runtime behavior has financial consequences.&lt;/p&gt;

&lt;p&gt;A coding agent can spend because it is useful.&lt;/p&gt;

&lt;p&gt;It can also spend because it is stuck.&lt;/p&gt;

&lt;p&gt;The failure mode usually does not look dramatic.&lt;/p&gt;

&lt;p&gt;It looks like this:&lt;/p&gt;

&lt;p&gt;inspect files&lt;br&gt;
call a model&lt;br&gt;
edit code&lt;br&gt;
run tests&lt;br&gt;
fail&lt;br&gt;
add context&lt;br&gt;
call the model again&lt;br&gt;
retry with a similar prompt&lt;br&gt;
switch strategy&lt;br&gt;
call another model&lt;br&gt;
run tests again&lt;br&gt;
keep going&lt;/p&gt;

&lt;p&gt;Every step can look reasonable.&lt;/p&gt;

&lt;p&gt;The whole run can still be waste.&lt;/p&gt;

&lt;p&gt;That is why a billing dashboard is not enough.&lt;/p&gt;

&lt;p&gt;A dashboard tells you what happened after usage exists.&lt;/p&gt;

&lt;p&gt;A runtime budget decides whether the next call should happen.&lt;/p&gt;

&lt;p&gt;A naive agent loop&lt;/p&gt;

&lt;p&gt;A simple coding agent loop might look like this:&lt;/p&gt;

&lt;p&gt;while (!task.done) {&lt;br&gt;
  const response = await provider.call({&lt;br&gt;
    model: task.model,&lt;br&gt;
    messages: task.messages,&lt;br&gt;
  });&lt;/p&gt;

&lt;p&gt;task = await applyAgentStep(task, response);&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;This is easy to understand.&lt;/p&gt;

&lt;p&gt;It is also dangerous.&lt;/p&gt;

&lt;p&gt;There is no budget.&lt;/p&gt;

&lt;p&gt;No max-step limit.&lt;/p&gt;

&lt;p&gt;No retry-storm detection.&lt;/p&gt;

&lt;p&gt;No prompt-loop detection.&lt;/p&gt;

&lt;p&gt;No known-pricing check.&lt;/p&gt;

&lt;p&gt;No no-progress detection.&lt;/p&gt;

&lt;p&gt;If this loop gets stuck, it keeps spending until something else stops it.&lt;/p&gt;

&lt;p&gt;That “something else” might be a provider limit, a user interruption, an admin cap, or the bill.&lt;/p&gt;

&lt;p&gt;None of those are ideal runtime controls.&lt;/p&gt;

&lt;p&gt;Add a pre-call decision&lt;/p&gt;

&lt;p&gt;A safer pattern puts a guard before the provider call:&lt;/p&gt;

&lt;p&gt;const decision = guard.beforeCall({&lt;br&gt;
  runId: task.id,&lt;br&gt;
  model: task.model,&lt;br&gt;
  messages: task.messages,&lt;br&gt;
  stepCount: task.steps.length,&lt;br&gt;
  retryCount: task.retryCount,&lt;br&gt;
  budgetRemaining: task.budgetRemaining,&lt;br&gt;
  previousPrompts: task.previousPrompts,&lt;br&gt;
  progressState: task.progress,&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;if (!decision.allowed) {&lt;br&gt;
  return {&lt;br&gt;
    status: "stopped",&lt;br&gt;
    reason: decision.reason,&lt;br&gt;
    error: decision.error,&lt;br&gt;
  };&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;const response = await provider.call({&lt;br&gt;
  model: task.model,&lt;br&gt;
  messages: task.messages,&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;The exact API does not matter.&lt;/p&gt;

&lt;p&gt;The placement matters.&lt;/p&gt;

&lt;p&gt;The check happens before the provider call.&lt;/p&gt;

&lt;p&gt;That means the runtime can stop unsafe execution before token usage is created.&lt;/p&gt;

&lt;p&gt;What should the runtime check?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Known model pricing&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the runtime does not know the model price, it cannot enforce a reliable budget.&lt;/p&gt;

&lt;p&gt;Do not guess.&lt;/p&gt;

&lt;p&gt;Fail closed.&lt;/p&gt;

&lt;p&gt;if (!pricingCatalog.has(model)) {&lt;br&gt;
  throw new Error(&lt;code&gt;Unknown pricing for model: ${model}&lt;/code&gt;);&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;In usage-based billing, model identity is part of the cost contract.&lt;/p&gt;

&lt;p&gt;A typo, alias, fallback, or wrapper mismatch can break assumptions.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Task-level budget&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Monthly limits are useful.&lt;/p&gt;

&lt;p&gt;But agent runs also need task-level budgets.&lt;/p&gt;

&lt;p&gt;A code review task should not have the same spend ceiling as a multi-hour migration.&lt;/p&gt;

&lt;p&gt;if (estimatedNextCallCost &amp;gt; budgetRemaining) {&lt;br&gt;
  return {&lt;br&gt;
    allowed: false,&lt;br&gt;
    reason: "budget_exceeded",&lt;br&gt;
  };&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;This lets the agent stop before the next call.&lt;/p&gt;

&lt;p&gt;Not after.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Max-step protection&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Agent loops need step limits.&lt;/p&gt;

&lt;p&gt;if (stepCount &amp;gt;= maxSteps) {&lt;br&gt;
  return {&lt;br&gt;
    allowed: false,&lt;br&gt;
    reason: "max_steps_exceeded",&lt;br&gt;
  };&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;This is basic, but important.&lt;/p&gt;

&lt;p&gt;An agent that cannot finish within a reasonable number of steps may be stuck.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Retry-storm detection&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Retries are useful.&lt;/p&gt;

&lt;p&gt;Retry storms are not.&lt;/p&gt;

&lt;p&gt;if (retryCount &amp;gt;= maxRetries &amp;amp;&amp;amp; lastErrorsAreSimilar(task.errors)) {&lt;br&gt;
  return {&lt;br&gt;
    allowed: false,&lt;br&gt;
    reason: "retry_storm_detected",&lt;br&gt;
  };&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;The goal is not to remove retries.&lt;/p&gt;

&lt;p&gt;The goal is to prevent blind retries.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Prompt-loop detection&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Agents sometimes send nearly the same prompt repeatedly.&lt;/p&gt;

&lt;p&gt;Small wording changes can hide the fact that the run is not moving.&lt;/p&gt;

&lt;p&gt;if (isSimilarToRecentPrompt(currentPrompt, previousPrompts)) {&lt;br&gt;
  return {&lt;br&gt;
    allowed: false,&lt;br&gt;
    reason: "similar_prompt_loop",&lt;br&gt;
  };&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;Prompt-loop detection is not perfect.&lt;/p&gt;

&lt;p&gt;But even a simple version can catch obvious waste.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;No-progress detection&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A run can be active and still useless.&lt;/p&gt;

&lt;p&gt;The agent may be editing files, calling tools, and producing logs.&lt;/p&gt;

&lt;p&gt;But the task may not be converging.&lt;/p&gt;

&lt;p&gt;A runtime should track progress signals:&lt;/p&gt;

&lt;p&gt;tests passing&lt;br&gt;
errors decreasing&lt;br&gt;
files changing meaningfully&lt;br&gt;
plan steps completing&lt;br&gt;
final answer getting closer&lt;br&gt;
user-defined success criteria&lt;/p&gt;

&lt;p&gt;If the run consumes steps without progress, stop.&lt;/p&gt;

&lt;p&gt;Why admin caps are not enough&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;That is useful.&lt;/p&gt;

&lt;p&gt;But admin caps operate at a broad level.&lt;/p&gt;

&lt;p&gt;They do not know why a single agent run is stuck.&lt;/p&gt;

&lt;p&gt;They do not know whether a prompt is repeating.&lt;/p&gt;

&lt;p&gt;They do not know whether this specific task is worth another call.&lt;/p&gt;

&lt;p&gt;That decision belongs closer to the runtime.&lt;/p&gt;

&lt;p&gt;Where AI CostGuard fits&lt;/p&gt;

&lt;p&gt;AI CostGuard is the local-first TypeScript runtime layer I’m building for this problem.&lt;/p&gt;

&lt;p&gt;It is designed to stop expensive agent failure modes before provider calls execute:&lt;/p&gt;

&lt;p&gt;retry storms&lt;br&gt;
prompt loops&lt;br&gt;
max-step explosions&lt;br&gt;
no-progress runs&lt;br&gt;
budget overruns&lt;br&gt;
unknown model pricing&lt;br&gt;
runaway agent behavior&lt;/p&gt;

&lt;p&gt;It is not a billing dashboard.&lt;/p&gt;

&lt;p&gt;It is not a cloud control plane.&lt;/p&gt;

&lt;p&gt;It is not a hard security boundary.&lt;/p&gt;

&lt;p&gt;It is a pre-call kill switch for agent cost and loop failures.&lt;/p&gt;

&lt;p&gt;The takeaway&lt;/p&gt;

&lt;p&gt;Usage-based AI coding changes the engineering model.&lt;/p&gt;

&lt;p&gt;You cannot only ask:&lt;/p&gt;

&lt;p&gt;“How much does this tool cost per month?”&lt;/p&gt;

&lt;p&gt;You have to ask:&lt;/p&gt;

&lt;p&gt;“What does my agent do when it gets stuck?”&lt;/p&gt;

&lt;p&gt;For agentic coding, cost control belongs in the runtime.&lt;/p&gt;

&lt;p&gt;Before the provider call.&lt;/p&gt;

&lt;p&gt;Before the bill.&lt;/p&gt;

&lt;p&gt;Before the loop becomes waste.&lt;br&gt;
&lt;a href="https://github.com/salimassili62-afk/ai-costguard" rel="noopener noreferrer"&gt;https://github.com/salimassili62-afk/ai-costguard&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>api</category>
      <category>llm</category>
      <category>agents</category>
    </item>
  </channel>
</rss>
