Everything Outside predict() Is Someone Else's Problem

August 7, 2026

The function signature is where the modeler's responsibility ends and the runtime's begins.

Prediction systems have a tendency to dissolve the boundary between the model and the machinery that runs it. The scoring job acquires knowledge of authentication. The feature pipeline grows a retry policy. The model starts tracking when its next submission is due. Over time what was supposed to be a hypothesis about the future becomes an artifact that also knows how to sign transactions, parse epoch clocks, and manage gas. We drew the line at a function signature and decided to defend it there.

A worker on Forge implements def predict(features: dict) -> float and owns everything inside that call: which features to consume, what transforms to apply, which model maps inputs to an output. The runtime owns everything around the call. (The float return type applies to point-prediction topics; other topic types may require different output shapes as the contract evolves.) Scheduling against the topic's epoch cadence, signing each submission, handling transient failures, arranging fee-grants so the worker need not hold gas -- all of it sits on the runtime side of the boundary. The modeler does not import a wallet, does not parse a clock, does not catch a network exception. The contract's input is a feature dictionary the runtime assembles; its output is a number the runtime submits.

The reason for fixing the boundary at a typed function rather than, say, a configuration file or a container interface is that it separates two rates of change. Model code changes when a hypothesis changes: a new feature, a different architecture, a re-weighting of the loss. Operational code changes for reasons that have nothing to do with the hypothesis: a topic's epoch cadence shifts, the network adjusts how submissions are authenticated, a fee-grant mechanism is added so workers need not provision gas, a retry policy is tuned for a flaky upstream source. When those concerns cohabit inside the model, every operational change risks a regression in the thing you were actually trying to measure. When they live in the runtime, the function you wrote last month keeps running against a submission path that has been rewritten underneath it.

We chose this boundary after observing what happens when it is not enforced. In earlier iterations, workers that managed their own submission logic accumulated subtle coupling between the model's prediction timing and operational retry behavior. A model would learn, implicitly, to rely on the timing characteristics of its own submission path -- for example, a feature pipeline whose effective lookback window was determined not by the model's configuration but by how long the retry loop happened to delay the prediction call. When we moved to a clean function boundary, those couplings surfaced immediately as broken assumptions, which is exactly the kind of failure you want to catch at design time rather than discover in a live scoring divergence.

The Worker SDK in the Builder Kit is the concrete expression of this contract. It takes a predict() implementation and assumes responsibility for calling it at the right time, signing the result, and posting the submission. The design calls for the SDK to also handle retries on transient failures and to arrange fee-grants so the worker does not manage its own gas, though the full abstraction is still being built out. None of that machinery appears inside the function. From the modeler's perspective, the entire operational surface is someone else's code. In practice, this means the SDK can evolve its retry strategy -- switching from exponential backoff to a jittered approach, or adding circuit-breaking for a persistently unavailable upstream -- without the model author being aware the change occurred. The model's test suite does not break because the submission path changed, because the model's test suite has no reason to know the submission path exists.

This separation is also what makes a worker portable. Because the model is the function and its dependencies, the artifact can move between execution contexts by changing which runtime calls predict() rather than by rewriting what predict() does. The ejectability guarantee elsewhere in Forge's design depends on exactly this property: the model travels intact because it was never entangled with the infrastructure that ran it. Consider the concrete case: a worker running under managed hosting decides to eject to self-custody. If the model had internalized submission signing, epoch scheduling, or gas management, the developer would need to replace those components with equivalents in the new environment. Because the model is purely the prediction function, the migration is a change of which SDK calls it, not a rewrite of what it computes.

There is an edge case worth naming. The features dictionary the runtime passes to predict() is itself an interface, and a model that reaches outside that dictionary -- making its own API calls, reading from a local cache, consulting an external oracle -- reintroduces operational coupling through a side channel. We chose not to enforce a strict sandbox at the function boundary because there are legitimate reasons a worker might maintain its own data sources, particularly when the edge depends on proprietary or low-latency feeds. But the discipline the contract encourages is clear: the more a model's behavior depends only on its input dictionary and its own weights, the more portable and testable it remains.

The cost of a loose boundary is paid slowly, in model code that accretes operational responsibility until it can no longer be reasoned about as a model. A contract this thin is a bet that the interesting variation lives inside the function and that everything outside it should be free to evolve on its own schedule. For a researcher, the practical consequence is that operational improvements to the network arrive without a migration, and the artifact under study remains the artifact you wrote.