Skip to content

symfonic.migration.framework_config_contract

framework_config_contract

Contract for translating FrameworkConfig into a public v11 Agent.

Only named settings translate. Stock model and recursion values leave the provider's model and native round budget in place; changed model fields apply over the provider default. Unsupported active values refuse before Agent, provider, memory, or tool construction. Inactive retired values ask for nothing. A dependent of auto_consolidate is inert when that flag is off.

The two COMPOSITION_SWITCHES (auto_hydrate, lazy_tooling) may be False without caller capabilities. Legacy automatic consolidation is retired: auto_consolidate=False must be explicit for this migration adapter, even when all other fields are stock. Native exact-case QUICK is composed through AgentHost separately; no legacy default or PostgreSQL parity is inferred.

UnsupportedFrameworkSettingError

UnsupportedFrameworkSettingError(refused: Sequence[tuple[str, str]], *, composed: bool)

Bases: ConfigurationError

Active FrameworkConfig settings outside this boundary's contract.

Raised at composition, before any Agent exists. settings lists every refused path at once, so a migration sees the whole set rather than one field per attempt.

Source code in src/symfonic/migration/framework_config_contract.py
def __init__(self, refused: Sequence[tuple[str, str]], *, composed: bool) -> None:
    self.settings = tuple(path for path, _ in refused)
    self.contract = FRAMEWORK_CONFIG_CONTRACT
    self.line = LEVER_RETIREMENT_LINE
    self.pin = LAST_PUBLISHED_HONOURING_LINE
    details = "; ".join(f"{path}: {why}" for path, why in refused)
    super().__init__(
        f"agent_from_config ({self.contract}) refuses active FrameworkConfig "
        f"settings this boundary does not support: {details}. Remove them, "
        "or compose the native capability on the Agent where one exists; "
        f"this composition does not read them. Settings retired on {self.line} "
        f"are honoured only by symfonic-core {self.pin}."
        + (
            " Composition switches are refused at False because the caller "
            "composed capabilities this boundary does not inspect."
            if composed and any(path in COMPOSITION_SWITCHES for path, _ in refused)
            else ""
        )
    )

unsupported_settings

unsupported_settings(config: FrameworkConfig, *, composed: bool) -> tuple[tuple[str, str], ...]

Each refused (path, reason); empty when the config is composable.

Source code in src/symfonic/migration/framework_config_contract.py
def unsupported_settings(config: FrameworkConfig, *, composed: bool) -> tuple[tuple[str, str], ...]:
    """Each refused ``(path, reason)``; empty when the config is composable."""
    if type(config) is not FrameworkConfig:
        # A subclass can move a stock default, which the baseline would hide.
        why = f"{type(config).__name__} is a subclass; only an exact FrameworkConfig is translated"
        return ((_CONFIG_TYPE_PATH, why),)
    refused = _out_of_domain(config) + _model_refusals(config)
    if config.auto_consolidate is not False or "auto_consolidate" not in config.model_fields_set:
        refused.append(("auto_consolidate", _AUTO_CONSOLIDATE_REFUSAL))
    for path, value in _differences(config):
        if path == "auto_consolidate" and value is False:
            continue
        if path in TRANSLATED_SETTINGS:
            continue
        if path in COMPOSITION_SWITCHES and not composed:
            continue
        if config.auto_consolidate is False and path in _CONSOLIDATION_GATED:
            continue
        retired = RETIRED_SETTINGS.get(path)
        if retired is not None and not retired.is_use(value):
            continue
        refused.append((path, _reason(path, value)))
    return tuple(refused)