Native history and tool-manifest cache composition¶
NativeCacheComposition marks the native Agent request that reaches an
Anthropic Messages API adapter. It is independent of prompt-region caching:
prompt-region cache_control markers annotate system blocks, while this
composition annotates completed conversation history and the bound native tool
schemas.
from symfonic import Agent
from symfonic.capabilities.caching import (
HistoryCachePolicy,
ManifestCachePolicy,
ManifestCachePosition,
NativeCacheComposition,
)
from symfonic.core.providers import AnthropicProvider
cache = NativeCacheComposition(
history=HistoryCachePolicy(enabled=True),
manifest=ManifestCachePolicy(position=ManifestCachePosition.CACHED),
)
agent = Agent(AnthropicProvider(), tools=[lookup_order], capabilities=[cache])
first = await agent.run("Find order A-14")
second = await agent.run("What is its status?", history=first.messages)
On the second request, the most recent completed plain-text history message is
sent as an Anthropic content block with cache_control: {"type": "ephemeral"}.
The current user message remains after that boundary. The marker is generated
from the history passed to that call; the policy stores no transcript, marker
index, tenant identifier, or model response. A process can therefore share one
immutable policy value across tenant-scoped agents without this feature
replaying one tenant's history into another tenant's request.
ManifestCachePolicy(position=ManifestCachePosition.CACHED) converts the
exact native tool set being bound into Anthropic schemas and marks its final
schema. A changed palette is converted again for that bind, so it receives a
new request manifest rather than reusing a mutable schema from a previous
turn. VOLATILE binds the same native tool schemas without a marker. This is
the exact native behavior; it does not claim to reproduce the retired legacy
text-manifest/JIT selection algorithm.
Disable either surface explicitly when needed:
NativeCacheComposition(
history=HistoryCachePolicy(enabled=False),
manifest=ManifestCachePolicy(enabled=False),
)
Migrating history caching deliberately¶
This is an explicit native composition, not compatibility for
MESSAGES_CACHE_POLICY and not a reproduction of its rolling ladder. To
isolate history caching, disable manifest caching and pass the caller-owned
first-turn messages into the second turn:
cache = NativeCacheComposition(
history=HistoryCachePolicy(enabled=True),
manifest=ManifestCachePolicy(enabled=False),
)
agent = Agent(AnthropicProvider(), capabilities=[cache])
first = await agent.run("first")
second = await agent.run("second", history=first.messages)
The deterministic evidence is request shape: the second Anthropic request has one marker on the completed plain-text assistant message; the disabled control has none. It can also measure canonical serialized request bytes, but that is not an HTTP payload size, provider token count, cache hit, billing, or latency. Measure those remotely with provider usage fields and repeated timed calls before making a cost or latency decision. Tool-result exchanges are not marked.
Optional live measurement¶
The repository has a billable Anthropic benchmark that emits provider usage and repeated wall-clock samples for history cache off/on. It is excluded from the default suite and requires both an API key and an explicit authorization:
ANTHROPIC_API_KEY=... SYMFONIC_RUN_HISTORY_CACHE_BENCHMARK=1 \
pytest -m live_llm tests/integration/live_llm/test_native_history_cache_metrics.py -s
It reports input/output tokens, cache read/creation tokens, and elapsed time;
it does not report an invoice or turn those values into a billing claim. Set
NATIVE_HISTORY_CACHE_MODEL or NATIVE_HISTORY_CACHE_REPETITIONS (1–5) only
when choosing the provider/model and sample count deliberately.
Only adapters that send the Anthropic Messages API cache_control dialect are
supported. Enabling a marker on OpenAI-, Google-, unknown-, or AWS Bedrock
Converse-backed providers raises ConfigurationError before any model call.
Bedrock Claude models use Converse cachePoint, which is not wire-compatible
with this composition. The provider controls any remote cache implementation;
these markers prove request shape, not cache billing or latency.