Skip to main content
In the functional lane, a harness is a plain async function. Optimizable parameters live outside the function in a Params value, in the spirit of JAX: pure functions over inputs, with a params pytree injected ambiently per call tree. Predictors are addressed by name instead of struct field path, and the same names appear as trace span components.
The fx lane is experimental. The struct world (Predict<S> fields plus Module::forward) remains fully supported; fx is an additional authoring style layered on the same machinery, and the two styles share the trace format and the ModuleState persistence format.

fx::predict

fx::predict::<S>(name, input) is the atomic LM call of the lane: one signature, one named parameter slot, one prediction.
Configuration (instruction override plus demos) comes from the ambient Params; with no scope active, the signature defaults apply. The LM resolves exactly as struct-based Predict calls do, through the globally configured LM. Under a capture() scope the span records name as its component, so traces from functional harnesses are addressable by the same names an optimizer would mutate. Internally, resolved predictors are cached by (signature type, name, config hash), so a hit reuses a fully warmed Predict instead of rebuilding per call. The cache is capped at 1024 entries and cleared when full. If a Params entry does not fit the signature, predict returns a PredictError reporting that the params do not fit.

Params

Params is the optimizable state of a functional harness: named PredictState values keyed by the names passed to predict. Evaluating a different candidate means injecting a different Params value, never mutating a module in place. Because Params round-trips losslessly through ModuleState::save and ModuleState::load, persistence works across both authoring styles.

with_params

Runs a future with params as the ambient parameter set for every predict call inside it. The scope is a tokio task-local, mirroring trace capture: only predict calls on the same task see the params, spawned subtasks do not inherit them, and nesting replaces the outer scope for the inner future. This is what enables concurrent candidate evaluation. The harness function takes nothing by &mut; each candidate is a Params value injected around an otherwise identical call, so different candidates can run on separate tasks at the same time against the same code.

FnModule and fx::module

fx::module(f) wraps an async function as a Module (returning FnModule<I, O, F>), so functional harnesses plug into evaluate_trainset, metrics, optimizers, and every other module consumer. The input and output types must implement Schema and Facet, and the function must return Result<Predicted<O>, PredictError>.

with_overlay

With the ir feature enabled, fx re-exports with_overlay from the IR bridge:
It unbinds an ir::Overlay candidate against a Program into Params, then scopes those params exactly like with_params. This lets IR-level candidates drive functional harnesses without translation code at the call site.

See also