Skip to content

Config

YAML company configuration loading and validation.

Schema

schema

Root configuration schema and config-level Pydantic models.

LocalModelParams pydantic-model

Bases: BaseModel

Per-model launch parameters for local providers.

Attributes:

Name Type Description
num_ctx int | None

Context window size override in tokens.

num_gpu_layers int | None

Number of layers to offload to GPU (0 = CPU only).

num_threads int | None

CPU thread count for inference.

num_batch int | None

Batch size for prompt processing.

repeat_penalty float | None

Repetition penalty multiplier (1.0 = disabled).

Config:

  • frozen: True
  • allow_inf_nan: False

Fields:

  • num_ctx (int | None)
  • num_gpu_layers (int | None)
  • num_threads (int | None)
  • num_batch (int | None)
  • repeat_penalty (float | None)

repeat_penalty pydantic-field

repeat_penalty = None

Repetition penalty

ProviderModelConfig pydantic-model

Bases: BaseModel

Configuration for a single LLM model within a provider.

Attributes:

Name Type Description
id NotBlankStr

Model identifier (e.g. "example-medium-001").

alias NotBlankStr | None

Short alias for referencing this model in routing rules.

cost_per_1k_input float

Cost per 1,000 input tokens (base currency).

cost_per_1k_output float

Cost per 1,000 output tokens (base currency).

max_context int

Maximum context window size in tokens.

estimated_latency_ms int | None

Estimated median latency in milliseconds.

local_params LocalModelParams | None

Per-model launch parameters for local providers.

Config:

  • frozen: True
  • allow_inf_nan: False

Fields:

id pydantic-field

id

Model identifier

alias pydantic-field

alias = None

Short alias for routing rules

cost_per_1k_input pydantic-field

cost_per_1k_input = 0.0

Cost per 1k input tokens in USD (base currency)

cost_per_1k_output pydantic-field

cost_per_1k_output = 0.0

Cost per 1k output tokens in USD (base currency)

max_context pydantic-field

max_context = 200000

Maximum context window size in tokens

estimated_latency_ms pydantic-field

estimated_latency_ms = None

Estimated median latency in milliseconds

local_params pydantic-field

local_params = None

Per-model launch parameters for local providers

ProviderConfig pydantic-model

Bases: BaseModel

Configuration for an LLM provider.

Attributes:

Name Type Description
driver NotBlankStr

Driver backend name (e.g. "litellm").

litellm_provider NotBlankStr | None

LiteLLM routing identifier (e.g. "example-provider"). When set, the driver uses this instead of the provider name as the model prefix for LiteLLM routing. Falls back to the provider name when None.

auth_type AuthType

Authentication type for this provider.

api_key NotBlankStr | None

API key (typically injected by secret management).

subscription_token NotBlankStr | None

Bearer token for subscription-based auth (e.g. provider subscription plans). Encrypted at rest.

tos_accepted_at AwareDatetime | None

Timestamp when the user accepted the subscription Terms of Service warning. Required when auth_type is SUBSCRIPTION.

base_url NotBlankStr | None

Base URL for the provider API.

oauth_token_url NotBlankStr | None

OAuth token endpoint URL.

oauth_client_id NotBlankStr | None

OAuth client identifier.

oauth_client_secret NotBlankStr | None

OAuth client secret.

oauth_scope NotBlankStr | None

OAuth scope string.

custom_header_name NotBlankStr | None

Name of custom auth header.

custom_header_value NotBlankStr | None

Value of custom auth header.

models tuple[ProviderModelConfig, ...]

Available models for this provider.

retry RetryConfig

Retry configuration for transient errors.

rate_limiter RateLimiterConfig

Client-side rate limiting configuration.

subscription SubscriptionConfig

Subscription and quota configuration.

degradation DegradationConfig

Degradation strategy when quota exhausted.

family NotBlankStr | None

Provider family for cross-validation grouping.

preset_name NotBlankStr | None

Name of the preset used to create this provider (if any). Used to resolve local model management capabilities.

Config:

  • frozen: True
  • allow_inf_nan: False

Fields:

Validators:

  • _validate_auth_fields
  • _validate_unique_model_identifiers

driver pydantic-field

driver = 'litellm'

Driver backend name

litellm_provider pydantic-field

litellm_provider = None

LiteLLM provider identifier for routing (e.g. 'example-provider'). Falls back to the provider name when None.

family pydantic-field

family = None

Provider family for cross-validation grouping (e.g. 'provider-family-a', 'provider-family-b'). When None, the provider name is used as the family.

auth_type pydantic-field

auth_type = API_KEY

Authentication type

api_key pydantic-field

api_key = None

API key

subscription_token pydantic-field

subscription_token = None

OAuth bearer token for subscription-based auth

tos_accepted_at pydantic-field

tos_accepted_at = None

When subscription ToS was accepted

base_url pydantic-field

base_url = None

Base URL for the provider API

oauth_token_url pydantic-field

oauth_token_url = None

OAuth token endpoint URL

oauth_client_id pydantic-field

oauth_client_id = None

OAuth client identifier

oauth_client_secret pydantic-field

oauth_client_secret = None

OAuth client secret

oauth_scope pydantic-field

oauth_scope = None

OAuth scope string

custom_header_name pydantic-field

custom_header_name = None

Name of custom auth header

custom_header_value pydantic-field

custom_header_value = None

Value of custom auth header

models pydantic-field

models = ()

Available models

retry pydantic-field

retry

Retry configuration for transient errors

rate_limiter pydantic-field

rate_limiter

Client-side rate limiting configuration

subscription pydantic-field

subscription

Subscription and quota configuration

degradation pydantic-field

degradation

Degradation strategy when quota exhausted

preset_name pydantic-field

preset_name = None

Preset used to create this provider (if any)

RoutingRuleConfig pydantic-model

Bases: BaseModel

A single model routing rule.

At least one of role_level or task_type must be set so the rule can match incoming requests.

Attributes:

Name Type Description
role_level SeniorityLevel | None

Seniority level this rule applies to.

task_type NotBlankStr | None

Task type this rule applies to.

preferred_model NotBlankStr

Preferred model alias or ID.

fallback NotBlankStr | None

Fallback model alias or ID.

Config:

  • frozen: True
  • allow_inf_nan: False

Fields:

Validators:

  • _at_least_one_matcher

role_level pydantic-field

role_level = None

Seniority level filter

task_type pydantic-field

task_type = None

Task type filter

preferred_model pydantic-field

preferred_model

Preferred model alias or ID

fallback pydantic-field

fallback = None

Fallback model alias or ID

RoutingConfig pydantic-model

Bases: BaseModel

Model routing configuration.

Attributes:

Name Type Description
strategy NotBlankStr

Routing strategy name (e.g. "cost_aware").

rules tuple[RoutingRuleConfig, ...]

Ordered routing rules.

fallback_chain tuple[NotBlankStr, ...]

Ordered fallback model aliases or IDs.

Config:

  • frozen: True
  • allow_inf_nan: False

Fields:

strategy pydantic-field

strategy = 'cost_aware'

Routing strategy name

rules pydantic-field

rules = ()

Ordered routing rules

fallback_chain pydantic-field

fallback_chain = ()

Ordered fallback model aliases or IDs

AgentConfig pydantic-model

Bases: BaseModel

Agent configuration from YAML.

Uses raw dicts for personality, model, memory, tools, and authority because :class:~synthorg.core.agent.AgentIdentity has runtime fields (id, hiring_date, status) that are not present in config. The engine constructs full AgentIdentity objects at startup.

Attributes:

Name Type Description
name NotBlankStr

Agent display name.

role NotBlankStr

Role name.

department NotBlankStr

Department name.

level SeniorityLevel

Seniority level.

personality dict[str, Any]

Raw personality config dict.

model dict[str, Any]

Raw model config dict.

memory dict[str, Any]

Raw memory config dict.

tools dict[str, Any]

Raw tools config dict.

authority dict[str, Any]

Raw authority config dict.

Config:

  • frozen: True
  • allow_inf_nan: False

Fields:

name pydantic-field

name

Agent display name

role pydantic-field

role

Role name

department pydantic-field

department

Department name

level pydantic-field

level = MID

Seniority level

personality pydantic-field

personality

Raw personality config

model pydantic-field

model

Raw model config

memory pydantic-field

memory

Raw memory config

tools pydantic-field

tools

Raw tools config

authority pydantic-field

authority

Raw authority config

autonomy_level pydantic-field

autonomy_level = None

Per-agent autonomy level override (D6)

GracefulShutdownConfig pydantic-model

Bases: BaseModel

Configuration for graceful shutdown behaviour.

Attributes:

Name Type Description
strategy NotBlankStr

Shutdown strategy name (e.g. "cooperative_timeout").

grace_seconds float

Seconds to wait for cooperative agent exit before force-cancelling.

cleanup_seconds float

Seconds allowed for cleanup callbacks (persist costs, close connections, flush logs).

Config:

  • frozen: True
  • allow_inf_nan: False

Fields:

strategy pydantic-field

strategy = 'cooperative_timeout'

Shutdown strategy name

grace_seconds pydantic-field

grace_seconds = 30.0

Seconds to wait for cooperative agent exit

cleanup_seconds pydantic-field

cleanup_seconds = 5.0

Seconds allowed for cleanup callbacks

TaskAssignmentConfig pydantic-model

Bases: BaseModel

Configuration for task assignment behaviour.

Attributes:

Name Type Description
strategy NotBlankStr

Assignment strategy name (e.g. "role_based").

min_score float

Minimum capability score for agent eligibility.

max_concurrent_tasks_per_agent int

Maximum tasks an agent can handle concurrently. Enforced by scoring-based strategies that filter out agents at capacity.

Config:

  • frozen: True
  • allow_inf_nan: False

Fields:

Validators:

  • _validate_strategy_name

strategy pydantic-field

strategy = 'role_based'

Assignment strategy name

min_score pydantic-field

min_score = 0.1

Minimum capability score for agent eligibility

max_concurrent_tasks_per_agent pydantic-field

max_concurrent_tasks_per_agent = 5

Maximum concurrent tasks an agent is intended to handle. Enforced by scoring-based strategies that filter out agents at capacity.

RootConfig pydantic-model

Bases: BaseModel

Root company configuration -- the top-level validation target.

Aggregates all sub-configurations into a single frozen model that represents a fully validated company setup.

Attributes:

Name Type Description
company_name NotBlankStr

Company name (required).

company_type CompanyType

Company template type.

departments tuple[Department, ...]

Organizational departments.

agents tuple[AgentConfig, ...]

Agent configurations.

custom_roles tuple[CustomRole, ...]

User-defined custom roles.

config CompanyConfig

Company-wide settings.

budget BudgetConfig

Budget configuration.

communication CommunicationConfig

Communication configuration.

providers dict[str, ProviderConfig]

LLM provider configurations keyed by provider name.

routing RoutingConfig

Model routing configuration.

logging LogConfig | None

Logging configuration (None to use platform defaults).

graceful_shutdown GracefulShutdownConfig

Graceful shutdown configuration.

workflow_handoffs tuple[WorkflowHandoff, ...]

Cross-department workflow handoffs.

escalation_paths tuple[EscalationPath, ...]

Cross-department escalation paths.

coordination_metrics CoordinationMetricsConfig

Coordination metrics configuration.

task_assignment TaskAssignmentConfig

Task assignment configuration.

memory CompanyMemoryConfig

Memory backend configuration.

persistence PersistenceConfig

Persistence backend configuration.

cost_tiers CostTiersConfig

Cost tier definitions.

org_memory OrgMemoryConfig

Organizational memory configuration.

api ApiConfig

API server configuration.

sandboxing SandboxingConfig

Sandboxing backend configuration.

mcp MCPConfig

MCP bridge configuration.

security SecurityConfig

Security subsystem configuration.

trust TrustConfig

Progressive trust configuration.

promotion PromotionConfig

Promotion/demotion configuration.

task_engine TaskEngineConfig

Task engine configuration.

coordination CoordinationSectionConfig

Multi-agent coordination configuration.

git_clone GitCloneNetworkPolicy

Git clone SSRF prevention network policy.

backup BackupConfig

Backup and restore configuration.

workflow WorkflowConfig

Workflow type configuration.

Config:

  • frozen: True
  • allow_inf_nan: False

Fields:

Validators:

  • _validate_unique_agent_names
  • _validate_unique_department_names
  • _validate_routing_references
  • _validate_degradation_fallback_providers

company_name pydantic-field

company_name

Company name

company_type pydantic-field

company_type = CUSTOM

Company template type

departments pydantic-field

departments = ()

Organizational departments

agents pydantic-field

agents = ()

Agent configurations

custom_roles pydantic-field

custom_roles = ()

User-defined custom roles

config pydantic-field

config

Company-wide settings

budget pydantic-field

budget

Budget configuration

communication pydantic-field

communication

Communication configuration

providers pydantic-field

providers

LLM provider configurations

routing pydantic-field

routing

Model routing configuration

logging pydantic-field

logging = None

Logging configuration

graceful_shutdown pydantic-field

graceful_shutdown

Graceful shutdown configuration

workflow_handoffs pydantic-field

workflow_handoffs = ()

Cross-department workflow handoffs

escalation_paths pydantic-field

escalation_paths = ()

Cross-department escalation paths

coordination_metrics pydantic-field

coordination_metrics

Coordination metrics configuration

task_assignment pydantic-field

task_assignment

Task assignment configuration

memory pydantic-field

memory

Memory backend configuration

persistence pydantic-field

persistence

Persistence backend configuration

cost_tiers pydantic-field

cost_tiers

Cost tier definitions

org_memory pydantic-field

org_memory

Organizational memory configuration

api pydantic-field

api

API server configuration

sandboxing pydantic-field

sandboxing

Sandboxing backend configuration

mcp pydantic-field

mcp

MCP bridge configuration

security pydantic-field

security

Security subsystem configuration

trust pydantic-field

trust

Progressive trust configuration

promotion pydantic-field

promotion

Promotion/demotion configuration

task_engine pydantic-field

task_engine

Task engine configuration

coordination pydantic-field

coordination

Multi-agent coordination configuration

git_clone pydantic-field

git_clone

Git clone SSRF prevention network policy

backup pydantic-field

backup

Backup and restore configuration

workflow pydantic-field

workflow

Workflow type configuration

Loader

loader

YAML configuration loader with layered merging and validation.

discover_config

discover_config()

Auto-discover a configuration file from well-known locations.

Search order:

  1. ./synthorg.yaml
  2. ./config/synthorg.yaml
  3. ~/.synthorg/config.yaml

Returns:

Type Description
Path

Resolved absolute :class:~pathlib.Path to the first file found.

Raises:

Type Description
ConfigFileNotFoundError

If no configuration file is found at any searched location.

Source code in src/synthorg/config/loader.py
def discover_config() -> Path:
    """Auto-discover a configuration file from well-known locations.

    Search order:

    1. ``./synthorg.yaml``
    2. ``./config/synthorg.yaml``
    3. ``~/.synthorg/config.yaml``

    Returns:
        Resolved absolute :class:`~pathlib.Path` to the first file found.

    Raises:
        ConfigFileNotFoundError: If no configuration file is found
            at any searched location.
    """
    candidates = [*_CWD_CONFIG_LOCATIONS, Path.home() / _HOME_CONFIG_RELATIVE]
    logger.debug(
        CONFIG_DISCOVERY_STARTED,
        searched_paths=[str(c) for c in candidates],
    )
    for candidate in candidates:
        if candidate.is_file():
            resolved = candidate.resolve()
            logger.info(CONFIG_DISCOVERY_FOUND, config_path=str(resolved))
            return resolved

    searched = [str(c) for c in candidates]
    msg = "No configuration file found. Searched:\n" + "\n".join(
        f"  - {p}" for p in searched
    )
    raise ConfigFileNotFoundError(
        msg,
        locations=tuple(ConfigLocation(file_path=p) for p in searched),
    )

load_config

load_config(config_path=None, *, override_paths=())

Load and validate company configuration from YAML file(s).

Each layer deep-merges onto the previous: built-in defaults, primary config, overrides, then env-var substitution.

Parameters:

Name Type Description Default
config_path Path | str | None

Path to the primary config file, or None to auto-discover.

None
override_paths tuple[Path | str, ...]

Additional config files layered on top.

()

Returns:

Type Description
RootConfig

Validated, frozen :class:RootConfig.

Raises:

Type Description
ConfigFileNotFoundError

If any config file does not exist or discovery finds nothing.

ConfigParseError

If any file contains invalid YAML.

ConfigValidationError

If the merged config fails validation.

Source code in src/synthorg/config/loader.py
def load_config(
    config_path: Path | str | None = None,
    *,
    override_paths: tuple[Path | str, ...] = (),
) -> RootConfig:
    """Load and validate company configuration from YAML file(s).

    Each layer deep-merges onto the previous: built-in defaults,
    primary config, overrides, then env-var substitution.

    Args:
        config_path: Path to the primary config file, or ``None``
            to auto-discover.
        override_paths: Additional config files layered on top.

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

    Raises:
        ConfigFileNotFoundError: If any config file does not exist
            or discovery finds nothing.
        ConfigParseError: If any file contains invalid YAML.
        ConfigValidationError: If the merged config fails validation.
    """
    if config_path is None:
        config_path = discover_config()
    config_path = Path(config_path)

    # Start with defaults, merge primary config
    merged = default_config_dict()
    yaml_text = _read_config_text(config_path)
    primary = _parse_yaml_string(yaml_text, str(config_path))
    merged = deep_merge(merged, primary)

    # Apply overrides and env-var substitution
    merged = _load_and_merge_overrides(merged, override_paths)
    merged = _substitute_env_vars(merged, source_file="<merged config>")

    return _finalize_config(merged, yaml_text, config_path, override_paths)

bootstrap_logging

bootstrap_logging(config=None)

Activate the observability pipeline after config is loaded.

Calls :func:~synthorg.observability.configure_logging with config.logging, or sensible defaults if config is None. Should be called once at startup after :func:load_config returns.

Parameters:

Name Type Description Default
config RootConfig | None

Validated root configuration. When None, the logging system uses default settings.

None
Source code in src/synthorg/config/loader.py
def bootstrap_logging(config: RootConfig | None = None) -> None:
    """Activate the observability pipeline after config is loaded.

    Calls :func:`~synthorg.observability.configure_logging` with
    ``config.logging``, or sensible defaults if *config* is ``None``.
    Should be called **once** at startup after :func:`load_config`
    returns.

    Args:
        config: Validated root configuration.  When ``None``, the
            logging system uses default settings.
    """
    from synthorg.observability import configure_logging  # noqa: PLC0415

    log_cfg = config.logging if config is not None else None
    configure_logging(log_cfg)

load_config_from_string

load_config_from_string(yaml_string, *, source_name='<string>')

Load and validate config from a YAML string.

Merges with built-in defaults before validation. Useful for API endpoints and testing.

Parameters:

Name Type Description Default
yaml_string str

Raw YAML content.

required
source_name str

Label used in error messages.

'<string>'

Returns:

Type Description
RootConfig

Validated, frozen :class:RootConfig.

Raises:

Type Description
ConfigParseError

If the YAML is invalid.

ConfigValidationError

If the merged config fails validation.

Source code in src/synthorg/config/loader.py
def load_config_from_string(
    yaml_string: str,
    *,
    source_name: str = "<string>",
) -> RootConfig:
    """Load and validate config from a YAML string.

    Merges with built-in defaults before validation.  Useful for API
    endpoints and testing.

    Args:
        yaml_string: Raw YAML content.
        source_name: Label used in error messages.

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

    Raises:
        ConfigParseError: If the YAML is invalid.
        ConfigValidationError: If the merged config fails validation.
    """
    data = _parse_yaml_string(yaml_string, source_name)
    merged = deep_merge(default_config_dict(), data)
    merged = _substitute_env_vars(merged, source_file=source_name)
    line_map = _build_line_map(yaml_string)
    return _validate_config_dict(
        merged,
        source_file=source_name,
        line_map=line_map,
    )

Defaults

defaults

Built-in default values for company configuration.

default_config_dict

default_config_dict()

Return base-layer configuration defaults as a raw dict.

These defaults serve as the base layer; user-provided YAML values override them during merging. They ensure that every field has a sensible starting value even if omitted from the config file.

Returns:

Type Description
dict[str, object]

Base-layer configuration dictionary.

Source code in src/synthorg/config/defaults.py
def default_config_dict() -> dict[str, object]:
    """Return base-layer configuration defaults as a raw dict.

    These defaults serve as the base layer; user-provided YAML values
    override them during merging.  They ensure that every field has a
    sensible starting value even if omitted from the config file.

    Returns:
        Base-layer configuration dictionary.
    """
    return {
        "company_name": "SynthOrg",
        "company_type": "custom",
        "departments": [],
        "agents": [],
        "custom_roles": [],
        "config": {},
        "budget": {},
        "communication": {},
        "providers": {},
        "routing": {},
        "logging": None,
        "graceful_shutdown": {},
        "workflow_handoffs": [],
        "escalation_paths": [],
        "coordination_metrics": {},
        "task_assignment": {},
        "memory": {},
        "persistence": {},
        "cost_tiers": {},
        "org_memory": {},
        "api": {},
        "sandboxing": {},
        "mcp": {},
        "security": {},
        "trust": {},
        "promotion": {},
        "task_engine": {},
        "coordination": {},
        "git_clone": {},
        "backup": {},
        "workflow": {},
    }

Errors

errors

Custom exception hierarchy for configuration errors.

ConfigLocation dataclass

ConfigLocation(file_path=None, key_path=None, line=None, column=None)

Source location for a configuration error.

Attributes:

Name Type Description
file_path str | None

Path to the configuration file.

key_path str | None

Dot-separated path to the key (e.g. "budget.alerts.warn_at").

line int | None

Line number in the file (1-based).

column int | None

Column number in the file (1-based).

ConfigError

ConfigError(message, locations=())

Bases: Exception

Base exception for configuration errors.

Attributes:

Name Type Description
message

Human-readable error description.

locations

Source locations associated with this error.

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)

__str__

__str__()

Format error message with source locations.

Source code in src/synthorg/config/errors.py
def __str__(self) -> str:
    """Format error message with source locations."""
    if not self.locations:
        return self.message
    parts = [self.message]
    for loc in self.locations:
        loc_parts: list[str] = []
        if loc.key_path:
            loc_parts.append(f"  {loc.key_path}")
        if 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 = ""
            loc_parts.append(f"    in {loc.file_path}{line_info}")
        parts.extend(loc_parts)
    return "\n".join(parts)

ConfigFileNotFoundError

ConfigFileNotFoundError(message, locations=())

Bases: ConfigError

Raised when a configuration file does not exist.

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)

ConfigParseError

ConfigParseError(message, locations=())

Bases: ConfigError

Raised when YAML parsing fails.

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)

ConfigValidationError

ConfigValidationError(message, locations=(), field_errors=())

Bases: ConfigError

Raised when Pydantic validation fails.

Attributes:

Name Type Description
field_errors

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

Source code in src/synthorg/config/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.

Source code in src/synthorg/config/errors.py
def __str__(self) -> str:
    """Format validation error with per-field details."""
    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)