Skip to content
JavaAgentic

Type at least two characters. Try “RAG”, “pgvector” or “tool calling”.

Productionizing Agentic Systems

Take agents to production: per-run budgets and step caps, guardrails, durable execution, scaling on the JVM, cost control and the operational patterns that keep agents from causing incidents.

Expert4 min readUpdated
On this page

The gap between an agent that demos well and one you can leave running is entirely operational: budgets, guardrails, durability and cost control. This tutorial covers the production concerns that, skipped, turn an impressive prototype into a 2am incident.

Key Takeaways

  • Enforce a hard per-run budget on steps and tokens — agents fail by looping.
  • Guardrails are deterministic code around the agent: timeouts, authorization, validation, approval.
  • Durable execution lets long tasks survive restarts and crashes.
  • Scale on concurrency (virtual threads), externalise state, bound per-run resources.

Budgets: the first guardrail

Agents fail by looping — retrying a failing tool with variations, each attempt re-sending the whole conversation, cost growing quadratically. A budget is the hard stop.

RunBudget.java
public class RunBudget {
 
    private final int maxSteps;
    private final long maxTokens;
    private int steps;
    private long tokens;
 
    // Called before every model call. Throws when a limit is hit, converting an
    // unbounded runaway into a bounded, logged failure.
    public synchronized void checkAndRecord(long tokensThisStep) {
        if (++steps > maxSteps) {
            throw new BudgetExceededException("step limit " + maxSteps + " reached");
        }
        tokens += tokensThisStep;
        if (tokens > maxTokens) {
            throw new BudgetExceededException("token budget " + maxTokens + " reached");
        }
    }
}

The full guardrail stack

Guardrails are deterministic code wrapped around the probabilistic agent.

Each is ordinary, deterministic code:

  • Budgets — step and token caps (above).
  • Timeouts — on every model and tool call; a hung dependency must not hang the agent.
  • Authorization — every action checked in code against the authenticated principal, never trusted from model output. See securing AI applications.
  • Validation — inputs bounded, outputs checked before they trigger anything.
  • Human approval — for irreversible actions. See human-in-the-loop systems.
  • Trajectory logging — every step, for debugging and audit.

Durable execution

An agent task that takes minutes should survive a restart. Persist state so it can resume:

Resumable agent state
record AgentState(String runId, String goal, List<Step> completedSteps, String status) {}
 
public AgentState resume(String runId) {
    AgentState state = stateStore.load(runId);
    if (state.status().equals("COMPLETED")) {
        return state;
    }
    // Continue from where it stopped, not from the beginning. A crash or deploy
    // mid-task loses no work.
    return continueFrom(state);
}

For serious durability, a workflow engine (Temporal and similar) gives you persistence, retries and resumption as infrastructure rather than something you hand-roll. For simpler cases, a database-backed state machine suffices.

Scaling on the JVM

Agents are I/O-bound — mostly waiting on model and tool calls — so they scale beautifully on Java 21 virtual threads:

Many concurrent agent runs
// Each run blocks on I/O most of the time; a blocked virtual thread costs
// almost nothing, so thousands of concurrent runs fit on a small instance.
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    runs.forEach(run -> executor.submit(() -> agentService.execute(run)));
}

Scale horizontally on concurrency, not CPU (agents barely use CPU), and externalise agent state so any instance can handle any run. See Docker & Kubernetes for Java developers.

Cost control in production

  • Per-feature cost metrics with alerts on the hourly rate — catch runaways in minutes, not on the invoice. See Spring AI observability.
  • Model routing — cheap models for easy steps, expensive ones only where needed.
  • Semantic caching — avoid re-answering near-identical requests. See AI caching strategies.
  • Budget alerts — a daily spend cap with a page when approached.

The pre-production checklist

Before an agent runs unattended in production:

  • Hard step and token budget, enforced before every call
  • Timeout on every model and tool call
  • Every action authorized in code
  • Irreversible actions behind human approval
  • Input bounded, output validated
  • Full trajectory logging
  • Per-feature cost metrics with alerting
  • State externalised; runs resumable
  • Evaluated on a fixed task set for outcome and safety
  • A kill switch to disable the agent instantly

Next

Frequently Asked Questions

How do you stop an AI agent from running up huge costs?
Enforce a hard per-run budget on both steps and tokens, checked before every model call, that aborts the run when exceeded. Agents fail by looping — retrying a failing tool while re-sending the whole conversation — so cost grows quadratically without a cap. A budget converts an unbounded incident into a bounded, logged one.
What guardrails does a production agent need?
Step and token budgets, timeouts on every tool and model call, authorization in code for every action, human approval for irreversible actions, input and output validation, and full trajectory logging. Guardrails are ordinary deterministic code around the probabilistic agent — they are what make it safe to run unattended.
What is durable execution for agents?
Persisting an agent's state so a long-running task survives restarts, crashes and deployments — the agent can resume from where it stopped rather than starting over. It matters for agents whose tasks take minutes or hours, where losing progress on a crash is costly. Frameworks like Temporal, or a database-backed state machine, provide it.
How do you scale agents on the JVM?
Agents are I/O-bound — they spend most of their time waiting on model and tool calls — so Java 21 virtual threads let you run many concurrent agent runs cheaply. Scale horizontally on concurrency rather than CPU, keep agent state externalised so any instance can handle any run, and bound per-run resources so one runaway agent cannot starve others.

Related tutorials