Story-2079 Walkthrough

Change: This is S-2 of the agent prompt-caching build (Epic-2050, RFD docs/notes/rfd-agent-caching-strategy.md §6.2 / D-6). S-1 landed the per-seat cache policy as config (cache_policy_for(seat)); S-2 adds the one wiring point that consumes it — the provider layer. It introduces model_settings(seat) (the single settings object carrying ZDR and the seat's cache policy), cache_point(seat) (the breakpoint marker prompt builders drop into the sandwich), and session_id plumbing, as byte-identical copies across the agents that carry them (Mira, Vega). Per-agent divergence stays in config.py/llm.yaml, never in the wiring.

What ships

FileRole
agents/mira/provider.py, agents/framer/provider.pyThe wiring, byte-identical: model_settings(seat, *, session_id=None), cache_point(seat), and the shared _seat_emits_cache_markers(seat) guard. Both delegate to the sibling config.py, so AGENT_IS_AMBIENT and the seat policies (the difference) live in config, not here.
agents/mira/tests/test_provider.pyExtends the ZDR test: a designed seat (converse) carries the cache breakpoint; ZDR is always present; session_id rides the header (and is capped at 256 chars); cache_point returns a marker; unknown seats fail open to cold.
agents/framer/tests/test_provider.py (new)The ambient guard on Vega: off seats are ZDR-only; a seat flipped on over a free-write vendor emits; the same seat over a paid-write vendor stays cold on BOTH surfaces; session_id still plumbs.
tests/test_provider_cache_parity.py (new)The byte-compare gate: the three wiring functions are byte-identical (via ast source segments) across Mira and Vega. Fails on a single byte of drift.

One settings object: ZDR + cache, never one without the other

model_settings(seat) is the D-6 "one wiring point". ZDR (zdr/data_collection=deny) and real-cost usage are unconditional — a cache flag can never loosen the provider filter. The instructions-cache breakpoint (openrouter_cache_instructions = the seat's TTL) is added only when the seat emits markers. This is the ZDR lesson applied to cache: put both in one place so no call site can carry one and forget the other.

settings = {
    "openrouter_provider": {"zdr": True, "data_collection": "deny"},
    "openrouter_usage": {"include": True},
}
if _seat_emits_cache_markers(seat):
    settings["openrouter_cache_instructions"] = cache_policy_for(seat).ttl
if session_id is not None:
    settings["extra_headers"] = {"x-session-id": session_id[:256]}

The shared guard, so both surfaces agree

_seat_emits_cache_markers(seat) is the ONE decision model_settings and cache_point share — a seat that must stay cold stays cold on both. It is fail-open (an off/unknown seat never even resolves a model), and it applies S-1's ambientxpaid-write guard verbatim (CachePolicy.emits_markers): a non-off seat on an ambient agent whose resolved provider is paid-write emits nothing (a write premium on a prefix never re-read within TTL loses money, §7.4).

session_id plumbing (Pydantic AI surface pinned)

The RFD left the exact SDK surface for S-2 to pin. pydantic-ai 1.107's OpenRouterModelSettings exposes cache fields (openrouter_cache_instructions/_messages/_tool_definitions, each bool | '5m' | '1h') but no first-class session field, so per §6.2 the sticky id rides the documented x-session-id header via extra_headers (≤256 chars). Converse passes the chat-session id, panel passes run_id, ambient passes nothing.

Acceptance criteria → tests

ACProof
settings object carries ZDR + cache fieldstest_model_settings_always_carries_zdr_and_usage + test_designed_seat_carries_cache_breakpoint (Mira) — one object, both concerns.
no per-agent divergence (byte-compare test like today's provider parity)tests/test_provider_cache_parity.py::test_shared_wiring_functions_are_byte_identical_across_agents.

Verification

cd agents/mira && uv run pytest tests -q     # 116 passed
cd agents/framer && uv run pytest tests -q   # 112 passed
uv run pytest tests/ -q -m unit                 # 171 passed (governance, incl. parity gate)
uv run ruff check agents/ tests/                # All checks passed!

Wiring primitive only: model_settings(seat) exists and is tested, but the call sites still pass ZDR_SETTINGS — swapping converse (S-3) and the panel (S-8) to it, and placing the cache_point markers in the sandwich, are the downstream enablement stories that depend on this one. No cache marker reaches the wire yet; ZDR behaviour is unchanged; no CodeZip rebuild or AgentCore redeploy is required.