# AI Agents Need Runtime State Checks, Not Just Better Prompts

> Source: <https://dev.to/assili_salim_e3c07f9954de/ai-agents-need-runtime-state-checks-not-just-better-prompts-5cdp>
> Published: 2026-07-11 04:46:23+00:00

Claude Code’s July 8 changelog is a useful reminder of what production agent engineering actually looks like.

The interesting parts are not model benchmarks.

They are state-management fixes.

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. �

Claude

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. �

Claude

These are not flashy issues.

They are the issues that show up when agents become real runtime systems.

Agents are state machines

A coding agent is not only a model call.

It has state:

current step

current task status

previous messages

tool results

approvals

turn limits

retry count

background job state

user input state

stop reason

If that state is wrong, the agent can behave incorrectly even if the model response is good.

A stale “Running” state can mislead the user.

A lost message can cause the agent to continue without new context.

A fake approval inside a transcript can be dangerous if treated as real input.

A max-turn limit can stop the model while leaving the surrounding runtime ambiguous.

This is why agent reliability is not just prompt quality.

It is runtime correctness.

The dangerous pattern: activity without permission

A common agent failure is not a crash.

It is continuation.

The agent keeps moving because nothing clearly told it to stop.

That can happen when:

the task status is stale

the stop condition is unclear

the agent hits a max-turn limit but the runtime still continues

a retry policy ignores the reason for failure

generated text is mistaken for user approval

background state is out of sync

For cost-sensitive agents, this matters.

Every unnecessary continuation can become another provider call.

The invoice will show the usage later.

The runtime should prevent the obviously wrong continuation before it happens.

Treat every provider call as an admission decision

A safer pattern is to check runtime state before every provider call.

type AgentRuntimeState = {

runId: string;

status: "working" | "needs_input" | "stopped" | "failed" | "completed";

stepCount: number;

maxSteps: number;

retryCount: number;

budgetRemaining: number;

model: string;

modelPriceKnown: boolean;

hasRealUserApproval: boolean;

lastStopReason?: string;

recentProgress: boolean;

};

Then make a decision before calling the provider.

function beforeProviderCall(state: AgentRuntimeState) {

if (state.status === "needs_input") {

return { allowed: false, reason: "needs_user_input" };

}

if (!state.hasRealUserApproval) {

return { allowed: false, reason: "missing_real_approval" };

}

if (state.stepCount >= state.maxSteps) {

return { allowed: false, reason: "max_steps_exceeded" };

}

if (!state.modelPriceKnown) {

return { allowed: false, reason: "unknown_model_pricing" };

}

if (state.budgetRemaining <= 0) {

return { allowed: false, reason: "budget_exceeded" };

}

if (!state.recentProgress && state.retryCount > 0) {

return { allowed: false, reason: "no_progress_retry" };

}

return { allowed: true };

}

The exact API is not important.

The placement is important.

The check happens before the provider call.

Generated approval is not approval

The approval issue is especially important.

An agent transcript may contain text that looks like permission.

That does not mean a human gave permission.

For production systems, approval should be an explicit runtime event.

Not a string inside generated text.

A safer shape:

type ApprovalEvent = {

runId: string;

approvedBy: "human" | "policy";

approvedAt: number;

scope: "tool_call" | "provider_call" | "file_edit";

};

Then the runtime checks the approval event, not the transcript.

function hasValidApproval(events: ApprovalEvent[], scope: ApprovalEvent["scope"]) {

return events.some(

event =>

event.scope === scope &&

(event.approvedBy === "human" || event.approvedBy === "policy")

);

}

This prevents a model-generated sentence from becoming operational permission.

Max-turns should produce a stop reason

A turn limit should not be a vague failure.

It should create a structured stop reason.

if (turnCount >= maxTurns) {

return {

status: "stopped",

reason: "max_turns_exceeded",

nextAction: "requires_human_review",

};

}

This makes the runtime easier to debug.

It also prevents accidental continuation.

If the agent stopped because it hit a limit, the next provider call should not happen automatically.

Status should control execution

Background agents make status accuracy critical.

If the UI says “Running,” but the task is actually blocked, the user may assume progress is happening.

If the runtime says “working,” but the agent needs input, the system may continue incorrectly.

A simple rule helps:

The runtime status should be the source of truth for execution.

if (task.status !== "working") {

return {

allowed: false,

reason: `task_status_${task.status}`

,

};

}

Do not let the provider call path ignore the task state machine.

Where AI CostGuard fits

This is the kind of failure mode I’m building AI CostGuard around.

AI CostGuard is a local-first TypeScript/Node.js pre-call runtime guard for AI-agent applications.

It focuses on risky provider calls before execution:

retry storms

prompt loops

max-step explosions

runaway agent execution

unknown model pricing

budget overruns

uncontrolled provider calls

It is not a billing ledger.

It is not a hard security boundary.

It does not replace provider dashboards.

The goal is narrower: give the runtime a way to say “this next call should not execute.”

Takeaway

Agent failures are becoming runtime failures.

Not just bad prompts.

Not just weak models.

State bugs.

Approval bugs.

Stale status.

Lost messages.

Unclear stop reasons.

If your agent can run in the background, it needs a real state machine.

And before every provider call, that state machine should decide whether the call is still allowed.
