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, Claude Opus 4.8 and Claude Opus 4.7. 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 July 2026, per million tokens, input then output: Claude Fable 5 at $10 and $50, Claude Opus 4.8 and Opus 4.7 at $5 and $25, Claude Sonnet 5 at $3 and $15, Claude Haiku 4.5 at $1 and $5. Sonnet 5 sits below its sticker price for now, because "Introductory pricing of $2/$10 per million input/output tokens is in effect through August 31, 2026". A step that only classifies log lines does not need Opus.
Effort is the second lever. output_config.effort accepts low, medium, high, xhigh and max, and the default is high, so setting high explicitly is the same as omitting it. Lower effort cuts more than reasoning length: the documentation says it makes Claude take fewer tool calls and combine operations into one. 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 value between requests invalidates prompt caching. In the documented example, request 2 reported cache_read_input_tokens: 3546; request 3, with effort changed from high to medium, reported cache_creation_input_tokens of 3546 and cache_read_input_tokens of 0. So vary effort across workloads, never inside one cached conversation. To steer depth without breaking the cache, do it in the prompt: a line like "Answer directly without deliberating." on the newest user message leaves the earlier breakpoints intact.
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. A cache read costs 0.1 times, so "caching pays off after just one cache read for the 5-minute duration (1.25x write), or after two cache reads for the 1-hour duration (2x write)".
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 1,024 tokens on Claude Opus 4.8 and Claude Sonnet 5, and 4,096 on Claude Haiku 4.5, so moving a job from Sonnet to Haiku 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 Opus 4.7 and later Opus models, Claude Fable 5 and Claude Sonnet 5 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, and accept bucket_width=1d, group_by[]=model and api_key_ids[]=. One limitation: "The Admin API is unavailable for individual accounts."
That last parameter is a cheap attribution trick: give each job its own API key, filter with api_key_ids[], and split the report 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.
Put a second brake outside the process. Run the job from a systemd timer instead of a permanent process, and set RuntimeMaxSec= on its service unit. With RuntimeMaxSec=600, 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 July 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 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 with RuntimeMaxSec= set, 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.