Study guides / CCAR-P / Domain 6

Claude Models, Prompting & Context Engineering · Lesson 5 of 5

6.5 - Prompt Reuse: Caching, Modular Prompts and Skills

Choose and configure prompt caching, modular prompt components and Agent Skills so shared instructions are cheap to run, easy to version and safe to govern.

Three mechanisms get called “prompt reuse,” and they solve different problems. Prompt caching reuses computation for an identical prefix (a cost lever at run time). Modular prompts reuse authored components across prompts (a maintenance lever). Skills package a procedure that is loaded into context only when needed (a context lever). Scenario questions usually hinge on picking the mechanism that matches the problem and then configuring it correctly.

Prompt caching: design the stable prefix

Caching matches an exact prefix in a fixed order: tools, then system, then messages. A change at any level invalidates that level and every level after it. You enable it either with a top-level cache_control field (automatic caching, where the breakpoint moves forward as a conversation grows) or with explicit breakpoints on individual content blocks, up to four per request. Anthropic’s placement rule: put cache_control on the last block whose prefix is identical across the requests that should share a cache. A per-request value such as a timestamp before the breakpoint changes the prefix every time: a fresh write on every request and no reads.

The economics, from the current caching page (verify before quoting): a 5-minute cache write costs 1.25x the base input price, a 1-hour write (ttl: 1h) costs 2x, and a cache read costs 0.1x (0.025x on Fable 5.1). Taking the base price as 1 for the cached tokens:

The lifetime is measured from the start of the request that writes or reads the entry, not from the end of its response, and each hit refreshes it. Behaviours that appear in scenarios:

Caching reduces the cost of re-reading a repeated prefix. It does not change how output tokens are generated, and cached tokens still count toward the context window (see 6.4). With compaction, put cache_control on the system prompt so only the new summary is a fresh write.

Common exam distractor

Answers that place the breakpoint after the unique user message, cache a prefix that contains per-request data, claim caching speeds up output generation or shrinks the context window, or expect N parallel first-time requests to yield N-1 hits are all wrong. The winning design puts stable content first, the breakpoint on the last stable block, variable content after it, and a warm-up request before any burst.

Modular prompts: components with owners and an assembly order

Split a large prompt into components that change at different rates: role and policy (rarely), tool definitions, domain reference, few-shot examples, task instructions, and per-request variables. Assemble them in stable-to-volatile order, which serves clarity and caching at once (see 6.2). Give each component an owner, a version and its own eval, and record which component versions each deployed prompt uses. Two trade-offs to name: a shared policy component removes drift but widens the blast radius, so every change is re-evaluated against all consumers; and per-tenant variants (or a feature flag that toggles a tool) multiply cache prefixes, because each variant is a different cache.

Skills: packaged instructions loaded on demand

An Agent Skill is a directory with a SKILL.md plus optional reference files and scripts. Anthropic’s docs describe three loading levels (progressive disclosure): level 1 is the name and description only, always in context at roughly 100 tokens per skill; level 2 is the SKILL.md body, read when the skill triggers (under 5k tokens); level 3 is bundled files, read only when needed, while scripts run and only their output enters context. Consequences: many skills can be installed cheaply, bundled reference material costs nothing until used, and the description is the routing signal. Authoring rules to know: name up to 64 characters of lowercase letters, numbers and hyphens (not containing the reserved words “anthropic” or “claude”); description non-empty, up to 1,024 characters, stating what the skill does and when to use it, written in the third person; keep the body under about 500 lines, split detail into files linked one level deep, and test across the models you will use.

Choosing the mechanism: facts and rules that are true for every request belong in the system prompt (and can be cached); a multi-step procedure used occasionally belongs in a Skill; a deterministic operation belongs in a script inside the Skill. In Claude Code, descriptions stay in context while the body loads on invocation and then persists; allowed-tools pre-approves tools rather than restricting them (disallowed-tools removes them). The API docs state that custom Skills do not sync across surfaces (claude.ai skills are per user, API skills are workspace-wide, Claude Code skills are filesystem or plugin based), so keep source in Git as the single source of truth; Claude Code also documents a separate path for loading skills enabled in a signed-in claude.ai account, so check the current behaviour for your surface. Using Skills through the API requires the code execution tool.

Key concept: reuse is also a governance problem

A shared prompt component or Skill changes the behaviour of every consumer at once. Treat it like a dependency: version it, review it, evaluate it before promotion, pin the version in production, and keep a rollback. Anthropic’s Skills guidance says exactly this, and the same discipline applies to shared system-prompt components.

Versioning and governance

In the Skills API you reference skills in container.skills with a type, skill_id and optional version (up to 20 skills per request). If you omit version you get the latest, so a new version uploaded by anyone in the workspace immediately changes what production runs; pin specific versions in production and use latest only in development. Custom skills are workspace-scoped: any API key in the workspace can read, invoke and delete them, so multi-tenant platforms should use a separate workspace per tenant. Anthropic’s enterprise guidance adds a security review before deployment (scripts, network calls, hardcoded credentials, MCP references), evaluation suites with a few representative queries that cover should-trigger, should-not-trigger and ambiguous cases, testing across the model tiers you use, separation of duties between author and reviewer, a registry of owner, version, dependencies and evaluation status, and limiting how many skills load at once because selection accuracy can degrade as descriptions accumulate. Note also that Agent Skills are not covered by zero-data-retention arrangements, which matters in regulated designs (see 4.4).

Exam traps

Practice question

An agent service sends a 30,000-token stable prefix (tool definitions, policy and reference material) followed by a short per-user question, in bursts of about 20 concurrent requests every few minutes. Which design gives the best cache benefit at reasonable cost?

  • A Put the breakpoint after the user's question so the whole request is cached, and use a separate, personalised system prompt per user to maximise relevance.

    The question differs on every request, so nothing after the stable prefix can ever be a hit, and per-user prompts multiply cache entries that are never reused.

  • B Add a per-request timestamp and the user's name at the top of the system prompt for personalisation, with the breakpoint placed after the reference material.

    The timestamp and name sit before the breakpoint, so the prefix differs on every request. That is a fresh cache write every time and no reads.

  • C Use the 1-hour TTL and place all four breakpoints on the tools, system and message blocks regardless of traffic, to be safe and avoid any cache misses.

    The 1-hour write costs more than the 5-minute write and pays off only when gaps between requests exceed five minutes. With bursts every few minutes the default TTL is refreshed by hits, and extra breakpoints on changing blocks add nothing.

  • D Put tools, policy and reference first, breakpoint on the last stable block, per-request data after it, warm the cache before each burst, and watch cache reads. Correct

    This makes the prefix identical across requests, respects the rule that entries become readable only after the first response begins, and verifies the result with the usage fields.

Build exercise: Cache it, modularise it, package it: measure savings and write the governance record

Advanced · 90 minutes

You'll practice:

  1. Create a components folder (role_policy.md, domain_reference.md, examples.md, task.md) and an assemble() function that joins them in stable-to-volatile order and returns the system blocks, with a manifest recording each component's version.

    Modularity is only useful if assembly order and versions are explicit, and the same order is what makes the prefix cacheable.

    You should see: A single system prompt built from four files, a manifest such as role_policy v3 and domain_reference v7, and per-request data kept out of the stable region.

    Hints
    1. Which components change least often, and which would change per request?
    2. Assemble the stable components into one block that ends with the cache breakpoint, and pass anything per-request in the user turn.
    3. def assemble(components):
          text = '\n\n'.join(open(f'components/{n}.md').read() for n in ('role_policy', 'domain_reference', 'examples'))
          return [{'type': 'text', 'text': text, 'cache_control': {'type': 'ephemeral'}}]
      MANIFEST = {'role_policy': 'v3', 'domain_reference': 'v7', 'examples': 'v2'}
  2. Make the assembled prefix longer than the minimum cacheable length for your model, send the same request twice with different questions, and print the three usage fields. Then change one word early in the prefix and repeat.

    Reading usage is how you verify caching works, and the one-word change shows the exact-prefix rule directly.

    You should see: Call 1 reports cache_creation_input_tokens; call 2 reports cache_read_input_tokens for the same prefix; after the edit, the read drops to zero and a new write appears.

    Hints
    1. If you see neither a write nor a read, what does the docs say happens to a prefix that is too short?
    2. Check the model's minimum length first; then compare cache_creation_input_tokens, cache_read_input_tokens and input_tokens on each call.
    3. def ask(q):
          r = client.messages.create(model='claude-sonnet-5', max_tokens=300, system=assemble(None),
              messages=[{'role': 'user', 'content': q}])
          u = r.usage
          print('write', u.cache_creation_input_tokens, 'read', u.cache_read_input_tokens, 'uncached', u.input_tokens)
      ask('Question one'); ask('Question two')
  3. Test the pitfalls: (a) put datetime.now() in the system text before the breakpoint and observe zero reads; (b) fire 5 identical requests in parallel against a cold prefix, then repeat after a warm-up request has begun responding.

    These two mistakes explain most real-world cache misses, and seeing them in your own usage numbers makes them memorable.

    You should see: (a) every call reports a write and no read; (b) the cold parallel burst shows several writes, while the warmed burst shows reads.

    Hints
    1. Which requests in the parallel burst could possibly find an entry, given when an entry becomes available?
    2. Use a thread pool for the burst; for the warmed run, complete one request first (or wait for its first streamed event) and only then launch the rest.
    3. from concurrent.futures import ThreadPoolExecutor
      with ThreadPoolExecutor(5) as pool:
          list(pool.map(ask, [f'Q{i}' for i in range(5)]))   # cold burst
      ask('warm-up')
      with ThreadPoolExecutor(5) as pool:
          list(pool.map(ask, [f'Q{i}' for i in range(5)]))   # warmed burst
  4. Given your expected traffic (requests per 5 minutes and typical gaps), compute total input cost for uncached, 5-minute TTL and 1-hour TTL using the multipliers on the current caching page and the base price for your model. State which you would deploy.

    Break-even reasoning, not habit, should decide the TTL. It also shows when caching does not pay at all.

    You should see: A three-row comparison and a one-line recommendation, for example that 5-minute caching pays back after one reuse while 1-hour caching only pays when gaps regularly exceed five minutes.

    Hints
    1. How many requests fall within one TTL window, and how many writes does each option imply for your traffic?
    2. Model uncached as n x base, 5-minute as one write at the write multiplier plus (n-1) reads at the read multiplier per active window, and 1-hour the same with its own write multiplier over the longer window.
    3. base = 1.0
      def uncached(n): return n * base
      def cached_5m(n, windows=1): return windows * 1.25 * base + (n - windows) * 0.1 * base
      def cached_1h(n, windows=1): return windows * 2.0 * base + (n - windows) * 0.1 * base
      print(uncached(10), cached_5m(10), cached_1h(10))
  5. Package one repeatable procedure as a Skill: a SKILL.md with a third-person description that says what and when, one reference file linked one level deep, and one script. Write six eval queries (trigger, should-not-trigger, ambiguous) and a governance record with owner, pinned version, review checklist and rollback rule.

    This exercises progressive disclosure and the governance practices the docs recommend: triggering accuracy is testable, and a version pin plus rollback keeps a shared artifact safe.

    You should see: A skill directory, a table of six queries with expected trigger or no-trigger, and a registry entry noting owner, version, dependencies, security review and evaluation status.

    Hints
    1. What phrase in the description would make the skill trigger on the wrong request, and what would make it miss the right one?
    2. Write the description first, then queries that stress it: near-miss requests that should not trigger, and ambiguous ones that reveal overlap with other skills.
    3. ---
      name: reviewing-invoices
      description: Checks supplier invoices for missing fields and total mismatches and drafts a correction request. Use when the user asks to validate, audit or reconcile an invoice.
      ---
      # Reviewing invoices
      For field rules see [rules.md](rules.md). Run scripts/check_totals.py on the extracted JSON before drafting.

Sources