TauOpt Python API reference#

Rendered from tauopt 0.4.0 by tools/gen_api_reference.py; python/tests/test_reference_is_current.py fails when this page and the package disagree. Signatures come from inspect.signature; the prose is the whole docstring for Client and ask and the first paragraph elsewhere. Usage walkthroughs live in the how-to pages; this page lists what exists.

Top-level exports#

Name

Kind

Summary

Client

class

The TauOpt client: one experiment/suggest/observe surface over two interchangeable drivers.

Page

class

One page of a list route (Client.suggestions, Client.observations): the list every caller iterates, indexes and measures, plus total – the TOTAL number of rows under the call’s filters (the service’s count; the page’s own length on the all-rows path), so a paging caller knows when it has read the last page.

ApiError

class

Non-2xx API response, surfaced with the server’s error payload.

OptimizationComplete

class

The optimization is COMPLETE – a terminal state, not a failure (the Ax completion-ladder shape).

TerminalApiError

class

A wire-mapped TERMINAL error: dual-lineage by construction – the full ApiError surface (status_code / error_type / details) plus the OptimizationComplete catch.

SpaceExhausted

class

PROVEN exhaustion: every input-feasible point is taken – the search space is complete, by construction (wire error_type: "space_exhausted", non-retryable 409).

AskDeadlineExceeded

class

Caps-mode request-deadline refusal, the one engine error that is a wire error on both planes: 503 ask_deadline_exceeded, deliberately no Retry-After (a caps re-ask repeats deterministic work, so waiting fixes nothing).

BackstopExceeded

class

The supervisor’s refusal: the SAME 503 ask_deadline_exceeded wire class with point = "backstop" – the ask did not answer within B(max_wait_s) = max(1.25 W, W + 3 s) (the protocol constants of tauopt._backstop), so a typed refusal was sent and the work abandoned or killed behind it.

AskValidationError

class

The caller’s argument error at the ask boundary – the ValueError class both drivers answer as 400 validation_error: n < 1, a max_wait_s outside (0, 300], strategy: "auto" with params, intents without dedup, a params key the strategy does not read, or a params value the strategy’s reader refuses (the wrong type, out of range, an unknown enum value, a two-writer or posture conflict, or a composition of valid values the strategy cannot run on this space).

LocalDriver

class

In-process implementation of the driver surface consumed by tauopt.Client.

OptimizerConfig

class

Fully explicit strategy selection – the public config stratum.

stable_seed

function

Deterministic seed from arbitrary parts via blake2b.

Parameter

class

One tunable parameter.

Space

class

An ordered list of parameters.

LinearConstraint

class

sum of terms[name] * value op rhs – over double, non-log, non-grid, unconditional parameters only (the SigOpt restriction; it keeps the unit-space transform affine-exact, so GP candidates are feasible by construction in raw space).

ExperimentState

class

The engine’s complete record of one experiment: search space, objective (plus optional metrics declarations), observation history, and open suggestions.

MetricSpec

class

One declared metric.

ask

function

Produce up to n suggestion drafts + the provenance that re-derives them.

__version__

constant

Package version string: 0.4.0

Client#

tauopt.Client – The TauOpt client: one experiment/suggest/observe surface over two interchangeable drivers.

Constructor:

Client(
    base_url: str | None = None,
    token: str | None = None,
    driver: Literal['hosted', 'local'] = 'hosted',
    *,
    timeout_s: float = 60.0,
    supervisor: Literal['auto', 'process', 'off'] = 'auto',
    backstop_alpha: float | None = None,
    backstop_grace_s: float | None = None,
)

Hosted (default): Client(base_url=..., token=...) talks to a TauOpt service over HTTP with Bearer auth (featherweight: httpx + pydantic only). Local: Client(driver="local") runs the full engine in-process with the same semantics, no server (needs tauopt[local]; the GP strategies need tauopt[botorch]).

A deadline-bearing ask (suggest(..., max_wait_s=W)) is BACKSTOPPED on the local driver: a typed answer within max(1.25 W, W + 3 s) whatever the engine’s cooperative clocks manage. supervisor picks the shape – "auto" (default) runs a deadline-bearing ask on a warm worker PROCESS, one per driver, started at the first such ask and killed and replaced behind a refusal; "process" runs every ask there (an ask with no deadline then gets a 375 s backstop, derived from the service’s 300 s substitute, and still carries no deadline of its own); "off" runs it inline, as before. backstop_alpha and backstop_grace_s override the protocol constants 1.25 and 3.0 for this driver (None = the constants). Local-driver options only: the hosted driver refuses them with ValueError. close() stops the worker. The worker hand-off is POSIX-only in this version: on Windows "auto" runs inline and "process" is refused at construction.

On the hosted driver the suggest POST listens for the backstop of its deadline plus a 40 s allowance whenever that exceeds timeout_s (415 s for an ask with no deadline), so the service’s typed refusal arrives before the client stops listening (the ask runs on its queue lane; provenance.lane worker); every other route keeps timeout_s.

Raises ValueError when the hosted driver is missing base_url/token, and ImportError from the local driver when math extras are absent (the message names the exact pip install target).

Client.close#

Client.close() -> None

Release the driver’s resources: the hosted driver’s connection pool, or the local driver’s worker process (the next supervised ask starts a fresh one). Idempotent.

Client.create_experiment#

Client.create_experiment(
    name: str,
    parameters: list[dict[str, Any]],
    metric: dict[str, Any] | None = None,
    metrics: list[dict[str, Any]] | None = None,
    linear_constraints: list[dict[str, Any]] | None = None,
    num_solutions: int = 1,
    observation_budget: int | None = None,
    parallel_bandwidth: int = 1,
    metadata: dict[str, Any] | None = None,
    suggestion_ttl_s: int | None = None,
) -> Experiment

metric (single-objective) XOR metrics (the metrics cluster: multimetric Pareto, thresholds, strategy=store|constraint). metric is a dict {"name": ..., "objective": "minimize" | "maximize"}; a metric without an objective maximizes. A bare string metric="name" is refused before any request leaves (400 validation_error naming the accepted form): the shorthand named no objective and silently maximized a loss. num_solutions asks best_assignments for k diverse top solutions (single-objective only). observation_budget is a planning hint rather than an enforced stop: the default routing never reads it; both drivers inject it as the queue_selection horizon when that config block names none; asks past the budget still answer, and your loop decides when to quit. suggestion_ttl_s is the hosted plane’s open-suggestion TTL in seconds: an open suggestion older than it is closed by the next ask on the experiment (expired_at set, its bandwidth slot reissued) and an observation against it answers 409 suggestion_expired; None leaves the service default (86400 s) in force and sends no key, 0 never expires. The local driver records and serves the value (the hosted field’s integer domain, 0..2147483647, lax-coerced as the service coerces, else 400 validation_error) and never expires a suggestion.

Client.experiment#

Client.experiment(experiment_id: str) -> Experiment

Fetch an experiment by id, with live progress (observation count, best observation, the open-suggestion count, and – on the hosted plane – the next ask’s cost estimate expected_suggest_s and, for a multimetric experiment, the frontier size frontier_count).

Client.update_experiment#

Client.update_experiment(
    experiment_id: str,
    name: str | None = None,
    observation_budget: int | None = None,
    parallel_bandwidth: int | None = None,
    metadata: dict[str, Any] | None = None,
    metric_thresholds: dict[str, float | None] | None = None,
    suggestion_ttl_s: int | None = None,
) -> Experiment

PATCH semantics – None leaves a field unchanged. Space and metric structure are immutable; metric_thresholds moves thresholds on declared metrics (SigOpt allowed mid-experiment threshold updates). suggestion_ttl_s moves the hosted plane’s open-suggestion TTL (0 never expires; the new value applies from the next ask). Because None means unchanged here as for every field, reverting a hosted experiment to the service default TTL has no Client form: send the REST PATCH with an explicit "suggestion_ttl_s": null (the MCP server’s update_experiment drops null inputs the same way). The local driver records and serves the value (the hosted field’s integer domain, 0..2147483647, lax-coerced as the service coerces, else 400 validation_error) and never expires a suggestion.

Client.archive_experiment#

Client.archive_experiment(experiment_id: str) -> None

Archive (soft-delete) an experiment – the active-experiment-cap remedy on the hosted plane (DELETE /v1/experiments/{id}, 204).

An archived experiment stays READABLE: experiment() still returns it (state becomes "deleted"), and best() plus suggestion dismissals (close_suggestion/close_all_suggestions) keep working, so a finished study remains auditable after it frees its cap slot. suggest/observe/update_experiment refuse with a 404 ApiError, and archiving twice 404s too – the hosted plane’s exact semantics; the local driver mirrors them. There is no unarchive.

Flaky-transport note: the hosted DELETE retries on 429 and on 500, 502, 503, and 504, so a success whose response was lost can surface the RETRY’s 404 even though the archive landed – treat not_found from this call as already-archived (the states are operationally equivalent: the cap slot is free either way).

Client.suggest#

Client.suggest(
    experiment_id: str,
    config: dict[str, Any] | None = None,
    max_wait_s: float | None = None,
    on_timeout: str | None = None,
    intents: list[dict[str, Any]] | None = None,
    tag: str | None = None,
) -> Suggestion

Ask for the next point. config pins an explicit optimizer config (the public config stratum); omitted -> the service/engine policy routes.

The wait budget is yours: max_wait_s bounds the whole ask (accepted domain: a number in (0, 300]); on_timeout="ladder" (default) degrades down the robustness ladder with the fallback recorded in provenance, "fail" surfaces an honest timeout (hosted: 503 + Retry-After) so you can run your own backoff. The experiment’s progress.expected_suggest_s estimates the next ask’s cost from history.

Raises AskDeadlineExceeded (a 503 ask_deadline_exceeded ApiError, no Retry-After: a caps re-ask repeats deterministic work) when config sets timebox_mode: "caps" and max_wait_s expires at a rung boundary or at delivery: no suggestion opens and no cheaper rung substitutes; the refusal receipt (point, max_wait_s, elapsed_s, rung) rides the exception’s attributes and its details. Both drivers raise the same class, so one except ApiError handler serves both. On the hosted plane, an ask that first waited for an ask slot reaches the engine with that wait already taken off its budget: the receipt’s max_wait_s is the budget the engine was handed after the wait (the figure you sent, less the queue wait) and elapsed_s counts from the engine’s start. A deadline-bearing ask is also BACKSTOPPED: a typed answer is promised within max(1.25 * max_wait_s, max_wait_s + 3 s) whatever the engine’s cooperative clocks manage (a native kernel that never returns). The local driver runs such an ask on a warm worker process (the constructor’s supervisor); past the backstop it raises BackstopExceeded, the same 503 ask_deadline_exceeded with point "backstop" and a stage (ask, warmup, worker_died, reap, abandoned), the hung work killed behind the response and the state untouched. The hosted plane answers the same class from its own backstop on its queue lane (provenance.lane worker). The suggest POST listens for max(timeout_s, B(W) + 40 s) (415 s with no deadline), so the service’s backstop answers before the client stops listening; a stalled connection ends in ReadTimeout at the derived bound (115 s for a 60 s deadline), or at timeout_s where the derivation is under it. The engine’s own failures outside the never-fail net (AskFailed: every rung failed, or an integrity postcondition failed after a rung answered) surface on both drivers as a 500 ApiError of the same error_type (all_rungs_failed with the attempts in details, integrity_violation with the failed check); the caller’s own argument errors (AskValidationError) are the 400 validation_error class, and any other engine-side ValueError is a 500 internal_error.

intents (dedup is the engine-wide default since 0.3.0 – explicit dedup: false disables and refuses intents): typed authorized-duplicate requests {kind, source_id, ordinal} – validated against the recomputed policy, never trusted; refusals surface in the suggestion’s provenance dedup record.

The ask-loop contract (0.3.0 completion ladder). Exhaustion is COMPLETION rather than failure. On a finite space the engine can prove exhausted (every finite space of at most 1,024 raw points; larger all-finite spaces without conditionals or linear constraints), once every input-feasible point is taken suggest raises SpaceExhausted – PROVEN exhaustion (the proof receipt rides details.dedup; raised when no draft can be produced, while a batch of authorized replicas returns normally with uniqueness_status space_exhausted); outside those proofs a fully covered space surfaces as 409 unique_candidate_unavailable:

try:
    sug = client.suggest(exp_id)
except OptimizationComplete:   # SpaceExhausted narrows the WHICH
    ...                        # done -- read the best point

Old except ApiError handlers keep working (dual lineage). A 409 unique_candidate_unavailable (bounded repair ran dry, NO proof – continuous/large spaces) is NOT completion: check details.dedup.unfilled_count/uniqueness_status, then observe or close pending work first – a same-state re-ask is a DETERMINISTIC repeat (P4), never a retry strategy. Authorized replication intents remain the continuation path at proven exhaustion (noisy objectives).

tag (an optional worker label: 1 to 128 printable characters, no control or separator characters) is stored on the suggestion, served back as Suggestion.tag on every read, and is the exact-match filter of suggestions, so a worker that dies and restarts lists its own open suggestions by its tag (the parallel-workers how-to, “Recovering after a worker dies”); a label, never a key for anything (no quota, no admission). Sent only when set; the hosted plane serves the tag and refuses a value outside the rule as 400 validation_error. The local driver refuses a value outside the rule as the same 400 with the service’s texts, and a non-string, a lone-surrogate or a non-finite tag is refused by the Client itself before any request, on both drivers, with the service’s text (the wire cannot carry those faithfully).

Client.observe#

Client.observe(
    experiment_id: str,
    suggestion_id: str | None = None,
    assignments: Assignments | None = None,
    value: float | None = None,
    values: dict[str, float] | None = None,
    value_stddev: float | None = None,
    values_stddev: dict[str, float] | None = None,
    failed: bool = False,
    metadata: dict[str, Any] | None = None,
) -> Observation

value in single-metric mode, values (name -> value, every optimize/constraint metric required) in metrics mode. value_stddev / values_stddev report measurement noise – it enters the GP as fixed per-point variance.

Client.close_suggestion#

Client.close_suggestion(experiment_id: str, suggestion_id: str) -> None

Dismiss an open suggestion without observing it. Its bandwidth slot frees, and the dismissal advances the sequential streams. Note a dismissed-unobserved point leaves the dedup universe and may be re-suggested later. Unknown id: 404 ApiError; already closed: no-op.

Client.close_all_suggestions#

Client.close_all_suggestions(experiment_id: str) -> int

Bulk-dismiss every open suggestion; returns how many were closed.

Client.best#

Client.best(experiment_id: str) -> BestAssignments

Best result so far: the single best observation (single-objective), k diverse top solutions when the experiment declared num_solutions > 1, or the non-dominated threshold-respecting frontier in metrics mode.

Client.suggestions#

Client.suggestions(
    experiment_id: str,
    state: Literal['open', 'closed'] | None = None,
    tag: str | None = None,
    *,
    limit: int | None = None,
    offset: int = 0,
) -> Page[Suggestion]

List the experiment’s suggestions in id order, as a Page (a list plus total, the service’s count under the filters): every row by default, or only the "open" ones (holding a bandwidth slot, awaiting an observation) or the "closed" ones. Works on archived experiments. This is the state check after a lost response: a POST that died with a 5xx may have committed server-side, so list before re-sending.

tag narrows the list to the suggestions whose worker label is exactly tag (beside state): the restart step of the recovery how-to. The hosted query ?tag= filters on the service; the local driver filters by construction. A value outside the tag’s rule (1 to 128 printable characters) is the same 400 validation_error on both planes; a non-string or a lone-surrogate tag is refused by the Client before any request (httpx would send a number as its decimal string, which the service would accept as a label).

limit and offset page a long history (the hosted window, ?limit=&offset=): limit absent lists every row, present it is clamped to 1..200; offset skips that many rows and must be non-negative – a negative one is a 400 validation_error on both planes (query.offset: Input should be greater than or equal to 0), never floored, and so is a negative limit (only 0 clamps up). total (the service’s count) is the total under state and tag whatever the window (on the hosted plane a paged count and its page are two reads: a concurrent insert can make them differ by one row). A value left at its default stays off the wire. The local driver mirrors the window and the refusals, and a state outside open/closed is the same 400 on both planes.

Client.observations#

Client.observations(
    experiment_id: str,
    *,
    limit: int | None = None,
    offset: int = 0,
    sort: str = 'created',
    dir: Literal['asc', 'desc'] = 'asc',
) -> Page[Observation]

List the experiment’s observations (archived experiments included) as a Page (a list plus total, the service’s count): in report order by default, every row.

sort and dir order the rows as the hosted list does (?sort=&dir=): created is id order (report order); value the first optimized metric’s value, with failed rows (no value) last in either direction and the id as the secondary key; value-<metric> a declared metric by name (a metric absent from a row’s values sorts last); any parameter name its assignment (numeric parameters as numbers, categoricals as text; a conditional parameter’s absent assignment last). The two reserved words win over a parameter of the same name. An unknown key is a 400 validation_error naming the accepted keys, on both planes in the service’s text; so is a dir outside asc/desc. limit and offset page the result: limit absent lists every row, present it is clamped to 1..200; a negative offset (or limit) is a 400 on both planes, never floored; total is the service’s count whatever the window. A value left at its default stays off the wire. The local driver mirrors the whole grammar; its text keys sort by code point where the hosted plane sorts under its database collation.

Client.experiments#

Client.experiments(
    limit: int = 50,
    offset: int = 0,
    *,
    q: str | None = None,
    sort: Literal['created', 'updated', 'name', 'observation_count'] | None = None,
    dir: Literal['asc', 'desc'] | None = None,
    include_deleted: bool = False,
) -> ExperimentList

List your experiments as one page of the {count, data} envelope (hosted: GET /v1/experiments): by default the ACTIVE ones, newest first, with count the active total the hosted plane’s active-experiment cap counts, each row WITH its progress (observation count, best observation, open-suggestion count; the hosted plane adds a multimetric experiment’s frontier size; the hosted list leaves expected_suggest_s null – read one by id for the estimate). limit is clamped to 1..200 on both planes; a negative offset (or limit) is a 400 validation_error on both (query.offset: Input should be greater than or equal to 0), never floored. q narrows to names containing it, case-insensitively (the % and _ wildcards are literal); sort is created (the default), updated (the last change to the experiment, its history or its open set), name or observation_count, dir asc or desc (the default), ties broken by the newest id; include_deleted lists the archived ones too (state "deleted"); count follows every filter. A filter left unset stays off the wire. The local driver mirrors the grammar and the refusals; its name order is by code point where the hosted plane sorts under its database collation. This is the reconciliation step after a lost create_experiment response: a POST is retried only on a 429 and on the transport’s pre-commit set (experiment_busy, queue_full, and ask_slots_busy, retired at deploy 2 with the lane it guarded but kept in the set); a lost response or any other 5xx – connection_pool_busy included, which can follow a commit – is never retried, so list before creating again.

Client.health#

Client.health() -> Health

Liveness probe (hosted: GET /health, no token needed, so it says nothing about yours): status is "ok" after the service’s database round-trip. A hosted 5xx (the database round-trip failed) is retried by the transport like any GET – up to five attempts, about eight seconds of backoff – before the ApiError surfaces. The local driver answers "ok".

Client.auth_check#

Client.auth_check() -> AuthCheck

Token probe (hosted: GET /v1/auth/check, not counted as usage): user_id is the account the token resolves to, or a 401 unauthorized ApiError for a missing, malformed, unknown, revoked, or other-lane token. The local driver has no accounts and answers user_id=None.

Exceptions#

Defined in tauopt._http and exported at the top level. Indentation follows the first base class; the parenthesized bases show every lineage, so TerminalApiError and SpaceExhausted are catchable as ApiError too. The wire-to-class mapping and the HTTP statuses are on Errors.

ApiError(RuntimeError) -- Non-2xx API response, surfaced with the server's error payload.
    AskDeadlineExceeded(ApiError) -- Caps-mode request-deadline refusal, the one engine error that is a wire error on both planes: 503 `ask_deadline_exceeded`, deliberately no `Retry-After` (a caps re-ask repeats deterministic work, so waiting fixes nothing).
        BackstopExceeded(AskDeadlineExceeded) -- The supervisor's refusal: the SAME 503 `ask_deadline_exceeded` wire class with `point = "backstop"` -- the ask did not answer within `B(max_wait_s) = max(1.25 W, W + 3 s)` (the protocol constants of `tauopt._backstop`), so a typed refusal was sent and the work abandoned or killed behind it.
OptimizationComplete(Exception) -- The optimization is COMPLETE -- a terminal state, not a failure (the Ax completion-ladder shape).
    TerminalApiError(OptimizationComplete, ApiError) -- A wire-mapped TERMINAL error: dual-lineage by construction -- the full `ApiError` surface (status_code / error_type / details) plus the `OptimizationComplete` catch.
        SpaceExhausted(TerminalApiError) -- PROVEN exhaustion: every input-feasible point is taken -- the search space is complete, by construction (wire `error_type: "space_exhausted"`, non-retryable 409).

Wire models#

Defined in tauopt.models: pydantic mirrors of the /v1 api objects – what both drivers hand back to callers. Every model is frozen and ignores unknown fields from the server.

Assignments = dict[str, float | int | str] (a type alias, not a model).

Metric#

One experiment metric: objective sets the direction, strategy the role (optimized, stored-only, or an outcome constraint), and threshold the constraint/frontier bound.

Field

Type

Default

name

str

required

objective

Literal['maximize', 'minimize']

'maximize'

strategy

Literal['optimize', 'store', 'constraint']

'optimize'

threshold

float | None

None

ParameterSpec#

One search-space parameter: numeric (double/integer) with exactly one of bounds or an explicit grid, or categorical with categorical_values. conditions makes it active only when each named parent categorical takes one of the listed values.

Field

Type

Default

name

str

required

type

Literal['double', 'integer', 'categorical']

required

bounds

tuple[float, float] | None

None

log_scale

bool

False

categorical_values

tuple[str, ...] | None

None

grid

tuple[float, ...] | None

None

conditions

dict[str, tuple[str, ...]] | None

None

LinearConstraintSpec#

One linear constraint over plain double parameters: the terms coefficient-weighted sum op rhs. Suggestions satisfy every linear constraint by construction.

Field

Type

Default

terms

dict[str, float]

required

op

Literal['>=', '<=']

required

rhs

float

required

ProvenanceTimings#

Wall-clock split of the suggest step: GP fit vs acquisition-optimization seconds across the ask. Absent on instant rungs (random/sobol).

Field

Type

Default

fit_s

float

required

acq_s

float

required

CenterRecord#

Mirror of the engine’s center-in-bootstrap decision record (branch / action / reason).

Field

Type

Default

branch

int

required

action

str

required

reason

str | None

None

Provenance#

Mirror of the engine’s provenance receipt (identity triple).

Field

Type

Default

engine_version

str

required

engine_sha

str

required

deps_fingerprint

str

required

strategy

str

required

config_hash

str

required

policy

str | None

None

seed

int

required

fallback_from

str | None

None

timings

ProvenanceTimings | None

None

center

CenterRecord | None

None

dedup

dict | None

None

selection

list[dict] | None

None

fits

list[dict] | None

None

work

list[dict] | None

None

acq_events

int | None

None

aggregation

dict | None

None

conditioning_events

list[dict] | None

None

acq_init_filters

list[dict] | None

None

fallback_attempts

list[dict] | None

None

base_noise

float | None

None

queue_wait_s

float | None

None

lane

str | None

None

attempt

int | None

None

BestObservation#

One entry of best_assignments: value in single-metric mode, values for multimetric (frontier entries).

Field

Type

Default

assignments

Assignments

required

value

float | None

None

values

dict[str, float] | None

None

Progress#

Experiment progress snapshot: observation_count, the scalar best_observation, expected_suggest_s (the next ask’s cost estimate; populated by the hosted plane, None from the local driver), open_suggestions (the open-suggestion count, both drivers), and frontier_count (the size of the non-dominated frontier of a multimetric experiment, the hosted plane’s bounded count: None on single-metric experiments, from the local driver, and past the service’s listing bound). The scalar best is None until a non-failed observation exists, and always None for multimetric experiments (the frontier is read via best assignments instead).

Field

Type

Default

observation_count

int

required

best_observation

BestObservation | None

None

expected_suggest_s

float | None

None

open_suggestions

int | None

None

frontier_count

int | None

None

Experiment#

An optimization experiment: the search space (parameters plus optional linear_constraints), the metric cluster (metric, or metrics when multimetric), and run settings (observation_budget, parallel_bandwidth, num_solutions).

Field

Type

Default

id

str

required

name

str

required

parameters

tuple[ParameterSpec, ...]

required

metric

Metric

required

metrics

tuple[Metric, ...] | None

None

linear_constraints

tuple[LinearConstraintSpec, ...] | None

None

num_solutions

int

1

observation_budget

int | None

None

parallel_bandwidth

int

1

suggestion_ttl_s

int | None

None

metadata

dict[str, Any] | None

None

state

Literal['active', 'deleted']

'active'

created

str

required

progress

Progress | None

None

Suggestion#

One suggested trial: the assignments to evaluate next, plus the provenance receipt identifying exactly how it was derived (replay additionally needs the recorded experiment state). state flips to closed when an observation reports against it, when it is explicitly dismissed, or (hosted plane only) when the next ask finds it older than the experiment’s suggestion_ttl_s and sets expired_at.

Field

Type

Default

id

str

required

experiment_id

str

required

assignments

Assignments

required

state

Literal['open', 'closed']

'open'

created

str

required

expired_at

str | None

None

provenance

Provenance

required

intent

dict | None

None

tag

str | None

None

Observation#

One reported result for a set of assignments: value or per-metric values, optional noise via value_stddev/values_stddev, or failed=True for a failed evaluation. suggestion_id is None when the observation is reported off-suggestion (direct assignments).

Field

Type

Default

id

str

required

experiment_id

str

required

suggestion_id

str | None

None

assignments

Assignments

required

value

float | None

None

values

dict[str, float] | None

None

value_stddev

float | None

None

values_stddev

dict[str, float] | None

None

failed

bool

False

metadata

dict[str, Any] | None

None

created

str

required

BestAssignments#

200-with-empty-data when nothing succeeded yet – the answer is an empty set, not a broken service. Multimetric: the Pareto frontier. num_solutions > 1: the diverse top-k.

Field

Type

Default

count

int

0

data

tuple[BestObservation, ...]

()

ExperimentList#

One page of GET /v1/experiments: count is the total under the call’s filters (by default the caller’s ACTIVE experiment total – the number the active-experiment cap counts – not the page size) and data the page, newest first by default, each row WITH its progress (the hosted list leaves expected_suggest_s null; read one by id for the estimate). Archived experiments are listed only with include_deleted; read one by id.

Field

Type

Default

count

int

0

data

tuple[Experiment, ...]

()

Health#

GET /health: status is "ok" after the service’s database round-trip (no token needed, so it says nothing about yours); the local driver answers "ok" for the in-process engine.

Field

Type

Default

status

str

required

AuthCheck#

GET /v1/auth/check: user_id is the account the token resolves to (an integer from the hosted service; the request is not counted as usage). The local driver has no accounts and answers None.

Field

Type

Default

user_id

int | None

None

Engine#

Defined in tauopt.engine: the function and the classes the how-to and explanation pages construct directly. ask, OptimizerConfig, Parameter, LinearConstraint, Space, MetricSpec, ExperimentState are exported at the top level; AskResult, Objective, ObservationRecord are importable from tauopt.engine. Every class below is frozen and rejects unknown fields. Pin names in the prose (P1, P4, P8) are the invariants listed in AGENTS.md. Field semantics and validation rules for OptimizerConfig are on Strategies and configuration.

ask#

ask(
    state: ExperimentState,
    *,
    config: OptimizerConfig | None = None,
    policy: Policy | None = None,
    n: int = 1,
    max_wait_s: float | None = None,
    on_timeout: Literal['ladder', 'fail'] = 'ladder',
    intents: Sequence[IntentRequest] = (),
) -> AskResult

Produce up to n suggestion drafts + the provenance that re-derives them.

config (explicit, wins) or policy (defaults to DefaultPolicyV1) chooses the intended strategy. strategy: "auto" is the seedable policy path: it routes exactly like config=None but carries explicit top-level fields (seed, center_in_bootstrap, dedup, duplicate_intents, queue_selection, categorical_mode, timebox_mode, base_noise – the composable flag surface) into whatever rung the policy picks and down the fallback ladder. max_wait_s deadlines the WHOLE ask; on_timeout="fail" re-raises TimeboxExceeded instead of laddering. intents are typed duplicate requests, validated – never trusted – after unique search; they require config.dedup. Flag-off asks always return exactly n drafts; dedup asks may return fewer, with the shortfall accounted on the AskResult.

AskResult#

The ask surface (additive schema): drafts are the returned candidates (unique fills first, then authorized grants – the slot-fill order); the counters partition requested_q. Precedence: space_exhausted is set whenever the enumeration PROOF holds – including alongside a full batch of authorized replicas; unique_candidate_unavailable only when slots remain UNFILLED without proof, and it is NON-RETRYABLE for identical (state, config) – P4 guarantees the same answer until the state hash changes. The partition invariants are MODEL-ENFORCED: a counter that cannot partition requested_q dies at construction, never ships.

Field

Type

Default

drafts

tuple[SuggestionDraft, ...]

required

provenance

EngineProvenance

required

requested_q

int

required

unique_count

int

required

authorized_replica_count

int

0

unfilled_count

int

0

uniqueness_status

Literal['space_exhausted', 'unique_candidate_unavailable'] | None

None

OptimizerConfig#

Fully explicit strategy selection – the public config stratum.

Field

Type

Default

strategy

str

'auto'

params

dict[str, Any]

{}

seed

int | None

None

center_in_bootstrap

bool

False

dedup

bool

True

duplicate_intents

DuplicateIntents | None

None

queue_selection

QueueSelectionPolicy | None

None

categorical_mode

Literal['one_hot', 'mixed', 'mixed_capped'] | None

None

timebox_mode

Literal['budget', 'off', 'caps'] | None

None

base_noise

float

0.001

Parameter#

One tunable parameter.

Field

Type

Default

name

str

required

type

Literal['double', 'integer', 'categorical']

required

bounds

tuple[float, float] | None

None

log_scale

bool

False

categorical_values

tuple[str, ...] | None

None

grid

tuple[float, ...] | None

None

conditions

dict[str, tuple[str, ...]] | None

None

LinearConstraint#

sum of terms[name] * value op rhs – over double, non-log, non-grid, unconditional parameters only (the SigOpt restriction; it keeps the unit-space transform affine-exact, so GP candidates are feasible by construction in raw space).

Field

Type

Default

terms

dict[str, float]

required

op

Literal['>=', '<=']

required

rhs

float

required

Space#

An ordered list of parameters. Order is semantic: it fixes unit-cube dimensions AND condition topology (a condition may only reference an earlier parameter).

Field

Type

Default

parameters

tuple[Parameter, ...]

required

linear_constraints

tuple[LinearConstraint, ...]

()

Objective#

The optimization target: a metric name plus a goal direction (“max” or “min”).

Field

Type

Default

metric

str

required

goal

Literal['max', 'min']

'max'

MetricSpec#

One declared metric. optimize metrics drive the search (1-4 of them; >=2 => Pareto). store metrics are recorded, never modeled. constraint metrics are black-box feasibility requirements: goal=max => value >= threshold is feasible (min => <=). Thresholds on optimize metrics are only meaningful with >=2 optimized (SigOpt semantics: they shape the frontier region – and here, the qLogNEHVI reference point).

Field

Type

Default

name

str

required

goal

Literal['max', 'min']

'max'

strategy

Literal['optimize', 'store', 'constraint']

'optimize'

threshold

float | None

None

ObservationRecord#

A reported result. failed=True records the attempt with no value; failed points are excluded from modeling. value is single-metric mode, values is metrics mode (never both); value_stddev/values_stddev report measurement noise (feeds the GP as fixed per-point variance). intent marks a row that was an authorized duplicate grant – copied from the open suggestion at observe.

Field

Type

Default

assignments

Assignments

required

value

float | None

None

failed

bool

False

values

dict[str, float] | None

None

value_stddev

float | None

None

values_stddev

dict[str, float] | None

None

intent

IntentTag | None

None

ExperimentState#

The engine’s complete record of one experiment: search space, objective (plus optional metrics declarations), observation history, and open suggestions.

Field

Type

Default

space

Space

required

objective

Objective

required

observations

tuple[ObservationRecord, ...]

()

open_suggestions

tuple[OpenSuggestion, ...]

()

n_dismissed

int

0

metrics

tuple[MetricSpec, ...]

()

ExperimentState.with_observation#

ExperimentState.with_observation(obs: ObservationRecord) -> ExperimentState

Append obs, returning the successor state. Raises ValueError unless the assignments validate against the space and the value shape matches this state’s metrics. Does not close a matching open suggestion; callers compose with without_open_suggestion.

ExperimentState.scored_observations#

ExperimentState.scored_observations() -> tuple[ObservationRecord, ...]

Non-failed observations carrying the value shape this state models.

ExperimentState.best_observation#

ExperimentState.best_observation() -> ObservationRecord | None

Single-objective best among FEASIBLE scored observations. Multimetric callers must route to pareto_frontier – a scalar best does not exist there.

ExperimentState.pareto_frontier#

ExperimentState.pareto_frontier() -> tuple[ObservationRecord, ...]

Non-dominated FEASIBLE scored observations over the optimized metrics, filtered to threshold-satisfying points when thresholds are set (SigOpt frontier semantics). Insertion order preserved; the records are the state’s own (identity, not copies). On numpy the hosted plan cap of 4,000 observations builds in a fraction of a second whatever the data; without numpy, or for rows that are not plain floats, the pairwise loop runs.

ExperimentState.to_json#

ExperimentState.to_json() -> str

Serialize to canonical minimal-form JSON (the P1 round-trip surface; P8 golden bytes: post-v0 fields drop at their defaults).

ExperimentState.from_json#

ExperimentState.from_json(payload: str) -> ExperimentState

Parse a to_json payload back into a fully validated state (the P1 round-trip inverse); every construction-time validator runs on load.

ExperimentState.state_hash#

ExperimentState.state_hash() -> str

Content address of this state: blake2b_<16hex> over the to_json bytes, stable across processes; the seed scope point strategies derive from (see resolve_seed).

ExperimentState.space_hash#

ExperimentState.space_hash() -> str

Content address of the space alone: blake2b_<16hex> over its canonical JSON, stable as observations accrue; the sequence-scope seed source for Sobol (see resolve_seed).

Strategy registry#

Names accepted by OptimizerConfig.strategy (tauopt.engine.strategies.registry.known()), the install extra each needs, and the class behind it. Parameter keys, bounds, and defaults are on Strategies and configuration.

Name

Requires

Class

Summary

gp_ei

tauopt[botorch]

tauopt.engine.strategies.gp_ei.GpEiStrategy

Single-objective GP + LogEI strategy (the module docstring carries the full model and params contract).

gp_nehvi

tauopt[botorch]

tauopt.engine.strategies.gp_nehvi.GpNehviStrategy

Multi-objective GP + qLogNEHVI strategy (the module docstring carries the full model and params contract).

random

nothing (stdlib only)

tauopt.engine.strategies.random_.RandomStrategy

Uniform-random strategy (the module docstring carries the floor contract).

sobol

tauopt[local]

tauopt.engine.strategies.sobol.SobolStrategy

Scrambled-Sobol space-filling strategy (the module docstring carries the stream and cursor laws).

ucb_cl

tauopt[botorch]

tauopt.engine.strategies.ucb_cl.UcbClStrategy

GP-UCB with constant-liar batch fill (the module docstring carries the role and params contract).

Fallback ladders (tauopt.engine.policy.default):

  • single-objective: gp_ei -> ucb_cl -> sobol -> random

  • multimetric: gp_nehvi -> sobol -> random