Skip to content

symfonic.agent.config

config

FrameworkConfig -- unified configuration for the symfonic agent framework.

Composes core AgentConfig and memory OrchestratorConfig with framework-specific behaviour flags.

FrameworkConfig

Bases: BaseModel

Immutable configuration composing core and memory configs.

Framework-specific flags control automatic hydration, consolidation, lazy tool routing, and streaming.

v8.7.1: unknown kwargs are ignored but logged once (WARNING). The class previously set only frozen=True while Pydantic's extra default is "ignore", so a typo'd or renamed flag on this 100+-flag config was dropped silently. Rather than hard-fail (extra="forbid" would break adopters passing stale flags on upgrade), unknown fields are still dropped -- preserving the original wire behaviour -- but each is logged once via _warn_unknown_fields so the typo is visible.

task_scheduler property

task_scheduler: TaskSchedulerProtocol

Return the configured scheduler or a NullScheduler fallback.

child classmethod

child(
    parent: FrameworkConfig,
    *,
    name: str | None = None,
    description: str | None = None,
    model_name: str | None = None,
    temperature: float | None = None,
    max_tokens: int | None = None,
    **overrides: Any,
) -> FrameworkConfig

Derive a sub-agent config from parent, overriding only what's named.

v9.1.0 (issue #28). The inheritance primitive the declarative SubAgentSpec (#27) builds on. A child agent inherits every behaviour flag from parent (auto_hydrate, auto_consolidate, lazy_tooling, enable_hms_prompt, the whole agent/orchestrator config, ...) so sub-agents don't drift from the parent's settings; only the arguments you pass are overridden.

Parameters:

Name Type Description Default
parent FrameworkConfig

The config to inherit from.

required
name str | None

Sets the child DomainTemplate.name (a copy of the parent domain with this name). Ignored when a full domain is passed via overrides.

None
description str | None

Sets the child DomainTemplate.description (same copy rules as name).

None
model_name str | None

Override the child ModelConfig.model_name while inheriting the rest of the parent's model (temperature, max_tokens, sampling, retries).

None
temperature float | None

Override the child ModelConfig.temperature.

None
max_tokens int | None

Override the child ModelConfig.max_tokens.

None
**overrides Any

Any other top-level FrameworkConfig field to override verbatim (e.g. domain=..., lazy_tooling=False).

{}

Returns:

Type Description
FrameworkConfig

A new frozen FrameworkConfig; parent is unmodified.

Source code in src/symfonic/agent/config.py
@classmethod
def child(
    cls,
    parent: FrameworkConfig,
    *,
    name: str | None = None,
    description: str | None = None,
    model_name: str | None = None,
    temperature: float | None = None,
    max_tokens: int | None = None,
    **overrides: Any,
) -> FrameworkConfig:
    """Derive a sub-agent config from ``parent``, overriding only what's named.

    v9.1.0 (issue #28). The inheritance primitive the declarative
    ``SubAgentSpec`` (#27) builds on. A child agent inherits **every**
    behaviour flag from ``parent`` (auto_hydrate, auto_consolidate,
    lazy_tooling, enable_hms_prompt, the whole ``agent``/``orchestrator``
    config, ...) so sub-agents don't drift from the parent's settings;
    only the arguments you pass are overridden.

    Args:
        parent: The config to inherit from.
        name: Sets the child ``DomainTemplate.name`` (a copy of the
            parent domain with this name). Ignored when a full ``domain``
            is passed via ``overrides``.
        description: Sets the child ``DomainTemplate.description`` (same
            copy rules as ``name``).
        model_name: Override the child ``ModelConfig.model_name`` while
            inheriting the rest of the parent's model (temperature,
            max_tokens, sampling, retries).
        temperature: Override the child ``ModelConfig.temperature``.
        max_tokens: Override the child ``ModelConfig.max_tokens``.
        **overrides: Any other top-level ``FrameworkConfig`` field to
            override verbatim (e.g. ``domain=...``, ``lazy_tooling=False``).

    Returns:
        A new frozen ``FrameworkConfig``; ``parent`` is unmodified.
    """
    import dataclasses as _dc

    update: dict[str, Any] = {}

    # Domain: a full ``domain`` override always wins; otherwise derive a
    # copy of the parent domain with the given name/description.
    if "domain" not in overrides and (
        name is not None or description is not None
    ):
        dom_update: dict[str, Any] = {}
        if name is not None:
            dom_update["name"] = name
        if description is not None:
            dom_update["description"] = description
        update["domain"] = parent.domain.model_copy(update=dom_update)

    # Model: rebuild the nested frozen ``ModelConfig`` on a copied
    # ``AgentConfig`` when any model knob is overridden.
    if model_name is not None or temperature is not None or max_tokens is not None:
        model_update: dict[str, Any] = {}
        if model_name is not None:
            model_update["model_name"] = model_name
        if temperature is not None:
            model_update["temperature"] = temperature
        if max_tokens is not None:
            model_update["max_tokens"] = max_tokens
        new_model = _dc.replace(parent.agent.model, **model_update)
        update["agent"] = _dc.replace(parent.agent, model=new_model)

    update.update(overrides)
    assembled = parent.model_copy(update=update)
    # v9.2.1 (review P2a): ``model_copy(update=...)`` skips validation, so
    # an out-of-range override (e.g. hydration_threshold=99) or a typo'd
    # field would be silently retained. Re-validate the assembled mapping
    # so field constraints are enforced and unknown keys are dropped +
    # warned (the ``_warn_unknown_fields`` before-validator). Nested model
    # instances are passed through unchanged (pydantic does not revalidate
    # instances), so object identity of ``agent``/``domain``/``scheduler``
    # is preserved.
    return cls.model_validate(dict(assembled.__dict__))

resolve_force_tool_choice

resolve_force_tool_choice() -> Literal[
    "off", "soft", "hard"
]

v7.20.0 T-7.20.0.8: single source of truth for the effective force-lever mode the engine should use.

Precedence (deliberate):

  1. procedural_force_tool_choice set explicitly to non-default ("soft" or "hard") wins — that's the precise field.
  2. Legacy procedural_force_first_action_tool=True with the enum at its default "off" resolves to "soft" — preserves v7.19.5 runtime semantics (clean abstain on provider refusal).
  3. Both defaults → "off" (no forcing).

The engine's force-resolver site at engine.py::_maybe_resolve_forced_tool_choice MUST consult this method rather than reading either field directly so the precedence rule lives in exactly one place.

Source code in src/symfonic/agent/config.py
def resolve_force_tool_choice(self) -> Literal["off", "soft", "hard"]:
    """v7.20.0 T-7.20.0.8: single source of truth for the effective
    force-lever mode the engine should use.

    Precedence (deliberate):

    1. ``procedural_force_tool_choice`` set explicitly to non-default
       (``"soft"`` or ``"hard"``) wins — that's the precise field.
    2. Legacy ``procedural_force_first_action_tool=True`` with the
       enum at its default ``"off"`` resolves to ``"soft"`` —
       preserves v7.19.5 runtime semantics (clean abstain on
       provider refusal).
    3. Both defaults → ``"off"`` (no forcing).

    The engine's force-resolver site at
    ``engine.py::_maybe_resolve_forced_tool_choice`` MUST consult
    this method rather than reading either field directly so the
    precedence rule lives in exactly one place.
    """
    # Step 1: enum-set-explicit wins (covers "soft" and "hard").
    if self.procedural_force_tool_choice != "off":
        return self.procedural_force_tool_choice
    # Step 2: legacy bool with default enum maps to "soft".
    if self.procedural_force_first_action_tool:
        return "soft"
    # Step 3: both at default.
    return "off"

with_defaults classmethod

with_defaults() -> FrameworkConfig

Create a config with all default values.

Source code in src/symfonic/agent/config.py
@classmethod
def with_defaults(cls) -> FrameworkConfig:
    """Create a config with all default values."""
    return cls()