Skip to content

symfonic.capabilities.prompting.strategies

strategies

The two context strategies, as declarations rather than code paths.

jit builds a lean prompt from what is already known: nothing is hydrated, and nothing is cached, because a prompt assembled per turn from a different subset has no stable prefix to cache. stratified admits hydrated context and caches the stable layers, which is the shape prompt caching was designed for.

The important property is that the strategy is a pair of pure functions over a contribution, not two compilers. One pipeline compiles both strategies, so a change to ordering, gating, or budgeting cannot land on one of them and miss the other — the failure mode a second builder always eventually produces.

ContextStrategy

Bases: StrEnum

How much context the compiler assembles, and whether it may be cached.

cache_directive_for

cache_directive_for(strategy: ContextStrategy, contribution: PromptContribution) -> CacheDirective

The cache annotation this contribution carries under strategy.

JIT caches nothing — including a contribution that declared a directive, since a prefix that changes shape per turn cannot be a cache hit and the breakpoint would be pure cost. Under stratified a declared directive wins, and the default is derived from volatility: stable layers cache at the provider's default tier, the volatile layer never does.

Source code in src/symfonic/capabilities/prompting/strategies.py
def cache_directive_for(
    strategy: ContextStrategy, contribution: PromptContribution
) -> CacheDirective:
    """The cache annotation this contribution carries under ``strategy``.

    JIT caches nothing — including a contribution that declared a directive,
    since a prefix that changes shape per turn cannot be a cache hit and the
    breakpoint would be pure cost. Under ``stratified`` a declared directive
    wins, and the default is derived from volatility: stable layers cache at
    the provider's default tier, the volatile layer never does.
    """
    if strategy is ContextStrategy.JIT:
        return UNCACHED
    if contribution.cache is not None:
        return contribution.cache
    if is_volatile(contribution.layer):
        return UNCACHED
    return CacheDirective(cacheable=True)

resolve_strategy

resolve_strategy(name: str | None = None, *, jit_context: bool | None = None) -> ContextStrategy

Resolve the strategy from an explicit name, falling back to the legacy flag.

Precedence is stated once, here, so the facade and the compiler cannot disagree about which strategy a given configuration selects — the classic "two factories, two answers" defect.

Source code in src/symfonic/capabilities/prompting/strategies.py
def resolve_strategy(
    name: str | None = None, *, jit_context: bool | None = None
) -> ContextStrategy:
    """Resolve the strategy from an explicit name, falling back to the legacy flag.

    Precedence is stated once, here, so the facade and the compiler cannot
    disagree about which strategy a given configuration selects — the classic
    "two factories, two answers" defect.
    """
    if name is None:
        if jit_context is None:
            return ContextStrategy.STRATIFIED
        return ContextStrategy.JIT if jit_context else ContextStrategy.STRATIFIED
    try:
        return ContextStrategy(name)
    except ValueError as exc:
        expected = ", ".join(repr(member.value) for member in ContextStrategy)
        raise ValueError(
            f"unknown context strategy {name!r}; expected one of {expected}."
        ) from exc

strategy_admits

strategy_admits(strategy: ContextStrategy, contribution: PromptContribution) -> bool

True when strategy assembles this contribution at all.

The only axis is hydration: JIT declines contributions whose content only exists after a retrieval pass, because performing that pass is exactly what JIT exists to avoid.

Source code in src/symfonic/capabilities/prompting/strategies.py
def strategy_admits(strategy: ContextStrategy, contribution: PromptContribution) -> bool:
    """``True`` when ``strategy`` assembles this contribution at all.

    The only axis is hydration: JIT declines contributions whose content only
    exists after a retrieval pass, because performing that pass is exactly what
    JIT exists to avoid.
    """
    return not (strategy is ContextStrategy.JIT and contribution.requires_hydration)