Skip to content

symfonic.agent.backend.binding

binding

Handing the kernel's palette projector a way to speak to a provider.

PaletteProjector narrows a palette and knows nothing about how a provider is called -- wrap and bind_with are both injected for that reason. This is the bind_with the compiler supplies.

Its own module rather than four more lines in plan.py: that file reached its size budget, and the seam is real. plan.py answers "what does one compiled invocation hold"; this answers "how is a tool set put onto a model", which is the capability's question and is why the import below may live here and may not live in the kernel.

palette_projector

palette_projector(provider: Any, config: Any, chat_model: Any, tools: Any, bound: Any, wrap: Any) -> Any

The projector one compiled plan holds, with both injections made here.

Built over the unbound model so a turn's resolved subset can be bound without recompiling: the plan stays immutable and the projector is one more frozen reference (TA8.63).

Both injections are made at this point because this is the only place that has what they need. forcing needs the provider the agent was composed with and the ModelConfig the plan resolved -- neither can change per turn, so the question is asked once. bind_with needs to name a capability, which this side of the seam may do and the kernel may not.

Source code in src/symfonic/agent/backend/binding.py
def palette_projector(
    provider: Any, config: Any, chat_model: Any, tools: Any, bound: Any, wrap: Any
) -> Any:
    """The projector one compiled plan holds, with both injections made here.

    Built over the *unbound* model so a turn's resolved subset can be bound
    without recompiling: the plan stays immutable and the projector is one
    more frozen reference (TA8.63).

    Both injections are made at this point because this is the only place
    that has what they need. ``forcing`` needs the provider the agent was
    composed with and the ``ModelConfig`` the plan resolved -- neither can
    change per turn, so the question is asked once. ``bind_with`` needs to
    name a capability, which this side of the seam may do and the kernel may
    not.
    """
    from symfonic.kernel.contracts.forcing import forcing_support
    from symfonic.kernel.palette import PaletteProjector

    return PaletteProjector(
        chat_model,
        tuple(tools),
        bound,
        wrap=wrap,
        forcing=forcing_support(provider, config),
        bind_with=_bind_through_the_binder,
    )