AI agent cost control on an always-on VPS
An unattended agent bills every loop with nobody watching. Hard caps, task budgets, prompt caching, batching, and the usage fields that show the spend.
How to keep an always-on AI agent from running up a bill
AI agent cost control on a VPS (virtual private server) is about ceilings you set before the agent starts, because nobody is watching the meter while it runs. Cap every response with max_tokens, bound the loop iterations in your own code, cache the part of the prompt that never changes, and log every response's usage numbers to see which job spends. The server rental is a fixed monthly price. The model API is metered per token, and an unattended loop is very good at spending tokens quietly.
This assumes an agent that already exists and calls the Messages API from a box you own. Building an AI agent with Claude on a VPS covers the machinery itself.
Why an unattended agent is a different cost shape
An interactive session has a human in it. When the model goes down a wrong path or reads a 40,000-line log, the person watching stops it. An unattended agent has no such brake: it runs until the loop ends, then a timer starts it again.
Frequency is the multiplier people miss. A job on a five-minute schedule runs 288 times a day and about 8,640 times a month. Whatever one run costs, that is the figure you multiply. Many "always-on" agents do not need to be on. They need to answer within some number of minutes, which is a schedule.
An agent also pays for things a chat window does not.
- Tool definitions ride along on every request. The tool-use system prompt costs 290 tokens on Claude Opus 4.8 with
tool_choiceofautoornone, and 410 withanyortool. The bash tool adds 325 more. Every MCP server you attach adds its schemas to that weight, MCP being the model context protocol. - Tool results are input tokens. A command that prints 8,000 lines puts 8,000 lines into the next request, and into every request after it in that turn.
- Fetched pages are input tokens. An average 10 kB web page is roughly 2,500 tokens and a 500 kB research PDF roughly 125,000.
max_content_tokenstruncates the text ones only, because it "applies to text content, not to binary content such as PDFs". Bound a PDF withmax_usesandallowed_domainsinstead. - Web search is priced per search, at $10 per 1,000 searches, however many results come back. A search that errors is not billed.
None of that is expensive once. All of it is expensive 8,640 times.
Hard ceilings and soft ceilings solve different problems
max_tokens is enforced. It is a hard cap on the total output of one request, thinking and response text together. Claude never generates past it, and the model cannot see the number. Reaching it gives stop_reason: "max_tokens" and a truncated answer. The catch for agents: every request in a tool-use loop carries its own max_tokens, so it bounds one response and not the task. Ten tool calls at 4,000 is a 40,000-token ceiling for the turn.
A task budget is advisory. task_budget sits inside output_config and tells the model how many tokens it has for the whole agentic loop, counting thinking, tool calls, tool results and output.
resp = client.beta.messages.create(
model="claude-opus-4-8",
max_tokens=4096,
betas=["task-budgets-2026-03-13"],
output_config={"task_budget": {"type": "tokens", "total": 64000}},
messages=messages,
)"Task budgets are a soft hint, not a hard cap." Claude may exceed one mid-action, and the enforced limit on output is still max_tokens. "The countdown is visible only to the model", and responses carry no remaining-budget field. The minimum accepted task_budget.total is 20,000 tokens, and less returns a 400 error. A budget too small for the work produces refusal-like behaviour, so the model scopes the task down or stops early.
One detail costs money instead of saving it. If your client decrements task_budget.remaining on each follow-up request, the changed value invalidates any cached prefix containing it. Set it once, on the first request.
Task budgets are in beta on Claude Fable 5.1, Claude Fable 5, Claude Opus 5.5, Claude Opus 5, Claude Opus 4.8, Claude Opus 4.7, Claude Sonnet 5.5 and Claude Haiku 5.5. Claude Sonnet 5 and Claude Haiku 4.5 are listed as Not supported, and task budgets do not apply to Claude Code, so a Claude Code session detached in tmux depends on session hygiene instead.
The third ceiling lives in the Claude Console: give the agent its own workspace, then set a monthly spend limit and per-minute rate limits on it. "You cannot set limits on the Default Workspace", and "Organization-wide limits always apply, even if workspace limits add up to more". Add spend notifications so a threshold alerts you before the cap does.
Per-job model choice, and what effort actually changes
Model choice is a per-job decision. As of October 2026, per million tokens, input then output: Claude Fable 5.1 and Fable 5 at $10 and $50, Claude Opus 5.5 at $4 and $20, Claude Opus 5, Opus 4.8 and Opus 4.7 at $5 and $25, Claude Sonnet 5.5 and Sonnet 5 at $2 and $10, Claude Haiku 5.5 at $0.10 and $0.50 for prompts up to 100,000 tokens ($0.50 and $2.50 above that, cached tokens included in the count), and Claude Haiku 4.5 at $1 and $5. Anthropic first sold Sonnet 5 at that price as an introductory rate, then made it the standard price and cancelled the planned rise to $3 and $15. A step that only classifies log lines does not need Opus.
Do not count on free usage to absorb a busy schedule either. Apart from the small credit the Claude API grants at signup, the only recurring allowance is the monthly API credit included with Claude Max and Team plans: $100 on Max 5x, $200 on Max 20x and up to $500 pooled on Team, refreshed each billing cycle. You can claim it after 7 days on an eligible plan, and no payment method is needed. It covers the Claude API, Managed Agents, the Agent SDK and the playground but not Claude Code, and it expires at the end of each cycle without rolling over. Spending it counts toward your spend cap, and once it runs out, requests stop unless the organization also holds purchased credits.
Effort is the second lever. output_config.effort accepts low, medium, high, xhigh and max, though not every model supports every level. The default is high on most models and medium on Claude Opus 5.5 and Claude Haiku 5.5, and setting a model's default explicitly is the same as omitting it. Lower effort cuts more than reasoning length: the documentation says it makes Claude make fewer tool calls and combine multiple operations into fewer calls. On an agent that is the bigger saving, because an avoided tool call is a whole request that never happens.
The trap is that effort fights the cache. Changing the top-level value between requests invalidates prompt caching. In the documented example on Claude Opus 5.5, request 2 reported cache_read_input_tokens: 3546. Request 3 changed effort from medium, that model's default, to low, and reported cache_creation_input_tokens of 3546 and cache_read_input_tokens of 0. So vary top-level effort across workloads, never inside one cached conversation. Two routes change depth without breaking the cache. On Claude Fable 5.1, Opus 5.5, Opus 5, Sonnet 5.5 and Haiku 5.5, a per-message effort change (beta, header mid-conversation-output-config-2026-07-01) sets a new level and leaves the cached prefix alone. On any model you can steer in the prompt instead: a line like "Answer directly without deliberating." on the newest user message leaves the earlier breakpoints intact. On Claude Haiku 5.5, though, that phrase does not reliably reduce thinking.
Thinking tokens bill at output rates and count against max_tokens, which is why a truncated answer often means thinking ate the budget. Read usage.output_tokens_details.thinking_tokens for the number. What actually fills a Claude token bill takes the meter apart.
Cache the stable prefix, and stop breaking it by accident
A cache write costs 1.25 times the base input price on the five-minute cache and 2 times on the one-hour cache. On most models a cache read costs 0.1 times, so "caching pays off after one cache read for the 5-minute duration (1.25x write), or after two cache reads for the 1-hour duration (2x write)". Reads are cheaper still on the newest models: 0.05 times on Claude Opus 5.5 and Claude Sonnet 5.5, and 0.025 times on Claude Fable 5.1.
One line explains why this suits an always-on agent: "The cache is refreshed for no additional cost each time the cached content is used." A job firing every two minutes against the five-minute cache keeps its prefix warm all day for one write.
Three ways to lose the cache without noticing.
A prefix that changes. "Cache prefixes are created in the following order: tools, system, then messages." Any byte change earlier in that order invalidates everything after it, and editing tool definitions invalidates the whole cache. The classic self-inflicted wound is a timestamp or a run id in the system prompt: every request then carries a different prefix, writes a fresh entry at 1.25x, and reads nothing back. The tell is usage.cache_read_input_tokens at 0 across identical-looking calls. Move the volatile text into the newest user message.
A prefix that is too short. Each model has a minimum cacheable length, and below it the request is processed without caching and "no error is returned". The figures include 512 tokens on Claude Fable 5.1, Fable 5, Opus 5.5, Opus 5, Sonnet 5.5 and Haiku 5.5, 1,024 on Claude Opus 4.8 and Claude Sonnet 5, and 4,096 on Claude Haiku 4.5, so moving a job from Sonnet 5 to Haiku 4.5 can switch caching off silently.
A conversation that outgrows the lookback. "The lookback window is 20 blocks." The system checks at most 20 positions per breakpoint, then stops. In the documented example, a turn holding 35 blocks with a breakpoint on block 35 checks blocks 35 down to 16, and the previous turn's entry at block 15 falls outside the window, so there is no hit. An agent appending several tool-use and tool-result blocks per turn crosses 20 in two or three turns. You get four breakpoints per request, so spend one on the recent messages.
Send anything that can wait to the Batches API
"All usage is charged at 50% of the standard API prices", on both input and output. Batch processing is asynchronous, "with most batches finishing in less than 1 hour", with results when every request has finished or after 24 hours, whichever comes first. That is typical, not guaranteed.
Poll processing_status until it reads ended. Requests returning errored, canceled or expired are not billed. One caveat if you lean on a spend cap: "batches may go slightly over your Workspace's configured spend limit."
The discounts stack, and because a batch can take longer than five minutes, the documentation recommends the one-hour cache for batches sharing context. So split the work: anything a person or a webhook waits on stays on the live path, and a nightly digest or yesterday's log classification goes into a batch at half price.
Log every response's usage fields to your own store
You cannot attribute spend you never recorded. Every response tells you what it cost.
u = resp.usage
row = {
"job": job_name,
"model": resp.model,
"uncached_input": u.input_tokens,
"cache_write": u.cache_creation_input_tokens,
"cache_read": u.cache_read_input_tokens,
"output": u.output_tokens,
"stop_reason": resp.stop_reason,
}Append one row per API call to a JSON-lines file, tagged with your job name. A week later you can say which job spends and which only looked busy. Watch cache_read: a column of zeros is the most common cost bug in a self-hosted agent.
One field is easy to misread. input_tokens counts only the tokens after the last cache breakpoint, so the real prompt size is total_input_tokens = cache_read_input_tokens + cache_creation_input_tokens + input_tokens. An agent reporting input_tokens: 400 on a large prompt is not cheap: the rest came from cache.
Count before you send. Token counting is free and its rate limits are separate from message creation, so use count_tokens to refuse an oversized attachment instead of paying to discover it. The result is an estimate, so re-measure per model and never reuse a count from another vendor's tokenizer. Claude 4.7 and later models, Claude Fable 5 and Claude Sonnet 5 among them, use a newer tokenizer that "produces approximately 30% more tokens for the same text". Claude Sonnet 4.6 and earlier, Claude Haiku 4.5 among them, use the previous one.
For the authoritative view, the Admin API reports usage at https://api.anthropic.com/v1/organizations/usage_report/messages and cost at https://api.anthropic.com/v1/organizations/cost_report. Both take an admin key (sk-ant-admin01-...) as x-api-key: $ANTHROPIC_ADMIN_KEY with anthropic-version: 2023-06-01. The usage report accepts bucket_width=1d, group_by[]=model and api_key_ids[]=. The cost report is daily only and groups by workspace_id or description. One limitation: "The Admin API is unavailable for individual accounts."
That api_key_ids[] parameter is a cheap attribution trick: give each job its own API key, filter the usage report with api_key_ids[], and split it per key with group_by[]=api_key_id. The filter is plural, the grouping dimension is singular. Keep the keys in the environment rather than in code, the way a first Claude API app on a VPS handles them.
Bound the loop, because nothing else will
A bounded iteration count is not optional here. The loop is yours, so the counter is yours:
for step in range(MAX_STEPS): # MAX_STEPS = 12, never "while True"
resp = client.messages.create(...)
if resp.stop_reason != "tool_use":
break
else:
log.warning("job %s hit MAX_STEPS=%d, giving up", job_name, MAX_STEPS)Neither ceiling above does it for you: max_tokens caps one response, and the model is only advised of a task budget. A hosted product would stop you here, the way Claude's cap on tool calls within a single turn halts a session that has made too many of them, but a loop you wrote yourself ships with no such backstop until you add one.
Put a second brake outside the process. Run the job from a systemd timer instead of a permanent process, and give its service unit a wall-clock cap. A timer job is usually Type=oneshot, and RuntimeMaxSec= "does not have any effect on Type=oneshot services", so set TimeoutStartSec=600 there. Use RuntimeMaxSec=600 only on a Type=simple unit. Either way, a hung run is killed after ten minutes instead of spinning until you notice. Running a program as a systemd service and timer covers the unit files themselves. Read what a run did with journalctl -u triage-agent.service --since "1 hour ago".
Cap retries as well, because a handler that retries forever bills every attempt. A 429 or a 500 deserves a few tries with backoff. A 400 deserves none, since the same request fails the same way.
AI agent cost control starts with reading your own numbers
Nobody can tell you what an always-on agent costs, because the cost is tokens per run multiplied by runs per day, and both halves belong to you. Run it once, read the usage row you logged, and multiply by your schedule. Check the cost report two days later against that arithmetic. When the two disagree, the gap is nearly always a broken cache or a loop that ran longer than you assumed.
This assumes an API key, because the agent is your own program calling the Messages API. For your own interactive work, which Claude plan fits the way you work covers the subscription side. Every price and limit here was checked against Anthropic's documentation in October 2026, so re-read the pricing page before you build a budget.
FAQ
How much does it cost to run an always-on AI agent on a VPS?
There are two bills and only one is predictable. The server is a fixed monthly price. The model API is metered per token, so the cost is what one run consumes multiplied by how often it runs. Anthropic publishes no figure for a self-hosted always-on agent, so treat any quoted number as a guess. Log usage from one real run and multiply by your schedule.
What is the difference between max_tokens and a task budget?
max_tokens is enforced and invisible to the model. It caps the output of one request, thinking included, and hitting it gives stop_reason: "max_tokens". A task budget is the opposite: the model is told the number and paces the agentic loop against it, but "Task budgets are a soft hint, not a hard cap" and the enforced limit is still max_tokens.
Why is cache_read_input_tokens always zero for my agent?
Because the prefix changes between calls, or it is too short to cache. The usual cause is a timestamp or a run id interpolated into the system prompt: the cache is keyed on the prefix, so any byte change invalidates everything after it. Changing tool definitions or the top-level effort value does the same. Otherwise it is size, since shorter prompts are not cached and no error is returned.
How do I stop an AI agent from looping forever?
Count iterations in your loop code and stop at a fixed maximum, because max_tokens bounds one response and an agent makes many. Add a wall-clock limit outside the process: start the job from a systemd timer and set TimeoutStartSec= on a oneshot unit, or RuntimeMaxSec= on a simple one, so a wedged run is killed on schedule. Cap retries too, since a retry loop bills every attempt.
Can I set a spending limit on a single Claude API key?
The documented spend limit is per workspace rather than per key, so give the agent a workspace of its own and cap its monthly spend there. "You cannot set limits on the Default Workspace". Add spend notifications so a threshold alerts you first. For attribution, issue each job its own key, then group the usage report with group_by[]=api_key_id.