Skip to content

Runtime Policies and Errors

Configuration

config

Typed settings and deterministic configuration resolution.

CachePolicy

Bases: str, Enum

Allowed cache read and write behavior.

OFF class-attribute instance-attribute

OFF = 'off'

READ_ONLY class-attribute instance-attribute

READ_ONLY = 'read_only'

WRITE_ONLY class-attribute instance-attribute

WRITE_ONLY = 'write_only'

READ_WRITE class-attribute instance-attribute

READ_WRITE = 'read_write'

Settings dataclass

Settings(home: Path, db_path: Path, cache_dir: Path, artifact_dir: Path, max_trials: int = 25, concurrency: int = 4, sample_concurrency: int = 8, seed: int = 0, max_retries: int = 2, sample_timeout_seconds: float = 60.0, trial_timeout_seconds: float = 300.0, cache_policy: CachePolicy = READ_WRITE)

Resolved core settings with no provider credentials.

home instance-attribute

home: Path

db_path instance-attribute

db_path: Path

cache_dir instance-attribute

cache_dir: Path

artifact_dir instance-attribute

artifact_dir: Path

max_trials class-attribute instance-attribute

max_trials: int = 25

concurrency class-attribute instance-attribute

concurrency: int = 4

sample_concurrency class-attribute instance-attribute

sample_concurrency: int = 8

seed class-attribute instance-attribute

seed: int = 0

max_retries class-attribute instance-attribute

max_retries: int = 2

sample_timeout_seconds class-attribute instance-attribute

sample_timeout_seconds: float = 60.0

trial_timeout_seconds class-attribute instance-attribute

trial_timeout_seconds: float = 300.0

cache_policy class-attribute instance-attribute

resolve classmethod

resolve(*, overrides: Mapping[str, Any] | None = None, config_path: str | Path | None = None, environ: Mapping[str, str] | None = None) -> Settings

Resolve defaults, selected TOML, known environment variables, and overrides.

Source code in src/tunarag/config.py
@classmethod
def resolve(
    cls,
    *,
    overrides: Mapping[str, Any] | None = None,
    config_path: str | Path | None = None,
    environ: Mapping[str, str] | None = None,
) -> "Settings":
    """Resolve defaults, selected TOML, known environment variables, and overrides."""

    values: dict[str, Any] = {
        "max_trials": 25,
        "concurrency": 4,
        "sample_concurrency": 8,
        "seed": 0,
        "max_retries": 2,
        "sample_timeout_seconds": 60.0,
        "trial_timeout_seconds": 300.0,
        "cache_policy": CachePolicy.READ_WRITE,
    }
    if config_path is not None:
        values.update(_read_toml(Path(config_path)))
    values.update(_read_environment(os.environ if environ is None else environ))
    if overrides is not None:
        _reject_unknown(overrides, source="overrides")
        values.update(overrides)

    home = _as_path("home", values.get("home", _default_home()))
    values["home"] = home
    values.setdefault("db_path", home / "experiments.db")
    values.setdefault("cache_dir", home / "cache")
    values.setdefault("artifact_dir", home / "artifacts")

    for field in _PATH_FIELDS:
        values[field] = _as_path(field, values[field])
    for field in _INTEGER_FIELDS:
        values[field] = _as_integer(field, values[field])
    for field in _FLOAT_FIELDS:
        values[field] = _as_float(field, values[field])
    values["cache_policy"] = _as_cache_policy(values["cache_policy"])
    return cls(**values)

as_dict

as_dict() -> dict[str, str | int | float]

Return a serializable settings view containing no secrets.

Source code in src/tunarag/config.py
def as_dict(self) -> dict[str, str | int | float]:
    """Return a serializable settings view containing no secrets."""

    return {
        "home": str(self.home),
        "db_path": str(self.db_path),
        "cache_dir": str(self.cache_dir),
        "artifact_dir": str(self.artifact_dir),
        "max_trials": self.max_trials,
        "concurrency": self.concurrency,
        "sample_concurrency": self.sample_concurrency,
        "seed": self.seed,
        "max_retries": self.max_retries,
        "sample_timeout_seconds": self.sample_timeout_seconds,
        "trial_timeout_seconds": self.trial_timeout_seconds,
        "cache_policy": self.cache_policy.value,
    }

Retry policy

retry

Deterministic retry and timeout policy.

RetryPolicy dataclass

RetryPolicy(max_retries: int = 2, sample_timeout_seconds: float | None = 60.0, trial_timeout_seconds: float | None = 300.0, base_delay_seconds: float = 0.5, max_delay_seconds: float = 30.0)

Bound retries, exponential backoff, and execution timeouts.

max_retries class-attribute instance-attribute

max_retries: int = 2

sample_timeout_seconds class-attribute instance-attribute

sample_timeout_seconds: float | None = 60.0

trial_timeout_seconds class-attribute instance-attribute

trial_timeout_seconds: float | None = 300.0

base_delay_seconds class-attribute instance-attribute

base_delay_seconds: float = 0.5

max_delay_seconds class-attribute instance-attribute

max_delay_seconds: float = 30.0

max_attempts property

max_attempts: int

Return the initial attempt plus configured retries.

lease_seconds property

lease_seconds: float

Return a recovery lease longer than the configured trial timeout.

delay_before_retry

delay_before_retry(failed_attempt_number: int) -> float

Return deterministic capped exponential backoff.

Source code in src/tunarag/retry.py
def delay_before_retry(self, failed_attempt_number: int) -> float:
    """Return deterministic capped exponential backoff."""

    if failed_attempt_number < 0:
        raise ValueError("failed attempt number must not be negative")
    return float(
        min(self.base_delay_seconds * (2**failed_attempt_number), self.max_delay_seconds)
    )

is_retryable

is_retryable(error: BaseException) -> bool

Classify known transient failures without parsing messages.

Source code in src/tunarag/retry.py
def is_retryable(self, error: BaseException) -> bool:
    """Classify known transient failures without parsing messages."""

    if isinstance(error, TunaRAGError):
        return error.retryable
    return isinstance(error, (TimeoutError, asyncio.TimeoutError, ConnectionError))

Stopping rules

stopping

Composable stop conditions for study execution.

StopContext dataclass

StopContext(completed_trials: int, exact_cost: float = 0.0, estimated_cost: float = 0.0, elapsed_seconds: float = 0.0, successful_trials: int = 0, failed_trials: int = 0, objective_scores: tuple[float | None, ...] = ())

Current study totals supplied to stop conditions.

completed_trials instance-attribute

completed_trials: int

exact_cost class-attribute instance-attribute

exact_cost: float = 0.0

estimated_cost class-attribute instance-attribute

estimated_cost: float = 0.0

elapsed_seconds class-attribute instance-attribute

elapsed_seconds: float = 0.0

successful_trials class-attribute instance-attribute

successful_trials: int = 0

failed_trials class-attribute instance-attribute

failed_trials: int = 0

objective_scores class-attribute instance-attribute

objective_scores: tuple[float | None, ...] = ()

StopCondition

Bases: Protocol

Return whether execution should stop for the current context.

should_stop

should_stop(context: StopContext) -> bool
Source code in src/tunarag/stopping.py
def should_stop(self, context: StopContext) -> bool: ...

MaxTrials dataclass

MaxTrials(limit: int)

Stop after a maximum number of completed trials.

limit instance-attribute

limit: int

should_stop

should_stop(context: StopContext) -> bool
Source code in src/tunarag/stopping.py
def should_stop(self, context: StopContext) -> bool:
    return context.completed_trials >= self.limit

fingerprint

fingerprint() -> object
Source code in src/tunarag/stopping.py
def fingerprint(self) -> object:
    return {"limit": self.limit}

CostBudget dataclass

CostBudget(limit: float, count_estimated: bool = True)

Stop when exact cost, or exact plus estimated cost, reaches a budget.

limit instance-attribute

limit: float

count_estimated class-attribute instance-attribute

count_estimated: bool = True

should_stop

should_stop(context: StopContext) -> bool
Source code in src/tunarag/stopping.py
def should_stop(self, context: StopContext) -> bool:
    total = context.exact_cost + (context.estimated_cost if self.count_estimated else 0.0)
    return total >= self.limit

fingerprint

fingerprint() -> object
Source code in src/tunarag/stopping.py
def fingerprint(self) -> object:
    return {"limit": self.limit, "count_estimated": self.count_estimated}

TimeBudget dataclass

TimeBudget(seconds: float)

Stop when elapsed wall-clock execution reaches a limit.

seconds instance-attribute

seconds: float

should_stop

should_stop(context: StopContext) -> bool
Source code in src/tunarag/stopping.py
def should_stop(self, context: StopContext) -> bool:
    return context.elapsed_seconds >= self.seconds

fingerprint

fingerprint() -> object
Source code in src/tunarag/stopping.py
def fingerprint(self) -> object:
    return {"seconds": self.seconds}

TargetScore dataclass

TargetScore(target: float)

Stop when any successful objective reaches a benefit-oriented target.

target instance-attribute

target: float

should_stop

should_stop(context: StopContext) -> bool
Source code in src/tunarag/stopping.py
def should_stop(self, context: StopContext) -> bool:
    return any(score is not None and score >= self.target for score in context.objective_scores)

fingerprint

fingerprint() -> object
Source code in src/tunarag/stopping.py
def fingerprint(self) -> object:
    return {"target": self.target}

MaxFailures dataclass

MaxFailures(limit: int, consecutive: bool = False)

Stop after a total or consecutive number of failed trials.

limit instance-attribute

limit: int

consecutive class-attribute instance-attribute

consecutive: bool = False

should_stop

should_stop(context: StopContext) -> bool
Source code in src/tunarag/stopping.py
def should_stop(self, context: StopContext) -> bool:
    if not self.consecutive:
        return context.failed_trials >= self.limit
    failures = 0
    for score in reversed(context.objective_scores):
        if score is not None:
            break
        failures += 1
    return failures >= self.limit

fingerprint

fingerprint() -> object
Source code in src/tunarag/stopping.py
def fingerprint(self) -> object:
    return {"limit": self.limit, "consecutive": self.consecutive}

NoImprovement dataclass

NoImprovement(patience: int, min_delta: float = 0.0, warmup_trials: int = 1)

Stop after completed trials fail to improve the best objective.

patience instance-attribute

patience: int

min_delta class-attribute instance-attribute

min_delta: float = 0.0

warmup_trials class-attribute instance-attribute

warmup_trials: int = 1

should_stop

should_stop(context: StopContext) -> bool
Source code in src/tunarag/stopping.py
def should_stop(self, context: StopContext) -> bool:
    if context.completed_trials < self.warmup_trials or not context.objective_scores:
        return False
    best: float | None = None
    since_improvement = 0
    for score in context.objective_scores:
        if score is not None and (best is None or score > best + self.min_delta):
            best = score
            since_improvement = 0
        else:
            since_improvement += 1
    return best is not None and since_improvement >= self.patience

fingerprint

fingerprint() -> object
Source code in src/tunarag/stopping.py
def fingerprint(self) -> object:
    return {
        "patience": self.patience,
        "min_delta": self.min_delta,
        "warmup_trials": self.warmup_trials,
    }

Errors

errors

Structured public errors with secret-safe diagnostic details.

ErrorCode

Bases: str, Enum

Stable machine-readable error identifiers.

CONFIGURATION class-attribute instance-attribute

CONFIGURATION = 'configuration.invalid'

SEARCH_SPACE class-attribute instance-attribute

SEARCH_SPACE = 'search_space.invalid'

DATASET class-attribute instance-attribute

DATASET = 'dataset.invalid'

ADAPTER class-attribute instance-attribute

ADAPTER = 'adapter.failed'

EVALUATION class-attribute instance-attribute

EVALUATION = 'evaluation.failed'

STORE class-attribute instance-attribute

STORE = 'store.failed'

RESUME class-attribute instance-attribute

RESUME = 'resume.incompatible'

BUDGET class-attribute instance-attribute

BUDGET = 'budget.exhausted'

OPTIONAL_DEPENDENCY class-attribute instance-attribute

OPTIONAL_DEPENDENCY = 'dependency.optional_missing'

SYNC_IN_ASYNC class-attribute instance-attribute

SYNC_IN_ASYNC = 'runtime.sync_in_async'

TunaRAGError

TunaRAGError(message: str, *, stage: str | None = None, retryable: bool = False, details: Mapping[str, Any] | None = None)

Bases: Exception

Base class for public package errors.

Source code in src/tunarag/errors.py
def __init__(
    self,
    message: str,
    *,
    stage: str | None = None,
    retryable: bool = False,
    details: Mapping[str, Any] | None = None,
) -> None:
    if not message.strip():
        raise ValueError("error message must not be empty")
    if stage is not None and not stage.strip():
        raise ValueError("error stage must not be empty")
    self.message = message
    self.stage = stage
    self.retryable = retryable
    safe_details = cast(dict[str, Any], redact(details or {}))
    self.details: Mapping[str, Any] = MappingProxyType(safe_details)
    super().__init__(message)

code class-attribute instance-attribute

message instance-attribute

message = message

stage instance-attribute

stage = stage

retryable instance-attribute

retryable = retryable

details instance-attribute

details: Mapping[str, Any] = MappingProxyType(safe_details)

as_dict

as_dict() -> dict[str, Any]

Return a JSON-compatible diagnostic representation.

Source code in src/tunarag/errors.py
def as_dict(self) -> dict[str, Any]:
    """Return a JSON-compatible diagnostic representation."""

    return {
        "code": self.code.value,
        "message": self.message,
        "stage": self.stage,
        "retryable": self.retryable,
        "details": dict(self.details),
    }

ConfigurationError

ConfigurationError(message: str, *, stage: str | None = None, retryable: bool = False, details: Mapping[str, Any] | None = None)

Bases: TunaRAGError

Configuration could not be parsed or validated.

Source code in src/tunarag/errors.py
def __init__(
    self,
    message: str,
    *,
    stage: str | None = None,
    retryable: bool = False,
    details: Mapping[str, Any] | None = None,
) -> None:
    if not message.strip():
        raise ValueError("error message must not be empty")
    if stage is not None and not stage.strip():
        raise ValueError("error stage must not be empty")
    self.message = message
    self.stage = stage
    self.retryable = retryable
    safe_details = cast(dict[str, Any], redact(details or {}))
    self.details: Mapping[str, Any] = MappingProxyType(safe_details)
    super().__init__(message)

code class-attribute instance-attribute

SearchSpaceError

SearchSpaceError(message: str, *, stage: str | None = None, retryable: bool = False, details: Mapping[str, Any] | None = None)

Bases: TunaRAGError

A search-space definition or candidate is invalid.

Source code in src/tunarag/errors.py
def __init__(
    self,
    message: str,
    *,
    stage: str | None = None,
    retryable: bool = False,
    details: Mapping[str, Any] | None = None,
) -> None:
    if not message.strip():
        raise ValueError("error message must not be empty")
    if stage is not None and not stage.strip():
        raise ValueError("error stage must not be empty")
    self.message = message
    self.stage = stage
    self.retryable = retryable
    safe_details = cast(dict[str, Any], redact(details or {}))
    self.details: Mapping[str, Any] = MappingProxyType(safe_details)
    super().__init__(message)

code class-attribute instance-attribute

DatasetError

DatasetError(message: str, *, details: Mapping[str, Any] | None = None)

Bases: TunaRAGError

An evaluation dataset could not be loaded or validated.

Source code in src/tunarag/errors.py
def __init__(self, message: str, *, details: Mapping[str, Any] | None = None) -> None:
    super().__init__(message, stage="dataset", details=details)

code class-attribute instance-attribute

AdapterError

AdapterError(message: str, *, stage: str | None = None, retryable: bool = False, details: Mapping[str, Any] | None = None)

Bases: TunaRAGError

The user-owned RAG adapter failed.

Source code in src/tunarag/errors.py
def __init__(
    self,
    message: str,
    *,
    stage: str | None = None,
    retryable: bool = False,
    details: Mapping[str, Any] | None = None,
) -> None:
    if not message.strip():
        raise ValueError("error message must not be empty")
    if stage is not None and not stage.strip():
        raise ValueError("error stage must not be empty")
    self.message = message
    self.stage = stage
    self.retryable = retryable
    safe_details = cast(dict[str, Any], redact(details or {}))
    self.details: Mapping[str, Any] = MappingProxyType(safe_details)
    super().__init__(message)

code class-attribute instance-attribute

EvaluationError

EvaluationError(message: str, *, stage: str | None = None, retryable: bool = False, details: Mapping[str, Any] | None = None)

Bases: TunaRAGError

An evaluator failed or returned invalid observations.

Source code in src/tunarag/errors.py
def __init__(
    self,
    message: str,
    *,
    stage: str | None = None,
    retryable: bool = False,
    details: Mapping[str, Any] | None = None,
) -> None:
    if not message.strip():
        raise ValueError("error message must not be empty")
    if stage is not None and not stage.strip():
        raise ValueError("error stage must not be empty")
    self.message = message
    self.stage = stage
    self.retryable = retryable
    safe_details = cast(dict[str, Any], redact(details or {}))
    self.details: Mapping[str, Any] = MappingProxyType(safe_details)
    super().__init__(message)

code class-attribute instance-attribute

StoreError

StoreError(message: str, *, stage: str | None = None, retryable: bool = False, details: Mapping[str, Any] | None = None)

Bases: TunaRAGError

Durable state could not be read or written.

Source code in src/tunarag/errors.py
def __init__(
    self,
    message: str,
    *,
    stage: str | None = None,
    retryable: bool = False,
    details: Mapping[str, Any] | None = None,
) -> None:
    if not message.strip():
        raise ValueError("error message must not be empty")
    if stage is not None and not stage.strip():
        raise ValueError("error stage must not be empty")
    self.message = message
    self.stage = stage
    self.retryable = retryable
    safe_details = cast(dict[str, Any], redact(details or {}))
    self.details: Mapping[str, Any] = MappingProxyType(safe_details)
    super().__init__(message)

code class-attribute instance-attribute

ResumeError

ResumeError(message: str, *, stage: str | None = None, retryable: bool = False, details: Mapping[str, Any] | None = None)

Bases: TunaRAGError

A study cannot resume under the supplied semantics.

Source code in src/tunarag/errors.py
def __init__(
    self,
    message: str,
    *,
    stage: str | None = None,
    retryable: bool = False,
    details: Mapping[str, Any] | None = None,
) -> None:
    if not message.strip():
        raise ValueError("error message must not be empty")
    if stage is not None and not stage.strip():
        raise ValueError("error stage must not be empty")
    self.message = message
    self.stage = stage
    self.retryable = retryable
    safe_details = cast(dict[str, Any], redact(details or {}))
    self.details: Mapping[str, Any] = MappingProxyType(safe_details)
    super().__init__(message)

code class-attribute instance-attribute

BudgetError

BudgetError(message: str, *, stage: str | None = None, retryable: bool = False, details: Mapping[str, Any] | None = None)

Bases: TunaRAGError

A configured scheduling budget has been exhausted.

Source code in src/tunarag/errors.py
def __init__(
    self,
    message: str,
    *,
    stage: str | None = None,
    retryable: bool = False,
    details: Mapping[str, Any] | None = None,
) -> None:
    if not message.strip():
        raise ValueError("error message must not be empty")
    if stage is not None and not stage.strip():
        raise ValueError("error stage must not be empty")
    self.message = message
    self.stage = stage
    self.retryable = retryable
    safe_details = cast(dict[str, Any], redact(details or {}))
    self.details: Mapping[str, Any] = MappingProxyType(safe_details)
    super().__init__(message)

code class-attribute instance-attribute

MissingOptionalDependencyError

MissingOptionalDependencyError(feature: str, extra: str)

Bases: TunaRAGError

A requested integration extra is not installed.

Source code in src/tunarag/errors.py
def __init__(self, feature: str, extra: str) -> None:
    if not feature.strip() or not extra.strip():
        raise ValueError("feature and extra must not be empty")
    super().__init__(
        f"{feature} requires the optional '{extra}' extra",
        stage="dependency",
        details={"feature": feature, "extra": extra},
    )

code class-attribute instance-attribute

SyncInAsyncContextError

SyncInAsyncContextError()

Bases: TunaRAGError

A synchronous entry point was called from an active event loop.

Source code in src/tunarag/errors.py
def __init__(self) -> None:
    super().__init__(
        "the synchronous API cannot run inside an active event loop; use the async API",
        stage="runtime",
    )

code class-attribute instance-attribute

redact

redact(value: Any, *, key: str | None = None) -> Any

Return a recursively redacted, JSON-compatible diagnostic value.

Source code in src/tunarag/errors.py
def redact(value: Any, *, key: str | None = None) -> Any:
    """Return a recursively redacted, JSON-compatible diagnostic value."""

    if key is not None and _is_sensitive_key(key):
        return _REDACTED
    if isinstance(value, Secret):
        return _REDACTED
    if is_dataclass(value):
        return {
            field.name: redact(getattr(value, field.name), key=field.name)
            for field in fields(cast(Any, value))
        }
    if isinstance(value, Mapping):
        return {str(item_key): redact(item, key=str(item_key)) for item_key, item in value.items()}
    if isinstance(value, Sequence) and not isinstance(value, (str, bytes, bytearray)):
        return [redact(item) for item in value]
    if value is None or isinstance(value, (bool, int, float, str)):
        return value
    return f"<{type(value).__name__}>"