Skip to content

Templates

Pre-built company templates, personality presets, and template builder.

Enums

enums

Company-template domain enumerations.

SkillPattern

Bases: StrEnum

Skill interaction patterns for company templates.

Based on the five-pattern taxonomy: Tool Wrapper, Generator, Reviewer, Inversion, and Pipeline.

Attributes:

Name Type Description
TOOL_WRAPPER

On-demand domain expertise; agents self-direct using specialized context.

GENERATOR

Consistent structured output from reusable templates.

REVIEWER

Modular rubric-based evaluation; separates what to check from how to check it.

INVERSION

Agent interviews user before acting; structured requirements gathering.

PIPELINE

Strict sequential workflow with hard checkpoints between stages.

PostureName

Bases: StrEnum

Named operating postures a template can declare.

A posture expands to a coherent runtime feature-flag bundle (knowledge substrate, conversational chat modes, red-team gate, mid-flight steering, cost-dial) so a template configures behaviour, not just an org chart. The toolsmith is intentionally excluded: enabling it needs an explicit capability allowlist and stays an operator opt-in.

Attributes:

Name Type Description
AUTONOMOUS

High-autonomy delivery; steering and knowledge on, human chat off.

SUPERVISED_CLIENT_FACING

Human-in-the-loop client work; group chat and agent invite on for stakeholder collaboration.

KNOWLEDGE_HEAVY

Knowledge-substrate-grounded work; entailment grounding and a shared knowledge base.

COST_DISCIPLINED

Budget-first operation; auto-downgrade on, optional features off to minimise spend.

SECURITY_HARDENED

Security-first operation; red-team completion gate on at a lowered stakes floor, self-extension off.

RESEARCH_AUTONOMOUS

Autonomous inquiry; knowledge substrate, steering, and clarify-or-park + routing proposals on.

Schema

schema

Template schema: Pydantic models for company templates.

TemplateVariable pydantic-model

Bases: BaseModel

A user-configurable variable within a template.

Variables declared here are extracted from the template YAML during the first parsing pass (before Jinja2 rendering). The variables section must use plain YAML -- no Jinja2 expressions.

Attributes:

Name Type Description
name NotBlankStr

Variable name (used in {{ name }} placeholders).

description str

Human-readable description for prompts/docs.

var_type Literal['str', 'int', 'float', 'bool']

Expected Python type name.

default str | int | float | bool | None

Default value (None means no default is provided). The required attribute determines whether the user must supply a value.

required bool

Whether the user must provide this value.

Config:

  • frozen: True
  • extra: forbid
  • allow_inf_nan: False

Fields:

Validators:

  • _validate_required_has_no_default
  • _validate_hidden_is_not_required
  • _validate_default_matches_var_type

name pydantic-field

name

Variable name

description pydantic-field

description = ''

Human-readable description

var_type pydantic-field

var_type = 'str'

Expected value type

default pydantic-field

default = None

Default value

required pydantic-field

required = False

Whether required

hidden pydantic-field

hidden = False

When True the setup wizard does not render an input for this variable; its default is applied. Reserved for advanced knobs the operator can change later in the dashboard, so the wizard stays focused on the few choices that matter up front.

TemplateAgentConfig pydantic-model

Bases: BaseModel

Agent definition within a template.

Uses string references and presets rather than full AgentConfig. The renderer expands these into full agent configuration dicts.

Attributes:

Name Type Description
role NotBlankStr

Built-in role name (case-insensitive match to role catalog).

name NotBlankStr | None

Agent name (may contain Jinja2 placeholders). None triggers auto-generation during rendering.

level SeniorityLevel

Seniority level override.

model NotBlankStr | dict[str, JsonValue]

A model reference: either an explicit configured model id/alias string, or a structured ModelRequirement dict with priority / min_context / requires_* capability flags and an optional family / model_pattern. Built-in templates use capability dicts so they resolve on any provider.

personality_preset NotBlankStr | None

Named personality preset from the presets registry.

personality dict[str, JsonValue] | None

Inline personality config dict (alternative to personality_preset).

department NotBlankStr | None

Department override (None uses the template system default during rendering).

strategic_output_mode StrategicOutputMode | None

Strategic output mode override for this agent (StrategicOutputMode | None). None inherits the company strategy config default.

merge_id NotBlankStr | None

Stable identity for inheritance merge. When a template has multiple agents with the same (role, department) pair, merge_id disambiguates them so child templates can target a specific agent. None means no merge_id is set.

remove bool

Merge directive -- when True, removes matching parent agent during inheritance.

Config:

  • frozen: True
  • extra: forbid
  • allow_inf_nan: False

Fields:

Validators:

  • _validate_modelmodel
  • _deep_copy_personality
  • _validate_personality_mutual_exclusion

role pydantic-field

role

Built-in role name

name pydantic-field

name = None

Agent name (may have Jinja2 vars); None triggers auto-generation

level pydantic-field

level = SeniorityLevel.MID

Seniority level

model pydantic-field

model

Explicit model id/alias or a structured ModelRequirement dict

personality_preset pydantic-field

personality_preset = None

Named personality preset

personality pydantic-field

personality = None

Inline personality override (alternative to preset)

department pydantic-field

department = None

Department override

strategic_output_mode pydantic-field

strategic_output_mode = None

Strategic output mode override for this agent

merge_id pydantic-field

merge_id = None

Stable identity for inheritance merge; None means unset

remove pydantic-field

remove = False

Merge directive: remove matching parent agent

TemplateDepartmentConfig pydantic-model

Bases: BaseModel

Department definition within a template.

Provides structural information -- department names, budget allocations, the head role, reporting lines, and operational policies.

Attributes:

Name Type Description
name NotBlankStr

Department name (standard or custom).

budget_percent float

Percentage of company budget (0-100).

head_role NotBlankStr | None

Role name of the department head.

head_merge_id NotBlankStr | None

Optional merge_id of the head agent. Should be provided when multiple agents share the same role used in head_role.

reporting_lines tuple[dict[str, str], ...]

Reporting line definitions within this department.

policies dict[str, JsonValue] | None

Department operational policies.

ceremony_policy dict[str, JsonValue] | None

Per-department ceremony policy override (dict[str, JsonValue] | None). None inherits the project-level policy.

remove bool

Merge directive -- when True, removes matching parent department during inheritance.

Config:

  • frozen: True
  • extra: forbid
  • allow_inf_nan: False

Fields:

Validators:

  • _validate_head_merge_id_requires_head_role

name pydantic-field

name

Department name

budget_percent pydantic-field

budget_percent = 0.0

Percentage of company budget

head_role pydantic-field

head_role = None

Role name of department head

head_merge_id pydantic-field

head_merge_id = None

merge_id of the head agent for disambiguation

reporting_lines pydantic-field

reporting_lines = ()

Reporting line definitions

policies pydantic-field

policies = None

Department operational policies

ceremony_policy pydantic-field

ceremony_policy = None

Per-department ceremony policy override

remove pydantic-field

remove = False

Merge directive: remove matching parent department

TemplateMetadata pydantic-model

Bases: BaseModel

Metadata about a company template.

Attributes:

Name Type Description
name NotBlankStr

Template display name.

description str

What this template is for.

version NotBlankStr

Semantic version string.

company_type CompanyType

Which CompanyType this template creates.

min_agents int

Minimum number of agents.

max_agents int

Maximum number of agents.

tags tuple[NotBlankStr, ...]

Categorization tags.

skill_patterns tuple[SkillPattern, ...]

Skill interaction patterns this template exhibits.

Config:

  • frozen: True
  • extra: forbid
  • allow_inf_nan: False

Fields:

Validators:

  • _validate_agent_range
  • _validate_unique_skill_patterns

name pydantic-field

name

Template display name

description pydantic-field

description = ''

Template description

version pydantic-field

version = '1.0.0'

Semantic version

company_type pydantic-field

company_type

Company type this template creates

min_agents pydantic-field

min_agents = 1

Minimum agents

max_agents pydantic-field

max_agents = 100

Maximum agents

tags pydantic-field

tags = ()

Categorization tags

skill_patterns pydantic-field

skill_patterns = ()

Skill interaction patterns

TemplateMemoryConfig pydantic-model

Bases: BaseModel

Template-level memory configuration overrides.

Attributes:

Name Type Description
embedder EmbedderOverrideConfig | None

Optional embedder override for the template.

Config:

  • frozen: True
  • extra: forbid
  • allow_inf_nan: False

Fields:

embedder pydantic-field

embedder = None

Optional embedder override

CompanyTemplate pydantic-model

Bases: BaseModel

A complete company template definition.

This is the top-level model parsed from a template YAML file during the first pass (before Jinja2 rendering). It holds metadata, variable declarations, and the structural definitions for agents and departments.

The raw YAML text is stored separately by the loader for the second pass (Jinja2 rendering).

Attributes:

Name Type Description
metadata TemplateMetadata

Template metadata.

variables tuple[TemplateVariable, ...]

Declared template variables (plain YAML, no Jinja2).

agents tuple[TemplateAgentConfig, ...]

Template agent definitions.

departments tuple[TemplateDepartmentConfig, ...]

Template department definitions.

workflow WorkflowType

Workflow name.

workflow_config dict[str, JsonValue]

Optional Kanban/Sprint sub-configurations, validated as WorkflowConfig on the rendered RootConfig.

communication NotBlankStr

Communication pattern name.

budget_monthly float

Default monthly budget in the configured currency.

autonomy dict[str, JsonValue]

Autonomy configuration dict (e.g. {"level": "semi"}).

workflow_handoffs tuple[dict[str, JsonValue], ...]

Cross-department workflow handoff definitions.

escalation_paths tuple[dict[str, JsonValue], ...]

Cross-department escalation path definitions.

extends NotBlankStr | None

Parent template name for inheritance (None for standalone templates).

memory TemplateMemoryConfig

Memory configuration overrides (e.g. embedder settings).

Config:

  • frozen: True
  • extra: forbid
  • allow_inf_nan: False

Fields:

Validators:

  • _normalize_extendsextends
  • _validate_agent_count_in_range
  • _validate_unique_variable_names
  • _validate_unique_department_names
  • _validate_unique_pack_names

metadata pydantic-field

metadata

Template metadata

variables pydantic-field

variables = ()

Declared template variables

agents pydantic-field

agents

Template agent definitions

departments pydantic-field

departments = ()

Template department definitions

workflow pydantic-field

workflow = WorkflowType.AGILE_KANBAN

Workflow type

workflow_config pydantic-field

workflow_config

Optional Kanban/Sprint sub-configurations. Validated as WorkflowConfig on the rendered RootConfig.

communication pydantic-field

communication = 'hybrid'

Communication pattern

budget_monthly pydantic-field

budget_monthly = 50.0

Default monthly budget in the configured currency

autonomy pydantic-field

autonomy

Autonomy configuration

workflow_handoffs pydantic-field

workflow_handoffs = ()

Cross-department workflow handoffs

escalation_paths pydantic-field

escalation_paths = ()

Cross-department escalation paths

extends pydantic-field

extends = None

Parent template name for inheritance

uses_packs pydantic-field

uses_packs = ()

Pack names to compose into this template

posture pydantic-field

posture = None

Named operating posture that expands to a feature-flag bundle

memory pydantic-field

memory

Memory configuration overrides.

Loader

loader

Template loading from built-in, user directory, and file-system sources.

Implements a two-pass loading strategy:

  • Pass 1: YAML-parse the template to extract metadata and the variables section (which uses plain YAML, no Jinja2).
  • Pass 2: Performed later by the renderer -- Jinja2-renders the raw YAML text, then YAML-parses the result.

Both are returned bundled as a :class:LoadedTemplate dataclass.

TemplateInfo dataclass

TemplateInfo(
    name,
    display_name,
    description,
    source,
    tags=(),
    skill_patterns=(),
    variables=(),
    agent_count=0,
    department_count=0,
    autonomy_level=SEMI,
    workflow="agile_kanban",
    posture=None,
)

Summary information about an available template.

Attributes:

Name Type Description
name str

Template identifier (e.g. "startup").

display_name str

Human-readable display name.

description str

Short description.

source Literal['builtin', 'user']

Where the template was found ("builtin" or "user").

tags tuple[str, ...]

Free-form categorization tags for filtering and discovery.

skill_patterns tuple[SkillPattern, ...]

Skill design pattern identifiers describing how the template's agents interact.

variables tuple[TemplateVariable, ...]

User-configurable TemplateVariable instances extracted from the template's variables section.

agent_count int

Number of agents defined in the template.

department_count int

Number of departments defined in the template.

autonomy_level AutonomyLevel

Autonomy level governing approval routing.

workflow str

Workflow type (e.g. "agile_kanban", "kanban").

LoadedTemplate dataclass

LoadedTemplate(template, raw_yaml, source_name)

Result of loading a template: structured data + raw text.

Attributes:

Name Type Description
template CompanyTemplate

Validated CompanyTemplate from Pass 1.

raw_yaml str

Raw YAML text for Pass 2 (Jinja2 rendering).

source_name str

Label for error messages.

list_templates

list_templates()

Return all available templates (user directory + built-in).

User templates override built-in ones. Sorted by name.

Returns:

Type Description
tuple[TemplateInfo, ...]

Sorted tuple of :class:TemplateInfo objects.

Source code in src/synthorg/templates/loader.py
def list_templates() -> tuple[TemplateInfo, ...]:
    """Return all available templates (user directory + built-in).

    User templates override built-in ones. Sorted by name.

    Returns:
        Sorted tuple of :class:`TemplateInfo` objects.
    """
    seen: dict[str, TemplateInfo] = {}

    # User templates (higher priority).
    _collect_user_templates(seen)

    # Built-in templates (lower priority).
    for name in sorted(BUILTIN_TEMPLATES):
        if name not in seen:
            try:
                loaded = _load_builtin(name)
                seen[name] = _template_info_from_loaded(
                    name,
                    loaded,
                    "builtin",
                )
            except (TemplateRenderError, TemplateValidationError, OSError) as exc:
                logger.warning(
                    TEMPLATE_BUILTIN_DEFECT,
                    template_name=name,
                    error_type=type(exc).__name__,
                    error=safe_error_description(exc),
                )

    return tuple(info for _, info in sorted(seen.items()))

list_builtin_templates

list_builtin_templates()

Return names of all built-in templates.

Returns:

Type Description
tuple[str, ...]

Sorted tuple of built-in template names.

Source code in src/synthorg/templates/loader.py
def list_builtin_templates() -> tuple[str, ...]:
    """Return names of all built-in templates.

    Returns:
        Sorted tuple of built-in template names.
    """
    return tuple(sorted(BUILTIN_TEMPLATES))

load_template

load_template(name)

Load a template by name: user directory first, then builtins.

Parameters:

Name Type Description Default
name str

Template name (e.g. "startup").

required

Returns:

Type Description
LoadedTemplate

class:LoadedTemplate with validated data and raw YAML.

Raises:

Type Description
TemplateNotFoundError

If no template with name exists.

Source code in src/synthorg/templates/loader.py
def load_template(name: str) -> LoadedTemplate:
    """Load a template by name: user directory first, then builtins.

    Args:
        name: Template name (e.g. ``"startup"``).

    Returns:
        :class:`LoadedTemplate` with validated data and raw YAML.

    Raises:
        TemplateNotFoundError: If no template with *name* exists.
    """
    name_clean = normalize_ascii_lowercase(name)
    logger.debug(TEMPLATE_LOAD_START, template_name=name_clean)

    # Sanitize to prevent path traversal (OS-independent).
    if "/" in name_clean or "\\" in name_clean or ".." in name_clean:
        msg = f"Invalid template name {name!r}: must not contain path separators"
        logger.warning(TEMPLATE_LOAD_INVALID_NAME, template_name=name)
        raise TemplateNotFoundError(
            msg,
            locations=(ConfigLocation(file_path=f"<template:{name}>"),),
        )

    # Try user directory first.
    if _USER_TEMPLATES_DIR.is_dir():
        user_path = _USER_TEMPLATES_DIR / f"{name_clean}.yaml"
        if user_path.is_file():
            result = _load_from_file(user_path)
            logger.debug(
                TEMPLATE_LOAD_SUCCESS,
                template_name=name_clean,
                source="user",
            )
            return result

    # Fall back to builtins.
    if name_clean in BUILTIN_TEMPLATES:
        result = _load_builtin(name_clean)
        logger.debug(
            TEMPLATE_LOAD_SUCCESS,
            template_name=name_clean,
            source="builtin",
        )
        return result

    available = list_builtin_templates()
    logger.error(
        TEMPLATE_LOAD_ERROR,
        template_name=name,
        available=list(available),
    )
    msg = f"Unknown template {name!r}. Available: {list(available)}"
    raise TemplateNotFoundError(
        msg,
        locations=(ConfigLocation(file_path=f"<template:{name}>"),),
    )

load_template_file

load_template_file(path)

Load a template from an explicit file path.

Parameters:

Name Type Description Default
path Path | str

Path to the template YAML file.

required

Returns:

Type Description
LoadedTemplate

class:LoadedTemplate with validated data and raw YAML.

Raises:

Type Description
TemplateNotFoundError

If the file does not exist.

TemplateValidationError

If validation fails.

Source code in src/synthorg/templates/loader.py
def load_template_file(path: Path | str) -> LoadedTemplate:
    """Load a template from an explicit file path.

    Args:
        path: Path to the template YAML file.

    Returns:
        :class:`LoadedTemplate` with validated data and raw YAML.

    Raises:
        TemplateNotFoundError: If the file does not exist.
        TemplateValidationError: If validation fails.
    """
    path = Path(path)
    if not path.is_file():
        msg = f"Template file not found: {path}"
        logger.warning(TEMPLATE_LOAD_NOT_FOUND, path=str(path))
        raise TemplateNotFoundError(
            msg,
            locations=(ConfigLocation(file_path=str(path)),),
        )
    return _load_from_file(path)

Renderer

renderer

Template rendering: Jinja2 substitution + validation to RootConfig.

Implements the second pass of the two-pass rendering pipeline:

  1. Collect user variables + defaults from the CompanyTemplate.
  2. Render the raw YAML text through a Jinja2 SandboxedEnvironment.
  3. YAML-parse the rendered text.
  4. Build a RootConfig-compatible dict and validate.

Template inheritance (extends) is resolved at the renderer level: each template's Jinja2 is rendered independently, then configs are merged via :func:~synthorg.templates.merge.merge_template_configs.

Config-dict assembly lives in :mod:synthorg.templates._config_assembly; agent expansion lives in :mod:synthorg.templates._agent_expansion.

render_template

render_template(loaded, variables=None, *, locales=None, custom_presets=None)

Render a loaded template into a validated RootConfig.

Resolves template inheritance (extends) before validation.

Parameters:

Name Type Description Default
loaded LoadedTemplate

:class:LoadedTemplate from the loader.

required
variables dict[str, object] | None

User-supplied variable values (overrides defaults).

None
locales list[str] | None

Faker locale codes for auto-name generation. Defaults to all Latin-script locales when None.

None
custom_presets Mapping[str, dict[str, JsonValue]] | None

Optional mapping of custom preset names to personality config dicts for resolving user-defined presets.

None

Returns:

Type Description
RootConfig

Validated, frozen :class:RootConfig.

Raises:

Type Description
TemplateRenderError

If rendering fails.

TemplateValidationError

If validation fails.

TemplateInheritanceError

If inheritance resolution fails.

Source code in src/synthorg/templates/renderer.py
def render_template(
    loaded: LoadedTemplate,
    variables: dict[str, object] | None = None,
    *,
    locales: list[str] | None = None,
    custom_presets: Mapping[str, dict[str, JsonValue]] | None = None,
) -> RootConfig:
    """Render a loaded template into a validated RootConfig.

    Resolves template inheritance (``extends``) before validation.

    Args:
        loaded: :class:`LoadedTemplate` from the loader.
        variables: User-supplied variable values (overrides defaults).
        locales: Faker locale codes for auto-name generation.
            Defaults to all Latin-script locales when ``None``.
        custom_presets: Optional mapping of custom preset names to
            personality config dicts for resolving user-defined presets.

    Returns:
        Validated, frozen :class:`RootConfig`.

    Raises:
        TemplateRenderError: If rendering fails.
        TemplateValidationError: If validation fails.
        TemplateInheritanceError: If inheritance resolution fails.
    """
    logger.info(
        TEMPLATE_RENDER_START,
        source_name=loaded.source_name,
    )
    config_dict = _render_to_dict(
        loaded,
        variables,
        locales=locales,
        custom_presets=custom_presets,
    )

    _apply_effective_posture(loaded.template, config_dict)

    # Merge with defaults and validate.
    merged = deep_merge(default_config_dict(), config_dict)
    result = validate_as_root_config(merged, loaded.source_name)
    logger.info(
        TEMPLATE_RENDER_SUCCESS,
        source_name=loaded.source_name,
    )
    return result

Merge

merge

Template config merging for inheritance.

Provides merge_template_configs which combines a parent config dict with a child config dict, implementing the merge semantics described in the template inheritance design.

merge_template_configs

merge_template_configs(parent, child)

Merge a parent config dict with a child config dict.

Merge strategies by field:

  • company_name, company_type: child wins if present.
  • config, security, budget (dict): deep-merged; child keys override parent.
  • agents (list): merged by (role, department, merge_id) key.
  • departments (list): merged by name (case-insensitive).
  • workflow, workflow_handoffs, escalation_paths, posture: child replaces entirely if present; otherwise inherited from parent.

Parameters:

Name Type Description Default
parent dict[str, object]

Rendered parent config dict (post-Jinja2, pre-defaults).

required
child dict[str, object]

Rendered child config dict (post-Jinja2, pre-defaults).

required

Returns:

Type Description
dict[str, object]

New merged config dict.

Source code in src/synthorg/templates/merge.py
def merge_template_configs(
    parent: dict[str, object],
    child: dict[str, object],
) -> dict[str, object]:
    """Merge a parent config dict with a child config dict.

    Merge strategies by field:

    - ``company_name``, ``company_type``: child wins if present.
    - ``config``, ``security``, ``budget`` (dict): deep-merged; child keys
      override parent.
    - ``agents`` (list): merged by ``(role, department, merge_id)`` key.
    - ``departments`` (list): merged by ``name`` (case-insensitive).
    - ``workflow``, ``workflow_handoffs``, ``escalation_paths``, ``posture``:
      child replaces entirely if present; otherwise inherited from parent.

    Args:
        parent: Rendered parent config dict (post-Jinja2, pre-defaults).
        child: Rendered child config dict (post-Jinja2, pre-defaults).

    Returns:
        New merged config dict.
    """
    logger.debug(TEMPLATE_INHERIT_MERGE, action="start")

    result: dict[str, object] = {}

    # Scalars: child wins if present.
    for key in ("company_name", "company_type"):
        if key in child and child[key] is not None:
            result[key] = child[key]
        elif key in parent:
            result[key] = parent[key]

    # Deep-merged dict sections: child keys override parent.
    for section in ("config", "security", "budget"):
        parent_section = parent.get(section, {})
        child_section = child.get(section, {})
        if parent_section or child_section:
            result[section] = deep_merge(
                parent_section if isinstance(parent_section, Mapping) else {},
                child_section if isinstance(child_section, Mapping) else {},
            )

    # Agents: merge by (role, department, merge_id) key.
    parent_agents = parent.get("agents", [])
    child_agents = child.get("agents", [])
    if parent_agents or child_agents:
        result["agents"] = _merge_agents(
            _as_dict_list(parent_agents, "agents"),
            _as_dict_list(child_agents, "agents"),
        )

    # Departments: merge by name.
    parent_depts = parent.get("departments", [])
    child_depts = child.get("departments", [])
    if parent_depts or child_depts:
        result["departments"] = _merge_departments(
            _as_dict_list(parent_depts, "departments"),
            _as_dict_list(child_depts, "departments"),
        )

    # Replace-if-present fields (deep-copied to prevent reference sharing).
    # ``posture`` is child-wins: a child template's declared posture replaces
    # the parent's entirely rather than blending two operating modes.
    for key in ("workflow", "workflow_handoffs", "escalation_paths", "posture"):
        if key in child and child[key] is not None:
            result[key] = copy.deepcopy(child[key])
        elif key in parent:
            result[key] = copy.deepcopy(parent[key])

    logger.debug(TEMPLATE_INHERIT_MERGE, action="done")
    return result

Presets

presets

Personality presets and auto-name generation for templates.

Provides comprehensive personality presets with Big Five dimensions and behavioral enums, plus internationally diverse auto-name generation backed by the Faker library.

get_personality_preset

get_personality_preset(name, custom_presets=None)

Look up a personality preset by name.

Custom presets take precedence over builtins; the result is a fresh one-level copy with any tuple-valued field normalised to a JSON list.

Parameters:

Name Type Description Default
name str

Preset name (case-insensitive, whitespace-stripped).

required
custom_presets Mapping[str, dict[str, JsonValue]] | None

Optional mapping of custom preset names to personality config dicts. Keys must be lowercased.

None

Returns:

Type Description
dict[str, JsonValue]

A fresh JSON-shaped copy of the personality configuration dict.

Raises:

Type Description
KeyError

If the preset name is not found in either source.

Source code in src/synthorg/templates/presets.py
def get_personality_preset(
    name: str,
    custom_presets: Mapping[str, dict[str, JsonValue]] | None = None,
) -> dict[str, JsonValue]:
    """Look up a personality preset by name.

    Custom presets take precedence over builtins; the result is a fresh
    one-level copy with any tuple-valued field normalised to a JSON list.

    Args:
        name: Preset name (case-insensitive, whitespace-stripped).
        custom_presets: Optional mapping of custom preset names to
            personality config dicts.  Keys must be lowercased.

    Returns:
        A fresh JSON-shaped copy of the personality configuration dict.

    Raises:
        KeyError: If the preset name is not found in either source.
    """
    key = normalize_ascii_lowercase(name)
    source: Mapping[str, PresetValue | JsonValue] | None = None
    if custom_presets is not None and key in custom_presets:
        source = custom_presets[key]
    elif key in PERSONALITY_PRESETS:
        source = PERSONALITY_PRESETS[key]
    if source is not None:
        return {
            field: list(value) if isinstance(value, (list, tuple)) else value
            for field, value in source.items()
        }
    available = sorted(PERSONALITY_PRESETS)
    if custom_presets:
        available = sorted({*available, *custom_presets})
    msg = f"Unknown personality preset {name!r}. Available: {available}"
    logger.warning(
        TEMPLATE_PERSONALITY_PRESET_UNKNOWN,
        preset_name=name,
        available=available,
    )
    raise KeyError(msg)

get_strategic_output_default

get_strategic_output_default(level)

Return the default strategic output mode for a seniority level.

Parameters:

Name Type Description Default
level SeniorityLevel

Agent seniority level.

required

Returns:

Type Description
StrategicOutputMode | None

Default strategic output mode, or None if the level has

StrategicOutputMode | None

no strategic default (i.e. strategic output is not applicable).

Source code in src/synthorg/templates/presets.py
def get_strategic_output_default(
    level: SeniorityLevel,
) -> StrategicOutputMode | None:
    """Return the default strategic output mode for a seniority level.

    Args:
        level: Agent seniority level.

    Returns:
        Default strategic output mode, or ``None`` if the level has
        no strategic default (i.e. strategic output is not applicable).
    """
    return STRATEGIC_OUTPUT_DEFAULTS.get(level)

validate_preset_references

validate_preset_references(template, custom_presets=None)

Check all agent personality_preset references against known presets.

Returns a tuple of warning messages for unknown presets. Does not raise -- purely advisory for pre-flight validation and template import/export scenarios.

Parameters:

Name Type Description Default
template CompanyTemplate

Parsed template to validate.

required
custom_presets Mapping[str, dict[str, JsonValue]] | None

Optional custom preset mapping. Keys must be lowercased.

None

Returns:

Type Description
tuple[str, ...]

Tuple of warning strings (empty when all presets are known).

Source code in src/synthorg/templates/presets.py
def validate_preset_references(
    template: CompanyTemplate,
    custom_presets: Mapping[str, dict[str, JsonValue]] | None = None,
) -> tuple[str, ...]:
    """Check all agent personality_preset references against known presets.

    Returns a tuple of warning messages for unknown presets.  Does not
    raise -- purely advisory for pre-flight validation and template
    import/export scenarios.

    Args:
        template: Parsed template to validate.
        custom_presets: Optional custom preset mapping.  Keys must
            be lowercased.

    Returns:
        Tuple of warning strings (empty when all presets are known).
    """
    issues: list[str] = []
    for agent_cfg in template.agents:
        preset = agent_cfg.personality_preset
        if preset is None:
            continue
        key = normalize_ascii_lowercase(preset)
        if custom_presets is not None and key in custom_presets:
            continue
        if key in PERSONALITY_PRESETS:
            continue
        issues.append(
            f"Agent {agent_cfg.role!r} references unknown personality preset {preset!r}"
        )
    return tuple(issues)

generate_auto_name

generate_auto_name(role, *, seed=None, locales=None)

Generate an internationally diverse agent name using Faker.

With seed, a fresh single-locale Faker instance is used so the shared cached instance is never mutated. role is accepted for positional-caller compatibility but does not influence the name; locales defaults to all Latin-script locales when None or empty.

Returns:

Type Description
str

A generated full name string.

Source code in src/synthorg/templates/presets.py
def generate_auto_name(
    role: str,  # noqa: ARG001
    *,
    seed: int | None = None,
    locales: list[str] | None = None,
) -> str:
    """Generate an internationally diverse agent name using Faker.

    With *seed*, a fresh single-locale Faker instance is used so the
    shared cached instance is never mutated.  *role* is accepted for
    positional-caller compatibility but does not influence the name;
    *locales* defaults to all Latin-script locales when None or empty.

    Returns:
        A generated full name string.
    """
    import random  # noqa: PLC0415

    from faker import Faker  # noqa: PLC0415

    from synthorg.templates.locales import ALL_LATIN_LOCALES  # noqa: PLC0415

    locale_list = locales or list(ALL_LATIN_LOCALES)
    try:
        if seed is not None:
            rng = random.Random(seed)  # noqa: S311
            chosen_locale = rng.choice(locale_list)
            # Fresh instance -- never mutate the shared cached one.
            fake = Faker([chosen_locale])
            fake.seed_instance(seed)
        else:
            fake = _get_faker(tuple(locale_list))
        return _two_part_name(fake.first_name, fake.last_name)
    except Exception as exc:  # noqa: BLE001 -- criticals re-raised
        reraise_critical(exc)
        from synthorg.observability.events.template import (  # noqa: PLC0415
            TEMPLATE_NAME_GEN_FAKER_ERROR,
        )

        logger.warning(
            TEMPLATE_NAME_GEN_FAKER_ERROR,
            locales=locale_list[:5],
            seed=seed,
            error_type=type(exc).__name__,
            error=safe_error_description(exc),
        )
        # Fall back to a known-safe locale.
        fallback = Faker(["en_US"])
        if seed is not None:
            fallback.seed_instance(seed)
        return _two_part_name(fallback.first_name, fallback.last_name)

Model Requirements

model_requirements

Structured model requirements and personality-based model affinity.

Provides :class:ModelRequirement for expressing what kind of LLM an agent needs (priority, context window, capability flags, family/pattern, or an explicit example id) and a preset-keyed affinity mapping that supplies capability defaults when the template does not state full requirements.

A template agent references a model by one of three forms, all expressed through :class:ModelRequirement:

  • explicit example id (model_id): pin a concrete configured model.
  • family / pattern (family / model_pattern): resolve to the newest configured model matching the family or glob.
  • capability (requires_tools / requires_vision / requires_reasoning plus priority / min_context): let the matcher pick the best-fitting configured model.

There is no tier-string selection axis: the matcher classifies models by real metadata, and ModelMatch.tier is report-only.

ModelTier module-attribute

ModelTier = Literal['large', 'medium', 'small']

Model capability tier: large (most capable), medium, small (cheapest).

ModelRequirement pydantic-model

Bases: BaseModel

Structured model requirement for a template agent.

Describes what an agent needs from an LLM. Resolution order at match time is explicit model_id first, then family / model_pattern, then capability scoring over the survivors of the hard filters.

Attributes:

Name Type Description
model_id NotBlankStr | None

Explicit configured model id (or alias) to pin. When set, the matcher selects this exact model and skips family/capability scoring.

priority ModelPriority

Optimisation axis when several models clear the filters.

min_context int

Minimum context window in tokens (0 = no minimum).

requires_tools bool

Hard-require function/tool-calling support.

requires_vision bool

Hard-require image-input support.

requires_reasoning bool

Hard-require extended-reasoning support.

family NotBlankStr | None

Resolve to the newest configured model in this family (e.g. "example-large"); pins a concrete id at match time.

model_pattern NotBlankStr | None

Resolve to the newest configured model whose id matches this glob (e.g. "example-*"); pins a concrete id.

Config:

  • frozen: True
  • extra: forbid
  • allow_inf_nan: False

Fields:

Validators:

  • _validate_resolution_axes

model_id pydantic-field

model_id = None

Explicit configured model id/alias to pin

priority pydantic-field

priority = 'balanced'

Optimisation axis for capability scoring

min_context pydantic-field

min_context = 0

Minimum context window in tokens

requires_tools pydantic-field

requires_tools = False

Hard-require function/tool-calling support

requires_vision pydantic-field

requires_vision = False

Hard-require image-input support

requires_reasoning pydantic-field

requires_reasoning = False

Hard-require extended-reasoning support

family pydantic-field

family = None

Resolve to the newest configured model in this family

model_pattern pydantic-field

model_pattern = None

Resolve to the newest configured model id matching this glob

parse_model_requirement

parse_model_requirement(raw)

Parse a model requirement from an explicit id string or a dict.

A bare string is an explicit example-id pin (model_id); a dict maps directly onto the :class:ModelRequirement capability/family fields.

Parameters:

Name Type Description Default
raw str | dict[str, JsonValue]

Either a non-blank model id/alias string, or a dict with ModelRequirement fields.

required

Returns:

Type Description
ModelRequirement

Parsed ModelRequirement.

Raises:

Type Description
ValueError

If raw is a blank string.

ValidationError

If raw is a dict with invalid fields.

Source code in src/synthorg/templates/model_requirements.py
def parse_model_requirement(raw: str | dict[str, JsonValue]) -> ModelRequirement:
    """Parse a model requirement from an explicit id string or a dict.

    A bare string is an explicit example-id pin (``model_id``); a dict maps
    directly onto the :class:`ModelRequirement` capability/family fields.

    Args:
        raw: Either a non-blank model id/alias string, or a dict with
            ``ModelRequirement`` fields.

    Returns:
        Parsed ``ModelRequirement``.

    Raises:
        ValueError: If *raw* is a blank string.
        ValidationError: If *raw* is a dict with invalid fields.
    """
    if isinstance(raw, str):
        pinned = raw.strip()
        if not pinned:
            msg = "Model id reference must be a non-blank string"
            logger.warning(
                TEMPLATE_MODEL_REQUIREMENT_INVALID,
                raw_reference=raw,
                reason="blank_model_id",
            )
            raise ValueError(msg)
        result = ModelRequirement.model_validate({"model_id": pinned})
    else:
        try:
            result = ModelRequirement.model_validate(raw)
        except ValidationError:
            logger.warning(
                TEMPLATE_MODEL_REQUIREMENT_INVALID,
                raw_requirement=raw,
                reason="dict_validation_failed",
            )
            raise

    logger.debug(
        TEMPLATE_MODEL_REQUIREMENT_PARSED,
        model_id=result.model_id,
        priority=result.priority,
        family=result.family,
    )
    return result

resolve_model_requirement

resolve_model_requirement(preset_name=None, overrides=None)

Merge a personality-preset affinity profile with explicit overrides.

The affinity profile supplies capability defaults (priority, context floor, hard requirement flags, optional family hint); any field the template states explicitly via overrides always wins.

Parameters:

Name Type Description Default
preset_name str | None

Optional personality preset name for affinity lookup.

None
overrides dict[str, JsonValue] | None

Explicit ModelRequirement fields from the template agent that take precedence over the affinity defaults.

None

Returns:

Type Description
ModelRequirement

Resolved ModelRequirement.

Source code in src/synthorg/templates/model_requirements.py
def resolve_model_requirement(
    preset_name: str | None = None,
    overrides: dict[str, JsonValue] | None = None,
) -> ModelRequirement:
    """Merge a personality-preset affinity profile with explicit overrides.

    The affinity profile supplies capability defaults (priority, context
    floor, hard requirement flags, optional family hint); any field the
    template states explicitly via *overrides* always wins.

    Args:
        preset_name: Optional personality preset name for affinity lookup.
        overrides: Explicit ``ModelRequirement`` fields from the template
            agent that take precedence over the affinity defaults.

    Returns:
        Resolved ``ModelRequirement``.
    """
    if overrides:
        pin = overrides.get("model_id")
        if isinstance(pin, str) and pin.strip():
            # A pin is selected verbatim and bypasses capability scoring, so
            # affinity flags would be inert against it and are NOT merged in.
            # The full overrides dict is parsed (not just the pin) so any
            # explicit field the template set alongside model_id is preserved,
            # and a contradictory model_id + family/model_pattern pairing is
            # rejected by the ModelRequirement validator.
            pinned = parse_model_requirement(
                {key: value for key, value in overrides.items() if value is not None}
            )
            logger.debug(
                TEMPLATE_MODEL_REQUIREMENT_RESOLVED,
                model_id=pinned.model_id,
                priority=pinned.priority,
                min_context=pinned.min_context,
                family=pinned.family,
                preset=preset_name,
            )
            return pinned

    affinity = MODEL_AFFINITY.get(
        normalize_ascii_lowercase_or_default(preset_name),
        MappingProxyType({}),
    )
    merged: dict[str, JsonValue] = {
        key: affinity[key] for key in _AFFINITY_DEFAULT_KEYS if key in affinity
    }
    if overrides:
        merged.update({k: v for k, v in overrides.items() if v is not None})

    result = parse_model_requirement(merged)
    logger.debug(
        TEMPLATE_MODEL_REQUIREMENT_RESOLVED,
        model_id=result.model_id,
        priority=result.priority,
        min_context=result.min_context,
        family=result.family,
        preset=preset_name,
    )
    return result

Model Matcher

model_matcher

Capability-aware model-matching engine.

Given a :class:~synthorg.templates.model_requirements.ModelRequirement and a set of available provider models, selects the best-fit model by (a) hard-filtering on declared capability requirements against each model's persisted metadata, (b) resolving any family/pattern reference to the newest matching configured model and pinning a concrete id, and (c) scoring the survivors on an absolute capability / context / priority composite. Selection is pluggable via :class:ModelSelectionStrategy.

ModelMatch pydantic-model

Bases: BaseModel

Result of matching a single agent to a provider model.

Attributes:

Name Type Description
agent_index int

Index of the agent in the template agent list.

provider_name NotBlankStr

Name of the matched provider.

model_id NotBlankStr

Matched (pinned) model identifier.

tier ModelTier

Report-only tier derived from the selected model's metadata.

score float

Match quality score (higher is better, 0-1 range).

Config:

  • frozen: True
  • extra: forbid
  • allow_inf_nan: False

Fields:

ModelSelectionStrategy

Bases: Protocol

Selects the best model for a requirement from candidates.

select

select(requirement, candidates, config)

Return the best model and its 0-1 score (or None, 0.0).

Source code in src/synthorg/templates/model_matcher.py
def select(
    self,
    requirement: ModelRequirement,
    candidates: Sequence[ProviderModelConfig],
    config: ModelMatcherConfig,
) -> tuple[ProviderModelConfig | None, float]:
    """Return the best model and its 0-1 score (or ``None``, 0.0)."""
    ...

CapabilityFitStrategy

Default capability-aware selection strategy.

Phase A hard-filters on declared capability requirements (optimistic for un-probed metadata -- a required capability fails only when the model is known to lack it; see :func:passes_hard_filters); phase B pins the newest model matching a family/pattern reference; phase C scores survivors on an absolute capability / context / priority composite.

select

select(requirement, candidates, config)

Select the best model for requirement from candidates.

Resolution order: an explicit model_id pin wins outright (the user chose it, so the capability hard-filters do not apply); then a family / model_pattern reference pins the newest match; then capability scoring over the hard-filter survivors.

Returns:

Type Description
ProviderModelConfig | None

(model, score) for the best survivor, or (None, 0.0)

float

when nothing matches the pin / clears the hard filters.

Source code in src/synthorg/templates/model_matcher.py
def select(
    self,
    requirement: ModelRequirement,
    candidates: Sequence[ProviderModelConfig],
    config: ModelMatcherConfig,
) -> tuple[ProviderModelConfig | None, float]:
    """Select the best model for *requirement* from *candidates*.

    Resolution order: an explicit ``model_id`` pin wins outright (the
    user chose it, so the capability hard-filters do not apply); then a
    ``family`` / ``model_pattern`` reference pins the newest match; then
    capability scoring over the hard-filter survivors.

    Returns:
        ``(model, score)`` for the best survivor, or ``(None, 0.0)``
        when nothing matches the pin / clears the hard filters.
    """
    if requirement.model_id is not None:
        pinned = [m for m in candidates if requirement.model_id in (m.id, m.alias)]
        if not pinned:
            return None, 0.0
        return self._newest(pinned), 1.0

    survivors = [m for m in candidates if passes_hard_filters(m, requirement)]
    if not survivors:
        return None, 0.0

    if requirement.family is not None or requirement.model_pattern is not None:
        matched = [m for m in survivors if self._ref_matches(m, requirement)]
        if matched:
            best = self._newest(matched)
            # Score against the matched subset, not all survivors: the
            # pool-normalised priority bonus would otherwise let unrelated
            # non-matching models shift the pinned model's score.
            return best, self._score(best, requirement, matched, config)
        logger.debug(
            TEMPLATE_MODEL_MATCH_FALLBACK,
            reason="family_pattern_miss",
            family=requirement.family,
            pattern=requirement.model_pattern,
        )

    scored = [
        (m, self._score(m, requirement, survivors, config)) for m in survivors
    ]
    return max(scored, key=lambda pair: pair[1])

passes_hard_filters

passes_hard_filters(model, requirement)

Return True when model clears every hard requirement.

Optimistic: a required capability is a hard fail only when the model is known to lack it (litellm / probe metadata with the flag False). A model with unknown metadata is allowed through -- most modern models support tools/reasoning, and excluding every un-probed cloud model would leave agents unassigned.

Runtime tool-call feedback overrides optimism: a model whose tool_calls_verified is False has proven at runtime that it cannot call tools, so it is excluded for requires_tools agents regardless of metadata source (the runtime signal is authoritative over the discovery-time claim).

Returns:

Type Description
bool

True when the model meets the context floor and every required

bool

capability it is known to possess (or has unknown metadata for).

Source code in src/synthorg/templates/model_matcher.py
def passes_hard_filters(
    model: ProviderModelConfig,
    requirement: ModelRequirement,
) -> bool:
    """Return ``True`` when *model* clears every hard requirement.

    Optimistic: a required capability is a hard fail only when the model is
    *known* to lack it (``litellm`` / ``probe`` metadata with the flag False).
    A model with ``unknown`` metadata is allowed through -- most modern models
    support tools/reasoning, and excluding every un-probed cloud model would
    leave agents unassigned.

    Runtime tool-call feedback overrides optimism: a model whose
    ``tool_calls_verified`` is ``False`` has *proven* at runtime that it cannot
    call tools, so it is excluded for ``requires_tools`` agents regardless of
    metadata source (the runtime signal is authoritative over the
    discovery-time claim).

    Returns:
        True when the model meets the context floor and every required
        capability it is known to possess (or has unknown metadata for).
    """
    if model.max_context < requirement.min_context:
        return False
    meta = model.metadata
    # Embedding models produce vector output, not chat completions, so they
    # must never be assigned to a chat agent regardless of any other
    # capability flags. An explicit ``model_id`` pin still bypasses this
    # filter (the operator escape hatch for a deliberately specialised agent).
    if meta.supports_embeddings:
        logger.debug(
            TEMPLATE_MODEL_MATCH_SKIPPED,
            model=model.id,
            reason="embedding_model_not_chat_capable",
        )
        return False
    if requirement.requires_tools and meta.tool_calls_verified is False:
        logger.debug(
            TEMPLATE_MODEL_MATCH_SKIPPED,
            model=model.id,
            reason="tool_calls_runtime_unverified",
        )
        return False
    # A runtime-proven tool caller is authoritative over stale discovery
    # metadata: ``tool_calls_verified is True`` re-admits a model whose
    # ``supports_tools`` flag is a false negative, so it is never permanently
    # unassignable to ``requires_tools`` agents.
    tools_supported = meta.tool_calls_verified is True or meta.supports_tools
    unknown = meta.metadata_source == "unknown"
    required_checks = (
        (requirement.requires_tools, tools_supported),
        (requirement.requires_vision, meta.supports_vision),
        (requirement.requires_reasoning, meta.supports_reasoning),
    )
    for required, supported in required_checks:
        if required and not unknown and not supported:
            logger.debug(
                TEMPLATE_MODEL_MATCH_SKIPPED,
                model=model.id,
                reason="capability_unmet",
                metadata_unknown=unknown,
            )
            return False
    return True

get_model_selection_strategy

get_model_selection_strategy()

Return the default model-selection strategy singleton.

Returns:

Type Description
ModelSelectionStrategy

The shared :class:CapabilityFitStrategy; swap by passing a

ModelSelectionStrategy

strategy to :func:match_model / :func:match_all_agents.

Source code in src/synthorg/templates/model_matcher.py
def get_model_selection_strategy() -> ModelSelectionStrategy:
    """Return the default model-selection strategy singleton.

    Returns:
        The shared :class:`CapabilityFitStrategy`; swap by passing a
        ``strategy`` to :func:`match_model` / :func:`match_all_agents`.
    """
    return _DEFAULT_STRATEGY

match_model

match_model(requirement, available, matcher_config=None, strategy=None)

Select the best model for a requirement from available models.

Parameters:

Name Type Description Default
requirement ModelRequirement

Structured model requirement.

required
available tuple[ProviderModelConfig, ...]

Tuple of available models from a single provider.

required
matcher_config ModelMatcherConfig | None

Operator-tunable score weights. None falls back to the default projected from EngineBridgeConfig.

None
strategy ModelSelectionStrategy | None

Selection strategy. None uses the default :class:CapabilityFitStrategy.

None

Returns:

Type Description
tuple[ProviderModelConfig | None, float]

Tuple of (best matching model or None, score 0-1).

Source code in src/synthorg/templates/model_matcher.py
def match_model(
    requirement: ModelRequirement,
    available: tuple[ProviderModelConfig, ...],
    matcher_config: ModelMatcherConfig | None = None,
    strategy: ModelSelectionStrategy | None = None,
) -> tuple[ProviderModelConfig | None, float]:
    """Select the best model for a requirement from available models.

    Args:
        requirement: Structured model requirement.
        available: Tuple of available models from a single provider.
        matcher_config: Operator-tunable score weights. ``None`` falls
            back to the default projected from ``EngineBridgeConfig``.
        strategy: Selection strategy. ``None`` uses the default
            :class:`CapabilityFitStrategy`.

    Returns:
        Tuple of (best matching model or None, score 0-1).
    """
    if not available:
        return None, 0.0
    cfg = matcher_config if matcher_config is not None else DEFAULT_MATCHER_CONFIG
    selector = strategy if strategy is not None else _DEFAULT_STRATEGY
    return selector.select(requirement, available, cfg)

match_all_agents

match_all_agents(
    agents, providers, matcher_config=None, strategy=None, *, tier_profile="balanced"
)

Batch-match template agents to provider models.

For each agent, resolves its model requirement and finds the best model across all configured providers. The reported tier is derived from the selected model's metadata.

Parameters:

Name Type Description Default
agents Sequence[Mapping[str, object]]

List of expanded agent config dicts. Requirement resolution checks model_requirement (object then dict), then falls back to personality_preset affinity defaults.

required
providers Mapping[str, _ProviderWithModels]

Provider name -> provider config mapping; each must expose a models tuple of ProviderModelConfig.

required
matcher_config ModelMatcherConfig | None

Operator-tunable score weights. None uses the default projected from EngineBridgeConfig.

None
strategy ModelSelectionStrategy | None

Selection strategy. None uses the default.

None
tier_profile str

Company model-tier profile ('economy' | 'balanced' | 'premium') that nudges each agent's resolved priority one rung along the cost<->quality ladder before matching; 'balanced' is a no-op, so an unset profile leaves matching unchanged.

'balanced'

Returns:

Type Description
list[ModelMatch]

List of ModelMatch results. An agent is omitted when no model

list[ModelMatch]

clears its hard capability requirements (a model the agent's required

list[ModelMatch]

capability is known to lack), when no models exist anywhere, or when

list[ModelMatch]

requirement resolution fails. Callers handle an omitted agent (left

list[ModelMatch]

unassigned at setup).

Source code in src/synthorg/templates/model_matcher.py
def match_all_agents(
    agents: Sequence[Mapping[str, object]],
    providers: Mapping[str, _ProviderWithModels],
    matcher_config: ModelMatcherConfig | None = None,
    strategy: ModelSelectionStrategy | None = None,
    *,
    tier_profile: str = "balanced",
) -> list[ModelMatch]:
    """Batch-match template agents to provider models.

    For each agent, resolves its model requirement and finds the best
    model across all configured providers.  The reported ``tier`` is
    derived from the *selected* model's metadata.

    Args:
        agents: List of expanded agent config dicts. Requirement
            resolution checks ``model_requirement`` (object then dict),
            then falls back to ``personality_preset`` affinity defaults.
        providers: Provider name -> provider config mapping; each must
            expose a ``models`` tuple of ``ProviderModelConfig``.
        matcher_config: Operator-tunable score weights. ``None`` uses the
            default projected from ``EngineBridgeConfig``.
        strategy: Selection strategy. ``None`` uses the default.
        tier_profile: Company model-tier profile ('economy' | 'balanced' |
            'premium') that nudges each agent's resolved priority one rung
            along the cost<->quality ladder before matching; 'balanced' is a
            no-op, so an unset profile leaves matching unchanged.

    Returns:
        List of ``ModelMatch`` results. An agent is omitted when no model
        clears its hard capability requirements (a model the agent's required
        capability is *known* to lack), when no models exist anywhere, or when
        requirement resolution fails. Callers handle an omitted agent (left
        unassigned at setup).
    """
    from synthorg.templates.model_requirements import (  # noqa: PLC0415
        ModelRequirement,
        parse_model_requirement,
        resolve_model_requirement,
    )

    cfg = matcher_config if matcher_config is not None else DEFAULT_MATCHER_CONFIG
    selector = strategy if strategy is not None else _DEFAULT_STRATEGY
    pool, owner = _build_pool(providers)
    # Domination pruning: drop the older sibling when a same-family model in
    # the same cost tier is strictly stronger (same price, worse). Tier
    # overrides apply so a promoted model is compared in its promoted tier.
    pruned = tuple(prune_dominated(pool, cfg.tier_overrides))
    ctx = _MatchContext(pruned, owner, Counter(), cfg, selector)

    resolved: list[tuple[int, ModelRequirement]] = []
    for idx, agent in enumerate(agents):
        req = _resolve_agent_requirement(
            agent,
            idx,
            ModelRequirement,
            parse_model_requirement,
            resolve_model_requirement,
        )
        if req is None:
            continue
        # The company model-tier profile biases the whole roster cheaper
        # ('economy') or stronger ('premium') by nudging each agent's resolved
        # priority one rung along the cost<->quality ladder; 'balanced' is a
        # no-op, so a profile-less call is unchanged.
        shifted = shift_priority(req.priority, tier_profile)
        if shifted != req.priority:
            req = req.model_copy(update={"priority": shifted})
        resolved.append((idx, req))
    # Assign the most-demanding roles first so the strongest models go to the
    # work that needs them, never wasted on a low-demand role.
    resolved.sort(key=lambda pair: demand_tier(pair[1]), reverse=True)

    results = [
        match
        for idx, req in resolved
        if (match := _match_agent(idx, req, ctx)) is not None
    ]
    results.sort(key=lambda match: match.agent_index)
    return results

Errors

errors

Custom exception hierarchy for template errors.

TemplateError

TemplateError(message, locations=())

Bases: ConfigError

Base exception for template errors.

Source code in src/synthorg/config/errors.py
def __init__(
    self,
    message: str,
    locations: tuple[ConfigLocation, ...] = (),
) -> None:
    self.message = message
    self.locations = locations
    super().__init__(message)

TemplateNotFoundError

TemplateNotFoundError(message, locations=())

Bases: TemplateError

Raised when a template cannot be found.

Source code in src/synthorg/config/errors.py
def __init__(
    self,
    message: str,
    locations: tuple[ConfigLocation, ...] = (),
) -> None:
    self.message = message
    self.locations = locations
    super().__init__(message)

TemplateRenderError

TemplateRenderError(message, locations=())

Bases: TemplateError

Raised when template rendering fails.

Covers Jinja2 evaluation errors, missing required variables, YAML parse errors during template processing, and invalid numeric values in rendered output.

Source code in src/synthorg/config/errors.py
def __init__(
    self,
    message: str,
    locations: tuple[ConfigLocation, ...] = (),
) -> None:
    self.message = message
    self.locations = locations
    super().__init__(message)

TemplateInheritanceError

TemplateInheritanceError(message, locations=())

Bases: TemplateRenderError

Raised when template inheritance fails.

Covers circular inheritance chains, excessive depth, and merge conflicts.

Source code in src/synthorg/config/errors.py
def __init__(
    self,
    message: str,
    locations: tuple[ConfigLocation, ...] = (),
) -> None:
    self.message = message
    self.locations = locations
    super().__init__(message)

TemplatePostureError

TemplatePostureError(message, locations=())

Bases: TemplateError

Raised when posture resolution fails.

Covers a posture name with no registered feature bundle, an unknown expansion-strategy discriminator, and an inheritance/pack graph that exceeds the maximum resolution depth (a likely cycle).

Source code in src/synthorg/config/errors.py
def __init__(
    self,
    message: str,
    locations: tuple[ConfigLocation, ...] = (),
) -> None:
    self.message = message
    self.locations = locations
    super().__init__(message)

TemplateValidationError

TemplateValidationError(message, locations=(), field_errors=())

Bases: TemplateError

Raised when a rendered template fails validation.

Attributes:

Name Type Description
field_errors

Per-field error messages as (key_path, message) pairs.

Source code in src/synthorg/templates/errors.py
def __init__(
    self,
    message: str,
    locations: tuple[ConfigLocation, ...] = (),
    field_errors: tuple[tuple[str, str], ...] = (),
) -> None:
    super().__init__(message, locations)
    self.field_errors = field_errors

__str__

__str__()

Format validation error with per-field details.

Returns:

Type Description
str

The base message when there are no field errors, otherwise a

str

header line plus one indented line per field error.

Source code in src/synthorg/templates/errors.py
@override
def __str__(self) -> str:
    """Format validation error with per-field details.

    Returns:
        The base message when there are no field errors, otherwise a
        header line plus one indented line per field error.
    """
    if not self.field_errors:
        return super().__str__()
    parts = [f"{self.message} ({len(self.field_errors)} errors):"]
    loc_by_key: dict[str, ConfigLocation] = {
        loc.key_path: loc for loc in self.locations if loc.key_path
    }
    for key_path, msg in self.field_errors:
        parts.append(f"  {key_path}: {msg}")
        loc = loc_by_key.get(key_path)
        if loc and loc.file_path:
            if loc.line is not None and loc.column is not None:
                line_info = f" at line {loc.line}, column {loc.column}"
            elif loc.line is not None:
                line_info = f" at line {loc.line}"
            else:
                line_info = ""
            parts.append(f"    in {loc.file_path}{line_info}")
    return "\n".join(parts)