Story-2078 Walkthrough

Change: This is S-1 of the agent prompt-caching build (Epic-2050, RFD docs/notes/rfd-agent-caching-strategy.md §6.1 / D-5). It adds the per-seat cache policy as config — a new cache: block in each agent's llm.yaml and a cache_policy_for(seat) loader in config.py that mirrors the existing models_for(role) exactly. Cache selection joins model selection as config, never code (pramaan-docs ADR-151). This story is schema + loader only; the one provider-layer wiring point that consumes it (§6.2) is a downstream story (S-2).

What ships

FileRole
agents/mira/llm.yamlNew cache: block. Mira is the cache winner (§4): converse = designed, the command panel seats (proposer/critic/synthesizer) = within_run, all ttl: "5m"; a paid_write_providers list carried for the shared guard.
agents/framer/llm.yamlNew cache: block. Vega is ambient (§4 ambient row): every seat is "off" (never emit markers — an explicit write on a never-re-read prefix only burns the paid-write premium, §7.4).
agents/mira/config.py, agents/framer/config.pyThe loader: a frozen CachePolicy dataclass, cache_policy_for(seat) (env override wins, unknown → off), paid_write_providers() / provider_is_paid_write(), an AGENT_IS_AMBIENT constant, and the emits_markers() guard.
agents/mira/tests/test_config.py, agents/framer/tests/test_cache_policy.pyUnit tests: schema parses, env override, unknown → off, and the ambient×paid-write guard truth table.

The loader, parallel to models_for

Where models_for(role) reads the models: block and applies a MIRA_<ROLE>_MODEL override, cache_policy_for(seat) reads the cache.seats.<seat> block and applies a MIRA_CACHE_<SEAT> override (VEGA_CACHE_<SEAT> for Vega). The one deliberate difference is the failure posture: models_for is fail-closed (an unset role raises — we never pin a hardcoded vendor), while cache_policy_for is fail-OPEN — an unknown seat, a missing cache: block, or an unrecognized policy value resolves to off. Caching is economics, not correctness (§6.1): a misconfig should emit no markers, never fail a run. ZDR is untouched — a cache flag can never loosen the provider filter.

The ambient×paid-write guard

The RFD's core money-safety rule (§4 ambient row / §6.2): an ambient agent must never write a cache prefix on a paid-write provider (Anthropic-class, 1.25–2x write premium) — the prefix is never re-read within TTL, so the write is pure loss. Encoded as a pure decision on CachePolicy:

def emits_markers(self, *, agent_is_ambient, provider_is_paid_write) -> bool:
    return self.enabled and not (agent_is_ambient and provider_is_paid_write)

Only the ambient and paid-write corner suppresses an enabled seat; every other corner emits (free-write providers cache harmlessly). AGENT_IS_AMBIENT is False for Mira (her live/on-demand routes re-read the prefix, so writes pay off) and True for Vega. provider_is_paid_write(model_id) is a deterministic config-level proxy on the vendor prefix (anthropic/…); the request-time resolved provider is S-2's job.

Acceptance criteria → tests

ACProof
schema parsedtest_schema_parsed_* — every seat resolves to its configured CachePolicy (Mira designed/within_run; Vega all off).
policy resolution incl. ambient×paid-write guardtest_ambient_paid_write_guard* (the truth table), test_provider_is_paid_write_by_vendor_prefix, test_env_override_wins, test_unknown_seat_resolves_off_fail_open, test_unrecognized_policy_value_resolves_off.

Verification

uv run --package mira pytest -q     # 174 passed
uv run --package framer pytest -q   # 174 passed
uv run ruff check agents/mira agents/framer   # All checks passed!

Config + loader only: no agent brain, runtime, wall, or prompt behaviour changes (nothing yet reads cache_policy_for at request time — that is S-2). No CodeZip rebuild or AgentCore redeploy is required.