Home / Articles

Running AWS AI Agents at Scale: Tokens, Spend, Limits and Autonomy

Token counting, spend monitoring, context-limit recovery and durable autonomous workflows for AWS agents, each explained through patterns full-stack JavaScript developers know.

Publisher: REACTAPP.TOP8 min read1929 words

This article is published in English.

Sources: 🏗️ Building Production-Ready Enterprise AI Agents on AWS — Part 3: Tokenization, Monitoring, Context Limits & Autonomous Workflows

An agent that is scoped sensibly and backed by validation and retrieval still has to survive its first month in production. What decides that is mostly unglamorous: how you count tokens, how you watch spend, what happens when the context window fills, and whether a long-running autonomous loop is built like a real workflow engine. If you come from full-stack JavaScript on AWS, each of these maps neatly onto something you already know, and this walk-through uses those parallels throughout.

Tokenization is the serialization layer

A web app serializes data before it crosses the wire, with JSON.stringify or protobuf. A language model does the equivalent for text: before it sees anything, the text is cut into tokens, which are sub-word fragments and not characters or whole words.

"Enterprise agents scale."
   → ["Enterprise", " agents", " scale", "."]   (≈4 tokens, illustrative)

The reason to care as an engineer comes down to three things.

  • Tokens are what you're billed in, comparable to Lambda's GB-seconds. Input and output both cost money, and since Bedrock prices differ per model, token count drives the AWS invoice directly.
  • Tokens also set your payload ceiling, like a request-body cap such as express.json({ limit: '5mb' }). In this case the ceiling is the context window, which runs to hundreds of thousands of tokens on current models and keeps growing, so look up the real limit for your model and don't guess.
  • Tokenization isn't uniform across languages. Non-English text frequently needs more tokens per word, which matters for multi-region products.

In Node.js, counting before sending feels like measuring a payload before a fetch:

// Node.js: counting tokens before sending a request
// (conceptually like calculating payload size before a fetch call)
import { encoding_for_model } from "tiktoken";

const enc = encoding_for_model("gpt-4o");
const tokens = enc.encode(userPrompt);
console.log(`Payload size: ${tokens.length} tokens`); // like Content-Length
enc.free();

On Bedrock you can skip the third-party tokenizer. The CountTokens API counts server-side for supported Claude models, takes the same message structure as the Converse API, and lets you pre-check a request's token count without paying for inference:

# Python (boto3): pre-flight token count via Bedrock, no separate tokenizer library needed
response = bedrock_runtime.count_tokens(
    modelId="anthropic.claude-3-5-sonnet-20241022-v2:0",
    input={"converse": {"messages": [{"role": "user", "content": [{"text": userPrompt}]}]}}
)
print(f"Input tokens: {response['inputTokens']}")

The lesson: measure token count the way you'd measure HTTP payload size, before sending and not after you're billed or rejected.

Watching token spend like CloudWatch for LLM bills

The previous section covered a single request. The aggregate view asks what every agent spent, per tenant and per day, and you want to find out before finance does. No one would put an Express API on AWS live without dashboards and alarms, and token monitoring deserves the same treatment.

The golden signals for an agent:

  • Prompt tokens per request, like inbound payload size.
  • Completion tokens per request, like response size.
  • Cumulative tokens in a session, which is the session's memory footprint.
  • Cost per agent, user or tenant, the same metering you'd do for per-tenant database usage in multi-tenant SaaS.
  • p95 and p99 latency of agent calls, held to your usual SLOs. State the SLO explicitly and fall back to a cached or best-effort path when it's at risk.
  • Spend rate in tokens per minute, comparable to rate limiting on requests per second.

Concrete steps on AWS:

  • Send custom CloudWatch metrics for prompt tokens, completion tokens and dollar cost (prompt_tokens, completion_tokens, cost_usd), labelled with the agent's name and the tenant.
  • Enable prompt caching. Bedrock can reuse long, stable prefixes such as system prompts, tool definitions and few-shot examples at a large discount and with lower latency. Put immutable content first, otherwise you pay full price for identical tokens on every call.
  • Create CloudWatch alarms that notify through SNS when daily spend passes a threshold, just like AWS billing alarms.
  • Write every agent call to S3, via Kinesis Firehose or direct writes, to keep an audit trail. SOC 2 and enterprise buyers will ask for it.
  • A back-of-the-envelope estimate works as it does for Lambda: add prompt and completion tokens, multiply by the model's price, then add what retrieval and infrastructure cost per session.

If you can't say how much Agent X cost last Tuesday, split by customer, you're demo-ready and not production-ready, and those are far apart.

Context-limit errors: circuit breaker plus pagination

Every context window has a ceiling. Long conversations, enormous tool outputs and bloated retrieval chunks can all exceed it. It's the 413 Payload Too Large of the agent world, only worse, because it can strike deep in a multi-step task.

Recovery strategies come straight from backend engineering:

  1. Summarising and compacting. Compress old turns periodically, like log rotation.
  2. Sliding window or truncation. Drop the oldest turns, as you'd page through a large query result and not load it all.
  3. Offloading to retrieval. Keep material outside the prompt and fetch it on demand, in the spirit of lazy loading.
  4. Splitting into sub-agents. Divide one huge task into smaller calls with fresh contexts, like turning a monolithic batch job into a queue of small ones.
  5. Degrading gracefully. If the overflow can't be recovered mid-task, fail with a structured, resumable checkpoint and never with a quiet hallucination.

Here's the catch-and-compact pattern in code, much like catching a database timeout:

# Conceptual: catching a context-limit error, like catching a DB timeout
try:
    result = agent.run(task, conversation_history)
except AgentError as err:
    if err.code == "context_length_exceeded":
        # Keep the system prompt intact — compact the *conversation*, never the identity.
        # Summarizing everything (including system instructions) is how agents quietly
        # "forget" their role mid-task, the same way you'd never let a cache-eviction
        # policy drop your app's config alongside its session data.
        compacted = summarize(conversation_history, preserve="system_prompt")
        result = agent.run(task, compacted)
    else:
        raise  # don't swallow unrelated errors

Context-limit errors are expected and not exceptional. Treat them like rate limits and timeouts: retries with backoff, checkpoints kept in DynamoDB and graceful degradation. An empty catch that swallows the error is never acceptable.

Autonomous workflows as state machines

An autonomous agent doesn't answer once. It plans, acts, observes and loops until a goal is reached. Anyone who has built an XState machine, a Redux saga or an AWS Step Functions flow for a multi-step checkout already knows the shape. This is the familiar ReAct-style loop of plan, act, observe and evaluate, with exits for completion and for escalation to a human, and it should be engineered like a durable workflow engine.

  • Idempotent steps. Re-running a step mustn't charge a customer twice or send a duplicate email.
  • Explicit stop conditions. Cap iterations, cost and time, or you've built an infinite while(true) with a credit card attached.
  • Human escalation. As with a saga's compensating transaction, low confidence or high stakes should route to a person and not execute automatically.
  • Persistent state. Checkpoint each step in DynamoDB or Step Functions state so a mid-workflow crash resumes and doesn't lose a transaction. Add a DynamoDB TTL to session items so stale conversations expire, the same discipline as expiring a Redis session key.
  • Fallback models. If the primary model, say Claude on Bedrock, times out or throttles, a circuit breaker should fail over to a secondary such as Amazon Nova and not show the user an error. It's the same idea as an HTTP client failing over to a backup region.
  • Model governance. Handle a model swap like any production deploy: canary it on part of the traffic, require sign-off and keep a rollback path. "We just changed the model" shouldn't be an unapproved change.
  • A kill switch. Keep a way, manual or threshold-triggered, to pause the whole agent fleet at once, like a feature-flag kill switch for a runaway release.

A checkpoint is just another resumable-job record in DynamoDB, which Step Functions reads on restart to continue from the last good step:

{
  "pk": "session#8f2c1a",
  "sk": "checkpoint#2026-08-23T10:15:00Z",
  "step": "plan",
  "hop_count": 2,
  "cost_so_far_usd": 0.014,
  "state": { "goal": "resolve billing dispute", "last_action": "lookup_charge" },
  "ttl": 1788084900
}

Don't build autonomy as a while loop wrapped around an LLM call. Build a durable, observable, resumable state machine, which is what it really is. Step Functions fits that need well if you want it ready-made.

One more layer sits above all of this: evals. Guardrail rules, retrieval quality and grounding all need regression tests just as application code does. An eval suite replays a fixed set of inputs against the agent and scores outputs against expected behaviour, so a prompt tweak or a model swap can't silently degrade production. Ragas scores retrieval-specific measures such as faithfulness and context relevance, while Promptfoo and Braintrust cover broader prompt and output testing. Wire your choice into CI the way you would pytest, so a failing eval blocks the deploy and nobody has to inspect outputs by eye afterwards.

Production readiness checklist

  • Every agent has one testable responsibility and narrowly scoped IAM permissions.
  • Handoffs carry structured context across a durable bus (SQS or EventBridge) and are protected against loops.
  • Input guardrails and output guardrails are separate, composable and versioned on their own.
  • Retrieval quality has its own precision and recall measurements and isn't lumped into "the AI is wrong".
  • Anything high-stakes passes hallucination checks: grounding, confidence scoring or a human gate.
  • Token usage is measured before a call goes out, not only billed afterwards.
  • CloudWatch dashboards break cost down per tenant and per agent, with SNS budget alerts.
  • A defined recovery plan exists for context-limit errors: compact, truncate or offload.
  • Autonomous loops have hard ceilings on iterations, elapsed time and spend.
  • Irreversible actions such as refunds, emails and deletions need a human approval step.
  • Agent decisions are fully recorded in S3 or CloudWatch for compliance.
  • Automated evals run in CI before any prompt or model change ships, a gate you'd never skip for application code.
  • Staging has been chaos-tested with simulated model timeouts, corrupted retrieval results and handoff loops, so the recovery paths are proven before production relies on them.

What it adds up to

Each agent concept has a counterpart in the full-stack world you know. Agents are microservices with judgment, and handoffs are durable, traceable, hop-limited routing. Guardrails are layered middleware, retrieval is a smarter query layer, and hallucinations are caught by grounding and verification and not by asking nicely. Tokens double as payload size and as the unit you pay in. Spend monitoring is the CloudWatch of model usage. A full context window is a predictable failure that needs a recovery plan, and autonomy is a durable state machine, nothing like a bare while loop.

Enterprise agents rarely fail in production because the model isn't smart enough. They fail because teams skip the plain distributed-systems discipline they'd never skip for an ordinary API: validation, observability, retries, idempotency, grounding and graceful degradation. If you've shipped a resilient JavaScript application on AWS, you have the instincts already. What's left is pointing those instincts at an unfamiliar, unpredictable dependency that answers in sentences.