Provenance and determinism#

Closed beta (nothing installed): the receipt this page describes rides every suggestion the hosted service answers (Hosted beta: getting started pastes one); the replay example runs the engine in your process and is for readers with repository access.

The receipt#

The provenance receipt as two layers. The identity, the seven fields through the seed (engine_version, engine_sha, deps_fingerprint, strategy, config_hash, policy, seed), is fed by the installed engine, the config of the ask, and the state at the ask; below it the thirteen fields past the seed (fallback_from, then the twelve audit records) enter no hash or seed. Under the receipt, the replay rebuilds the recorded state as it stood at the ask (the observations in order and the open suggestions) in a fresh process, asks with the pinned config and the ask's own intents, and on an engine whose triple matches, with no wall budget binding, gets the same assignments and the receipt's strategy, config_hash, and seed.

The receipt. Its identity, the seven fields through the seed, fixes the suggestion together with the recorded state and the ask’s intents: the installed engine resolves the triple, the config’s canonical form hashes to config_hash, and the seed is the config’s explicit value or a hash of the strategy, its params, and the state scope (state_hash, or space_hash for sobol). Everything past the seed enters no hash or seed: fallback_from names the descent, the twelve records from timings down are audit, and on the hosted plane the service appends three transport keys of its own (queue_wait_s, lane, attempt) beside them. A replay rebuilds the recorded state as it stood at the ask (the observation history and the open suggestions, in canonical form) in a fresh process, asks with the pinned config and the ask’s own intents, and on an engine whose triple matches – with no wall budget binding, or in caps mode – gets the same assignments with the receipt’s strategy, config_hash, and seed.#

Every suggestion carries a receipt with enough identity to re-derive it. Its fields:

  • The identity triple: engine_version (the release tag), engine_sha (the engine build), and deps_fingerprint (the numerics-relevant dependency set). How each one is resolved is the next section.

  • strategy, the rung that answered; policy, the router that chose it (None when the config pins a strategy other than auto); config_hash, the content address of the config; seed, the integer the strategy ran with.

  • fallback_from, the intended strategy when a ladder descent answered, and fallback_attempts, one record per failed rung with its verbatim error (the local driver; the hosted plane serves these records, and conditioning_events, without their exception_text – the text stays in the service’s stored provenance, the operator’s database, never the wire).

  • timings, the wall-clock split between Gaussian-process (GP) fitting and acquisition optimization, accumulated across every rung attempted; absent when no attempted rung recorded a fit or acquisition phase (a space-filling answer that followed a failed GP rung still carries the GP rung’s timings).

  • dedup, the deduplication record: collisions, repair counts, per-draft origins, the exhaustion proof, intent refusals, and the uniqueness status.

  • GP internals when a fit ran: fits (each fitted model’s parameters as dtype, shape, and a digest of the raw bytes), work (the effective optimizer knobs), acq_events, aggregation (duplicate-point collapsing), conditioning_events (noise-floor retries), and base_noise.

  • Three more audit records when their mechanisms ran: center (the branch center_in_bootstrap took), selection (one queue-selection decision per slot), and acq_init_filters (the armed finite-filtering initializer’s per-attempt receipt).

Everything past the seed is audit and enters no hash or seed, so a receipt can gain fields across versions without moving an older one.

The receipt is also the observability surface. The library emits no log records (no module in tauopt creates a logger), so a quiet run is a normal run and a descent shows up in fallback_from, never on a console. The only messages tauopt itself can write to stderr are two Python warnings warnings from the armed acquisition initializer on gp_nehvi’s retry path: one when no raw candidate scores a non-zero acquisition value and the initial conditions fall back to random draws, one on a Sobol dimension overflow; the ask still answers. Warnings from the tensor libraries pass through unfiltered.

How the identity triple is resolved#

engine_version is the installed distribution’s version. engine_sha resolves in a fixed order: the TAUOPT_ENGINE_SHA environment variable when it is set to a non-empty value; else the tauopt._build_info module that python/hatch_build.py stamps into every wheel and sdist at build time (the commit the distribution was built from, so a plain pip install of a built wheel carries the real sha); else git rev-parse HEAD when the installed provenance.py sits inside a checkout of this repository at its source path (a source checkout or an editable install pip install -e "./python[all]", which carries no stamped module); else the literal string unknown. The git leg checks the checkout’s layout, so a wheel installed into a venv that lives under some other git repository reads its own stamp and never that repository’s HEAD. unknown therefore means a build in which no sha source was available (no TAUOPT_ENGINE_SHA, no git metadata, no prior stamp: a source tree copied without its .git, or an older build) installed outside any checkout; TAUOPT_ENGINE_SHA=$(git rev-parse HEAD) exported before the run overrides every leg. Two receipts that both read unknown agree on the sha without saying anything about the build.

deps_fingerprint hashes the name==version lines of nine distributions, in this order: tauopt, numpy, scipy, torch, botorch, gpytorch, linear_operator, pydantic, and httpx (a missing one hashes as absent). The hash is one-way, so store the lines themselves next to any receipt you intend to replay. The engine exposes the list; the snippet prints the set behind every fingerprint on this page, then shows the override winning:

import os
from importlib import metadata

from tauopt.engine.provenance import _FINGERPRINT_DISTS, deps_fingerprint, engine_sha

for dist in _FINGERPRINT_DISTS:  # the engine's own list
    try:
        print(f"{dist}=={metadata.version(dist)}")
    except metadata.PackageNotFoundError:
        print(f"{dist}==absent")
print(deps_fingerprint())
print(engine_sha() == "unknown")  # False in a checkout or an editable install
os.environ["TAUOPT_ENGINE_SHA"] = "build-1234"
print(engine_sha())  # the declared value wins
tauopt==0.4.0
numpy==2.4.6
scipy==1.18.0
torch==2.12.1+cpu
botorch==0.18.1
gpytorch==1.15.2
linear_operator==0.6.1
pydantic==2.13.4
httpx==0.28.1
env_blake2b_ad8a0b2c3ff0d12d
False
build-1234

TAUOPT_DEPS_FINGERPRINT overrides the fingerprint the same way TAUOPT_ENGINE_SHA overrides the sha (a non-empty value wins; an empty one is ignored). It exists for service builds, which stamp a hash of their lock file into the image, so a hosted receipt’s fingerprint is a build stamp rather than a hash of installed versions: it never equals a fingerprint computed on your machine, however closely the versions match. Replaying a hosted suggestion therefore means re-asking the service with the same recorded state and config; a local replay of a hosted receipt would need that build’s dependency set installed, which the receipt does not carry.

What cross-process determinism rests on#

The contract is that engine version, engine build, dependency fingerprint, config hash, state, and seed together fix the suggestion. Three mechanisms make it hold.

Seeds never come from process state. An explicit seed wins verbatim. Otherwise the seed derives, through a BLAKE2b hash, from the strategy, its params, and a scope hash: the space hash for sobol, so its scramble stays fixed and the sequence continues across asks, and the full state hash for the point strategies, so successive asks differ. Top-level config flags never feed the seed, so turning dedup or a timebox mode on cannot move the underlying stream.

The GP rungs pin their compute: single-threaded, double precision, under a forked random-number stream seeded from the resolved seed, so load on the machine cannot change a fit. And the fingerprint names the dependency set because minor bumps of the tensor libraries move numerics; a matching version and sha with a different fingerprint may be a different computation (two of the nine distributions, pydantic and httpx, are wire-layer packages whose bumps move the fingerprint without moving any number).

import json

from tauopt import ExperimentState, OptimizerConfig, Parameter, Space, ask
from tauopt.engine import Objective, ObservationRecord

space = Space(parameters=(
    Parameter(name="x", type="double", bounds=(0.0, 1.0)),
    Parameter(name="k", type="integer", bounds=(1, 5)),
))
state = ExperimentState(space=space, objective=Objective(metric="score", goal="max"))
for i, k in enumerate((1, 3, 5, 2, 4)):
    state = state.with_observation(ObservationRecord(assignments={"x": i / 5, "k": k}, value=float(i % 3)))
config = OptimizerConfig(strategy="gp_ei")
result = ask(state, config=config)
p = result.provenance
print(json.dumps({
    "drafts": [d.assignments for d in result.drafts],
    "strategy": p.strategy, "seed": p.seed, "config_hash": p.config_hash,
    "state_hash": state.state_hash(), "deps": p.deps_fingerprint,
}, sort_keys=True))
{"config_hash": "blake2b_9d65974ae3d0347b", "deps": "env_blake2b_ad8a0b2c3ff0d12d", "drafts": [{"k": 5, "x": 0.0}], "seed": 1021186392863887800, "state_hash": "blake2b_8517303f875f0e7e", "strategy": "gp_ei"}

A second, separate process printed the same line byte for byte. The premise is the same engine and the same dependency set. The fitted-parameter digests in fits cover the case where that premise fails quietly: two replicas with different fitted state disagree on the receipt instead of agreeing by accident. The benchmark suite observes this over every cell of a campaign: The benchmark suite.

Timebox modes and purity#

The config field timebox_mode sets how a GP rung relates to the clock.

  • budget (the default, also what an omitted field means at execution) gives each GP rung a cooperative wall-clock budget. An overrun descends the ladder, so which rung answers can depend on machine load. The receipt records the executed rung and seed; what is not reproducible under a binding budget is the choice of rung and, when the fit itself is cut short, the fitted hyperparameters of the executed rung; a non-binding timebox, off, or caps re-derive exactly.

  • off installs no timeout machinery (when no ask-level max_wait_s is set; a request deadline re-arms a wall budget on the executed rung); with no request deadline, fits and optimizations run to convergence, and no clock read can influence the answer.

  • caps keeps off’s clock-free structure and adds deterministic work ceilings (fit_maxiter, acq_maxiter). It is accepted at the top level only, because a params-form setting would not survive a ladder descent.

off and caps are the pure modes: the answer is independent of wall time. An ask-level max_wait_s bounds the executed rung under budget and off. Under caps it never shapes the computation: the ask refuses at a rung boundary or at the single delivery decision after the work completes, with AskDeadlineExceeded, and the computed drafts (candidate points) are discarded rather than substituted. Availability may depend on the clock; the answer never does. The explicit budget value hashes differently from the omitted default even though it executes the same way:

from tauopt import OptimizerConfig

base = OptimizerConfig(strategy="gp_ei")
print(base.canonical_json(), base.config_hash())
for mode in ("budget", "off", "caps"):
    cfg = OptimizerConfig(strategy="gp_ei", timebox_mode=mode)
    print(mode, cfg.config_hash(), cfg.config_hash() == base.config_hash())
seeded = OptimizerConfig(strategy="gp_ei", seed=7)
print(seeded.canonical_json(), seeded.config_hash())
{"params":{},"seed":null,"strategy":"gp_ei"} blake2b_9d65974ae3d0347b
budget blake2b_218b394836e2ee5f False
off blake2b_5941d0f3ef8511cc False
caps blake2b_85436d8936578d44 False
{"params":{},"seed":7,"strategy":"gp_ei"} blake2b_2e698b2aceb47132

The canonical form behind config_hash keeps three fields always: strategy, params (as {} when empty), and seed (as null when unset), as the first line shows. The seven later fields drop out at their defaults, center_in_bootstrap at false, dedup at true, duplicate_intents, queue_selection, categorical_mode, and timebox_mode at null, and base_noise at 1e-3, and serialize at any other value, which is why the explicit budget hashes differently from the omitted field.

What a replay needs#

The receipt identifies the run; it does not carry the state. A replay needs the recorded experiment state as it stood at the ask (observation history plus open suggestions, in canonical form), the config behind config_hash, the seed, the ask’s own intents when it carried any, and an engine whose version, sha, and fingerprint match (two unknown shas match without proving anything; see the resolution above). With a descended rung, replay the rung named in strategy rather than the one in fallback_from.

Do it: Replay a suggestion.