AI Agents and Their Identity — Why a Persona Matters More Than a Prompt, and What It Costs in Tokens
Giving an agent a clear identity — scope, tools, style, definition of done — is the cheapest way to improve output quality and the most expensive line on your token bill if you do it carelessly. Here's the engineering and the math.
- AI Agents
- Claude API
- Prompt Caching
- Tokens
- LLM
Introduction
In 2026, "write a good prompt" is no longer an architecture. The teams I work with have stopped building one giant prompt that handles everything. Instead they build agents: specialised units, each with its own scope of responsibility, its own set of tools and its own identity.
Sounds like cosmetics? It isn't. An agent's identity is simultaneously the cheapest way to improve answer quality and the single most expensive item on your token bill — if you build it without understanding what happens on every request.
This article covers three things: what an agent identity actually consists of, why it changes model behaviour, and exactly how it translates into tokens and money.
What "identity" means for an agent
Identity is not a name and a friendly greeting. In practice it is five layers, and every one of them is load-bearing:
| Layer | Question it answers | Where it lives |
|---|---|---|
| Role and scope | What is this agent for — and what is it explicitly NOT for? | System prompt |
| Tool boundary | What actions can it take at all? | Tool definitions |
| Style and format | How does it report back? Prose, JSON, a diff? | System prompt + structured outputs |
| Definition of done | When is the task finished? | System prompt |
| Memory | What does it know from previous sessions? | Memory tool / external store |
The interesting part is that these layers are not documentation for humans. They are the search space the model operates in. A narrow, explicit identity removes whole classes of wrong decisions before the model makes them.
using Anthropic;
using Anthropic.Models.Messages;
// The identity is a stable artifact — treat it like code, not like a string literal
// scattered across the codebase. Version it, review it, test it.
const string ReleaseAuditorIdentity = """
You are a release auditor for a .NET MAUI mobile application.
SCOPE: you review a release candidate diff and report risk. You do not
write features, you do not refactor, you do not touch CI configuration.
METHOD: for each changed file, classify risk as low / medium / high and
justify it with a concrete failure scenario, not a general concern.
OUTPUT: a table of findings ordered by severity, then a single
go / no-go recommendation. No preamble, no summary of what you did.
DONE: every changed file in the diff appears exactly once in the table.
""";
AnthropicClient client = new();
var response = await client.Messages.Create(new MessageCreateParams
{
Model = "claude-opus-5",
MaxTokens = 16000,
Thinking = new ThinkingConfigAdaptive(),
OutputConfig = new OutputConfig { Effort = Effort.High },
System = new List<TextBlockParam>
{
new() { Text = ReleaseAuditorIdentity },
},
Messages = [new() { Role = Role.User, Content = releaseDiff }],
});Why identity matters more than prompt wording
It narrows the decision space
An agent without a declared scope treats every request as an invitation to be helpful in general. That is exactly how you get a code reviewer that starts refactoring, or a data extractor that adds friendly commentary you then have to parse around. "SCOPE / not in scope" does more work than three paragraphs of polite instructions.
It survives long loops
A single-turn call barely notices a weak identity. An agent that runs twenty tool calls does. Every iteration is an opportunity to drift — to pick up the framing of the last tool result instead of the framing of the task. A strong identity is re-asserted on every single request, because the system prompt is re-sent every single request. That property is the whole reason drift is manageable, and it is also why identity has a price tag.
It makes the agent testable
An identity with an explicit definition of done is a contract you can write assertions against. "Every changed file appears exactly once" is checkable. "Be thorough" is not.
It is where your security boundary lives
Claude does not know your approval policy. The harness does. Identity plus a deliberately narrow tool surface is how you express that boundary: hard-to-reverse actions get dedicated, gateable tools instead of a general-purpose shell.
The price of identity: tokens
Here is the mechanic that surprises people who move from single calls to agents:
The system prompt and the tool definitions are re-sent with every request in the loop.
An agent run is not one API call. It is N calls, where N is the number of tool round-trips, and each one carries the full identity, the full tool schema set and the entire conversation so far.
Take a realistic setup:
| Component | Tokens |
|---|---|
| Identity (system prompt) | 1,500 |
| Tool definitions (6 tools) | 800 |
| Fixed prefix per request | 2,300 |
An agent that makes 20 tool calls therefore pays for that prefix 20 times: 46,000 input tokens before a single line of actual task content. On Claude Opus 5 input pricing ($5 / 1M tokens) that's about $0.23 per run just to keep re-introducing the agent to itself — and at 10,000 runs a month, $2,300.
That is the number that makes people delete their system prompts. Don't. Cache them.
Prompt caching: how identity becomes almost free
Prompt caching turns that fixed prefix from a per-request cost into a near-zero one. One invariant governs everything:
Caching is a prefix match. Any byte change anywhere in the prefix invalidates everything after it.
The render order is tools → system → messages. So a cache breakpoint on the last system block covers the tools and the identity together — exactly the 2,300 tokens above.
var response = await client.Messages.Create(new MessageCreateParams
{
Model = "claude-opus-5",
MaxTokens = 16000,
System = new List<TextBlockParam>
{
new()
{
Text = ReleaseAuditorIdentity, // byte-identical on every request
CacheControl = new CacheControlEphemeral(), // 5-minute TTL by default
},
},
Messages = messages,
});
// Always verify — caching fails silently, never loudly
Console.WriteLine($"written: {response.Usage.CacheCreationInputTokens}");
Console.WriteLine($"read: {response.Usage.CacheReadInputTokens}");
Console.WriteLine($"full price: {response.Usage.InputTokens}");The economics: a cache read costs about 0.1× the base input price, a cache write 1.25× for the default 5-minute TTL (or 2× for a 1-hour TTL). Applied to the same 20-iteration run:
| Scenario | Effective input tokens | Cost (Opus 5) |
|---|---|---|
| No caching (20 × 2,300) | 46,000 | $0.230 |
| Cached (1 write + 19 reads) | ~7,250 | $0.036 |
An 84% reduction on the identity portion of the bill, with no change to the prompt itself. The break-even is trivially fast: with a 5-minute TTL, two requests already pay for the write premium.
One caveat worth knowing: the minimum cacheable prefix is model-dependent and not monotonic across generations — 512 tokens on Claude Opus 5, 1,024 on Sonnet 5, 4,096 on Haiku 4.5. A 3,000-token identity caches on Opus 5 and silently does not on Haiku 4.5. No error, just cache_creation_input_tokens: 0.
The silent cache killers
This is where real money leaks. Every one of these looks harmless in review:
| Pattern | Why the bill doubles |
|---|---|
DateTime.Now interpolated into the system prompt | Prefix changes every request — nothing ever caches |
| User or session ID in the identity | A separate cache entry per user; zero sharing |
Conditional identity sections (if (isPremium) system += ...) | Every flag combination is a distinct prefix |
| Tool list built per user or per request | Tools render at position 0 — the entire cache dies |
| Non-deterministic JSON serialisation of tool schemas | Byte order differs → prefix differs |
| Switching model mid-conversation | Caches are model-scoped |
The fix is architectural, not a matter of marker placement: freeze the identity and push everything volatile to the end of the prompt.
When an operator instruction genuinely has to arrive mid-conversation — a mode switch, a newly injected piece of state — do not edit the top-level system prompt. On Claude Opus 5, Opus 4.8 and Fable 5 you can append a { "role": "system" } message to the messages array instead. It sits after the cached history, so the prefix survives, and unlike text smuggled into a user turn it is a non-spoofable operator channel.
One monolithic agent, or a team of specialists?
Once you accept that identity is cached, the calculus changes. A narrow agent is no longer more expensive than a broad one — and it is measurably better:
| Dimension | One generalist agent | A team of specialists |
|---|---|---|
| Prompt size per request | Large (all rules for all tasks) | Small (only relevant rules) |
| Cache hit rate | Low — the prompt shifts per task type | High — each identity is frozen |
| Context pollution | Everything lands in one window | Sub-task reading stays in the subagent |
| Model choice | One model for everything | Cheap model for reading, strong for judgement |
| Failure diagnosis | "The prompt got worse" | Isolated to one agent |
The context-pollution row is the one that matters most in practice. A sub-task that reads twenty files does not need to leave twenty file contents in the main agent's window forever. Delegate it, and the main loop receives the conclusion rather than the raw material.
Model choice compounds the effect:
| Model | Model ID | Input $/1M | Output $/1M |
|---|---|---|---|
| Claude Fable 5 | claude-fable-5 | $10.00 | $50.00 |
| Claude Opus 5 | claude-opus-5 | $5.00 | $25.00 |
| Claude Sonnet 5 | claude-sonnet-5 | $3.00 | $15.00 |
| Claude Haiku 4.5 | claude-haiku-4-5 | $1.00 | $5.00 |
Reading-heavy, low-judgement sub-tasks on Haiku 4.5 cost a fifth of Opus 5 per input token. But note the constraint from the previous section — do not switch models inside one conversation to chase that saving, because caches are model-scoped and you will pay more than you save. Spawn a subagent with the cheaper model instead and keep the main loop on one model.
Two more levers worth knowing, both on the current models:
Effort (
output_config.effort, fromlowtomax) controls thinking depth and overall verbosity. Lower effort means fewer, more consolidated tool calls and less preamble — often the single best cost lever for subagents and routine work.Context editing and compaction keep long runs from ballooning: editing clears stale tool results, compaction summarises earlier history server-side when you approach the context window.
A practical token budget
For the release-auditor agent above, running 20 tool calls on a medium-sized diff:
| Item | Tokens | Notes |
|---|---|---|
| Identity + tools (cached) | ~7,250 effective | 1 write + 19 reads |
| Tool results (the diff, file reads) | ~60,000 | The real driver — prune it |
| Growing conversation history | ~35,000 effective | Cached incrementally per turn |
| Output (findings, reasoning) | ~8,000 | Billed at 5× the input rate |
The lesson from that table is the part nobody expects: after caching, identity is the cheapest thing in the run. The expensive things are unfiltered tool results and output tokens. Which means the instinct to shorten the system prompt to save money is precisely backwards — a longer, sharper identity that stops the agent from reading three files it didn't need pays for itself many times over.
Summary
Six rules I now apply to every agent I build:
Write the identity as an artifact, not a string literal. Scope, tool boundary, output format, definition of done. Version it, review it, test it.
Assume it is re-sent on every request — because it is. That's what makes it work and what makes it cost.
Cache the prefix and verify the cache. If
cache_read_input_tokensis zero across repeated requests, something is invalidating it. Find it.Freeze the prefix; push volatile content to the end. No timestamps, no user IDs, no conditional sections, no per-request tool sets.
Prefer several narrow agents over one broad one. Better cache behaviour, isolated context, isolated failures, cheaper model choices per sub-task.
Optimise tool results before you optimise the prompt. That's where the tokens actually are.
Giving an agent an identity isn't roleplay and it isn't decoration. It is the interface contract between your system and a non-deterministic component — and once it's cached, it's also the cheapest reliability you can buy.
Note: all token counts and costs above are order-of-magnitude estimates from my own runs. Measure your own with a token-counting endpoint before budgeting against them.
