Skip to content

symfonic.services.switching.binding_stage

binding_stage

DMC-2 — the compiled binding stage, executed once per invocation.

The plan is compiled once and never changes. The binding is not part of it: if it were, a long-lived Agent would keep serving the generation that was current when it was constructed, and a revert would require every adopter to rebuild their agents. So the plan carries the stage — a declaration of which bundle to bind and under whose scope — and executing that stage at the start of each invocation is what captures one atomic vector for that invocation alone.

BindingStage dataclass

BindingStage(bundle_id: str, tenant_scope_hash: str = '', inherited_pin: InvocationPin | None = None)

A bind-phase stage: what to bind, never what was bound.

Deliberately holds no vector, epoch, or binding source. Two invocations of one compiled plan must be able to observe two different generations, and a stage that cached a resolved vector could not express that.

child

child(pin: InvocationPin) -> BindingStage

Derive the stage a sub-agent runs under (EFX-L-2: never widening).

A child inherits the parent's pin rather than resolving its own. A child that re-resolved could land on a newer generation mid-way through its parent's invocation, which is the exact mixed-generation run that CUT-SS-4 forbids.

Source code in src/symfonic/services/switching/binding_stage.py
def child(self, pin: InvocationPin) -> BindingStage:
    """Derive the stage a sub-agent runs under (EFX-L-2: never widening).

    A child inherits the parent's pin rather than resolving its own. A
    child that re-resolved could land on a newer generation mid-way through
    its parent's invocation, which is the exact mixed-generation run that
    CUT-SS-4 forbids.
    """
    if pin.bundle_id != self.bundle_id:
        raise ContractViolationError(
            f"a child binding stage inherits its parent's pin; the supplied pin "
            f"names bundle {pin.bundle_id!r} but the stage binds "
            f"{self.bundle_id!r}."
        )
    return replace(self, inherited_pin=pin)

execute async

execute(admission: AdmissionController, *, invocation_id: str) -> BoundGeneration

Capture exactly one generation vector for one invocation.

Source code in src/symfonic/services/switching/binding_stage.py
async def execute(
    self, admission: AdmissionController, *, invocation_id: str
) -> BoundGeneration:
    """Capture exactly one generation vector for one invocation."""
    return await admission.admit(
        invocation_id=invocation_id,
        bundle_id=self.bundle_id,
        tenant_scope_hash=self.tenant_scope_hash,
        inherited_pin=self.inherited_pin,
    )