Skip to content

symfonic.agent.cutover.routes

routes

The per-capability cutover switches the legacy engine routes through.

A switch is a pair: the migrated implementation and the legacy path that stays dormant behind it. Recording the fallback by name is what makes the rollback story checkable — a switch whose legacy_fallback no longer resolves is a deletion that happened before T4.4.6 retired it, and that is a thing a test can notice.

Nothing here reads an environment variable or a file. The route is a function of filed cutover criteria (criteria.py) plus explicit operator action, so "which implementation served this turn?" has one answer per process and it is derivable from evidence rather than from deployment trivia.

CapabilitySwitch dataclass

CapabilitySwitch(capability: str, migrated_by: str, verified_by: str, legacy_fallback: str, retired_by: str = 'T4.4.6')

One capability's migration, its verification, and its way back.

Reserved for names dispatch actually consults. Four today — :data:INVOCATION_RUN, :data:INVOCATION_STREAM, :data:INVOCATION_STREAM_TYPED and :data:INVOCATION_CONTINUATION — and a name earns one by owning an atomic segment of a turn, not by having an implementation.

CoverageRegister dataclass

CoverageRegister(capability: str, migrated_by: str, verified_by: str, legacy_fallback: str, retired_by: str = 'T4.4.6')

A capability that was built — and that routes nothing.

Nine of the eleven names this module used to call switches were registers: route_for("memory") answered kernel or legacy and no dispatch read it, so flipping one changed which turn? None. They were counted as migration progress all the same, and "nine of eleven flipped" measured how much code existed rather than how much of it ran.

The distinction that decides which type a name gets: a switch owns an atomic segment of a turn, so flipping it changes what serves that turn. A register records that a capability was migrated and verified, which is real evidence and a different claim.

Memory and prompting are the sharpest illustration. Both are composed and dispatched since #14 and #21 — through the envelope, on evidence a bundle carries. Their registers still route nothing, and calling them switches would say the flip did work the envelope actually did.

Route

Bases: StrEnum

Which implementation of a capability serves an invocation.

register_for

register_for(capability: str) -> CoverageRegister

The coverage register named capability, or a ConfigurationError.

Registers answer "was this built and verified?" -- never "what serves this turn?". Asking a switch for evidence is the mirror mistake and fails the same way.

Source code in src/symfonic/agent/cutover/registers.py
def register_for(capability: str) -> CoverageRegister:
    """The coverage register named ``capability``, or a ``ConfigurationError``.

    Registers answer "was this built and verified?" -- never "what serves this
    turn?". Asking a switch for evidence is the mirror mistake and fails the
    same way.
    """
    # Imported inside the function, because ``routes`` imports this module:
    # the mirror-mistake message is the one place a register has to know the
    # switch ledger, and a module-level import would make that one message a
    # circular dependency between the two halves of one split.
    from symfonic.agent.cutover.routes import CAPABILITY_SWITCHES
    from symfonic.core.contracts.errors import ConfigurationError

    try:
        return COVERAGE_REGISTERS[capability]
    except KeyError:
        pass
    if capability in CAPABILITY_SWITCHES:
        raise ConfigurationError(
            f"{capability!r} is a switch, not a coverage register: dispatch "
            "reads it every turn. Ask switch_for() for it."
        )
    raise ConfigurationError(
        f"unknown capability {capability!r}; known registers are "
        f"{sorted(COVERAGE_REGISTERS)}."
    )

switch_for

switch_for(capability: str) -> CapabilitySwitch

The switch named capability, or a ConfigurationError.

Unknown names fail loudly rather than defaulting: a mistyped capability that silently answered "legacy" would be a cutover nobody notices did not happen.

Source code in src/symfonic/agent/cutover/routes.py
def switch_for(capability: str) -> CapabilitySwitch:
    """The switch named ``capability``, or a ``ConfigurationError``.

    Unknown names fail loudly rather than defaulting: a mistyped capability
    that silently answered "legacy" would be a cutover nobody notices did not
    happen.
    """
    from symfonic.core.contracts.errors import ConfigurationError

    try:
        return CAPABILITY_SWITCHES[capability]
    except KeyError:
        pass
    if capability in COVERAGE_REGISTERS:
        # Named apart from an unknown capability, because this one exists and
        # the caller's expectation is what is wrong. Answering with a route --
        # which this function used to do -- is how nine names that dispatch
        # never reads came to be counted as flipped switches.
        raise ConfigurationError(
            f"{capability!r} is a coverage register, not a switch: the "
            "capability is migrated and verified, and no dispatch reads it, so "
            "there is no route to give you. Ask register_for() for its "
            "evidence. A name becomes a switch when flipping it changes what "
            f"serves a turn; today that is {sorted(CAPABILITY_SWITCHES)}."
        )
    raise ConfigurationError(
        f"unknown cutover capability {capability!r}; known switches are "
        f"{sorted(CAPABILITY_SWITCHES)} and known registers are "
        f"{sorted(COVERAGE_REGISTERS)}."
    )