Skip to content

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.