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).
| File | Role |
|---|---|
agents/mira/llm.yaml | New 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.yaml | New 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.py | The 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.py | Unit tests: schema parses, env override, unknown → off, and the ambient×paid-write guard truth table. |
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 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.
| AC | Proof |
|---|---|
| schema parsed | test_schema_parsed_* — every seat resolves to its configured CachePolicy (Mira designed/within_run; Vega all off). |
| policy resolution incl. ambient×paid-write guard | test_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. |
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.