🏷️ Two meanings of "skill"
The word “skill” has both directions that cost different amounts. (a) The description of the skill — the short line that appears in the skills list — stays always in the stable prefix. (b) O content complete (the big document) only loads when the skill is invoked.
What it is
A skill introduces itself to the model with a short description (cheap, always present) and loads its body only on demand. They are two blocks with different costs and timing.
Why learn
Because without separating the two, you think "every skill is expensive." In reality, only the content adds weight—and only once, when invoked.
Description (a)
Short line in the skills list. Always in the stable prefix. Cheap and constant.
Content (b)
The complete document. It’s included only when the skill is invoked. It costs once, then becomes a cheap read.
Key concepts
Description = always present, cheap.
Content = on demand, costs 1×.
Invoke = bring the content into the conversation.
Having many skills listed ≠ loading them all.
➕ A Skill in the middle does NOT reset the cache
When a skill is invoked mid-conversation, its content is added appended at the END of the history — not at position 0. Everything that came before remains valid and is read cheaply; only the new skill block is written (one write). In subsequent turns, it is already cheap read.
What it is
Appending at the end preserves the entire previous prefix: prefix matching continues to work up to the point where the new content begins. So there’s no reset—just an incremental write.
Why learn
Because it removes the fear of invoking skills while working. That turn may show a lower cache %, due to the skill write, but it's a one-time investment—it gets cheap starting with the next turn.
Same amount of new content, opposite results: appending to the end preserves the cache; changing position 0 clears everything.
Key concepts
Append at the end = intact prefix.
Single write of the skill block.
Following turns = cheap read.
Invoking a skill ≠ resetting the cache.
💥 Breaking the tools list = position 0
The tools are rendered in the position 0 — before system and the
history. That’s why changing the list tools
invalidates EVERYTHING: because prefix matching starts from the first byte, any change to the tools block invalidates the entire prefix after it.
What it is
The list tools occupies position 0 in the assembly order tools → system → messages. Changing any byte there is the worst possible case for the cache.
Why learn
Because it's the most expensive silent invalidator. Knowing that position 0 is sacred changes how you connect MCPs, edit tool descriptions, and build the list.
Attention
It’s not just “adding a tool.” Remove, reorder or change one character a description counts too. Position 0 needs to be byte-for-byte identical across requests.
Key concepts
tools = prompt position 0.
Change tools = full reset.
Order: tools → system → messages.
Position 0 = byte-for-byte identical.
🔨 Examples of cache breaks
Four classic ways to break position 0 without noticing. They all change the list
tools — intentionally or by accident.
Connect/disconnect an MCP midway
Registers (or removes) new tools in the list → position 0 changes → invalidates everything.
Change description or input_schema
Just one word in a description or one field in the schema is enough—the bytes of tools change.
Non-deterministic order
Assemble tools of a set/dict without sorting → the order varies across requests → silent miss. (That’s what the copy-run example below is about.)
Turn tools on/off by mode/permission
Switching to a mode that enables/disables tools changes the rendered list at position 0.
What it is / Why learn it
What it is: the four most common causes of cache "disappearing" for no apparent reason — all change position 0.
Why learn: recognizing the pattern makes you suspect the tools list first when cache_read_input_tokens resets.
Key concepts
MCP connected in the middle = new tools.
1 word in the description is enough to break it.
set/dict no sort = variable order.
Mode/permission turns tools on and off.
🔍 Tool Search appends intentionally
O Tool Search loads a tool’s schema
appending — without changing the list tools.
This was designed specifically to DO NOT break the cache: discovering a tool via Tool Search is safe; connecting an MCP that rewrites the list is not.
What it is
Tool Search brings in a tool’s schema on demand, appended at the end (like a skill), preserving the prefix. It’s the same “append at the end = safe” pattern applied to tools.
Why learn
Because it distinguishes two paths that look the same: "bringing a new tool into the conversation" can be safe (Tool Search, appended) or destructive (rewriting tools, position 0).
✓ Safe
- ✓Find a tool via Tool Search (appends at the end).
- ✓Invoke a skill (appends content at the end).
✗ Breaks
- ✗Connect an MCP that rewrites the tools list.
- ✗Edit an existing tool’s description/schema.
Key concepts
Tool Search appends the schema.
Made for no break the cache.
Finding a tool ≠ rewriting tools.
Same principle as skills (end = safe).
👍 Rule of thumb
The phrase that resolves 90% of day-to-day decisions: new content at the END =
safe. Changing the list of tools or in the
system prompt = position 0 reset.
What it is
A one-line mental test: before any change, ask “does this change the beginning of the prompt or only add to the end?” End = cheap write; beginning = reset.
Why learn
Because you don't need to memorize every case: the rule of thumb classifies any new action as "safe" or "reset" right away.
Practical tip
Always ask: "does this change the beginning or just add to the end?" If the beginning changes (tools/system), expect a reset. If you add to the end (skill/Tool Search/new message), it's a single, cheap write.
Copy-run example — make the assembly of tools deterministic
Objective: ensure the list tools (position 0) must be byte-for-byte identical in every request. Iterating over a set/dict without sorting, the order varies → the cache never hits.
# ❌ QUEBRA — ordem varia entre requests (cache nunca bate)
tools = [tool_schema(name) for name in <meu_set_de_tools>]
# ^ set/dict: ordem não garantida
# ✅ ESTÁVEL — ordena por nome → mesma sequência de bytes sempre
tools = [tool_schema(name) for name in sorted(<meu_set_de_tools>)]
# (se as tools forem dicts, serialize com chaves ordenadas também)
# payload = json.dumps(tool, sort_keys=True)
How to verify: run the same request twice in a row and check whether usage. With the sorted(...), o cache_read_input_tokens stops being 0 between identical requests — a sign that position 0 stayed identical and the prefix matched.
No arquivo <caminho que monta a lista de tools>, procure
onde `tools` é construída a partir de um set ou dict e:
1. ordene a serialização por nome (sorted / sort_keys=True);
2. confirme que a ordem fica estável entre dois requests;
3. me mostre o antes/depois do trecho.
Key concepts
End = safe (single write).
Beginning (tools/system) = reset.
sorted(...) keeps position 0 stable.
cache_read > 0 between identical requests = ok.
Module summary
Next module:
2.3 — Invalidation in depth