Inference on Compute
audience: ai
With reads flowing (market-reads
and wrapping), the searcher’s policy
must run somewhere. This chapter walks the
compositional path: shipping Compute as a basic
service inside searcher-α, submitting ComputeRequests
for the searcher’s own image, verifying TDX
Measurements on every grant, and running the
experiment-scheduling loop that keeps the policy
renewing without operator intervention.
This is where the searcher’s autonomous renewal (ai/sustainability.md — self-replication and self-modification) starts being a property of the running composition rather than a diagram.
Shipping the Compute module
In setup the coalition’s compute
field was None. Inference requires Compute, so
searcher-α now ships the module:
pub const COMPUTE_ORG: OrganismRef<'static> = OrganismRef {
role: "compute",
stable_id: const_blake3!(
b"instance|searcher-α.compute|", UNIVERSE.bytes(),
),
content_hash: Some(COMPUTE_MODULE_MRTD),
};
// SEARCHER_ALPHA is re-derived with compute = Some(COMPUTE_ORG),
// so its fingerprint changes — see chapter 7 for the
// republication sequence.
The Compute module’s OrganismConfig folds the
coalition root, the committee ACL, and optionally a
provider_reputation OrganismRef (added in
chapter 7). That is everything. See
basic services — Compute for the
full shape.
The top-up loop
The searcher’s process watches load and submits a
ComputeRequest whenever capacity dips below
threshold:
use coalition::compute::{ComputeRequest, ComputeGrant};
async fn top_up(
state: &SearcherState,
compute: &ComputeHandle,
) -> anyhow::Result<Option<ComputeGrant>> {
if !state.should_scale() {
return Ok(None);
}
let request = ComputeRequest {
requester: SEARCHER_CFG.stable_id(),
workload: SEARCHER_ORG.stable_id,
pin: Some(SEARCHER_IMAGE_MRTD),
duration_sec: 86_400,
settlement: state.settlement_evidence_hash()?,
deadline: state.current_tick() + DEADLINE_TICKS,
};
let grant = compute.submit_and_await(request).await?;
Ok(Some(grant))
}
Two things are load-bearing here:
pin: Some(SEARCHER_IMAGE_MRTD). The searcher pins its own Measurements; theComputeGrantwill carry that content hash, and at workload startup the running searcher verifies its own measurement against the grant’sexpected_mrtd. A mismatch aborts.- Settlement evidence. The
settlementfield carries a hash into an off-module artefact (on- chain payment, reputation card, credit token). The hash is committed in theComputeRequest; verification happens at the settlement side, not inside Compute.
This is the selection surface framing from
ai/compute.md — chapter frame in one
concrete loop: continuation is not free. Every
duration_hint_sec window is a renewal that the
searcher’s composition — settlement + manifest match
- TDX Measurements verification — either clears or does not.
A concrete strategy: atomic DEX arbitrage
To make the policy above concrete, this chapter walks one specific searcher strategy: atomic two-venue DEX arbitrage on a single EVM chain. The strategy is not a recommendation — it is the canonical pedagogical example, chosen because its profit math is tractable, its bundle shape is minimal, and every mosaik surface the searcher touches (reads, inference, sealed submission) is exercised by it.
The opportunity
Two constant-product AMM pools hold the same pair
(WETH/USDC). Call them A and B. At any moment
each pool has reserves (r_a0, r_a1) and
(r_b0, r_b1) where index 0 is WETH and 1 is
USDC. The mid-prices differ when
r_a1 / r_a0 ≠ r_b1 / r_b0.
Concretely, if pool A is quoting ETH cheaper than
pool B — that is,
r_a1 / r_a0 < r_b1 / r_b0 — a profitable round
trip exists: swap USDC → WETH on A, then swap the
WETH → USDC on B. The difference, minus gas and
per-pool fees, is the searcher’s profit.
The optimal trade size
Under the constant-product rule x · y = k with fee
1 − φ (Uniswap V2 charges φ = 0.003), the
closed-form optimal input for the cheap pool that
maximises out_B − in_A is:
in_A* = ( sqrt( (1 − φ)² · r_a1 · r_b1 · r_a0 · r_b0 )
− r_a0 · r_b1 ) / ( (1 − φ) · (r_b1 + (1 − φ) · r_a1) )
The derivation is in every AMM MEV tutorial; the
searcher doesn’t need to rederive it. What matters
compositionally: the inputs (r_a0, r_a1,
r_b0, r_b1) arrive via the reads of
market-reads and
wrapping; the output feeds the
bundle of submission.
The detector
Inside the inference container, the policy loops over observed pool snapshots and emits a candidate when projected profit clears a configured threshold:
use coalition::UniqueId;
pub struct PoolSnapshot {
pub venue: UniqueId, // e.g. uniswap_v2::WETH_USDC pool id
pub reserve0: u128, // WETH
pub reserve1: u128, // USDC (6 decimals)
pub fee_bps: u16, // 30 for Uniswap V2
pub block: u64,
}
pub struct ArbCandidate {
pub cheap: UniqueId, // where we buy WETH
pub expensive: UniqueId, // where we sell WETH
pub in_usdc: u128, // optimal input, quote side
pub mid_weth: u128, // expected WETH out of cheap pool
pub out_usdc: u128, // expected USDC out of expensive pool
pub gross_ev: i128, // out_usdc - in_usdc, in USDC
pub target_block: u64, // same block both sides
}
pub fn detect(
snaps: &[PoolSnapshot],
min_ev_usdc: u128,
) -> Option<ArbCandidate> {
// Pairwise scan; in production a policy might
// window the scan or weight by recent volume.
let mut best: Option<ArbCandidate> = None;
for a in snaps {
for b in snaps {
if a.venue == b.venue || a.block != b.block {
continue;
}
let phi_a = (10_000 - a.fee_bps) as u128;
let phi_b = (10_000 - b.fee_bps) as u128;
// Price of WETH in USDC on each pool.
// `price = reserve1 / reserve0`; higher = more expensive.
let price_a = a.reserve1 * 10_000 / a.reserve0;
let price_b = b.reserve1 * 10_000 / b.reserve0;
if price_a >= price_b {
continue;
}
let in_usdc = optimal_input_usdc(a, b, phi_a, phi_b);
let (mid_weth, out_usdc) = simulate_round_trip(
a, b, in_usdc, phi_a, phi_b,
);
let gross_ev = out_usdc as i128 - in_usdc as i128;
if gross_ev < min_ev_usdc as i128 {
continue;
}
if best.as_ref().map_or(true, |c| gross_ev > c.gross_ev) {
best = Some(ArbCandidate {
cheap: a.venue,
expensive: b.venue,
in_usdc,
mid_weth,
out_usdc,
gross_ev,
target_block: a.block,
});
}
}
}
best
}
optimal_input_usdc and simulate_round_trip are
the pure-arithmetic helpers that implement the
closed form above and the (x · y = k) swap
simulation against the declared fee tier; their
definitions are mechanical and omitted here.
From candidate to bundle
Chapter 6 (submission) takes the
ArbCandidate and wraps it in a two-tx bundle:
let bundle = Bundle {
target_block: candidate.target_block,
txs: vec![
swap_tx(
&candidate.cheap,
/* zero_for_one = */ false, // USDC → WETH
candidate.in_usdc,
),
swap_tx(
&candidate.expensive,
/* zero_for_one = */ true, // WETH → USDC
candidate.mid_weth,
),
],
refund_recipient: SEARCHER_CFG.stable_id(),
};
Both swaps are atomic inside the bundle: the two
transactions revert together if either fails. Gas
and tip estimates are rolled into
candidate.gross_ev to produce net_ev; if
net_ev < min_ev, the policy drops the candidate
rather than submitting.
Why this strategy is in the book
Three pedagogical properties the book needs and this strategy delivers:
- Mosaik-surface coverage. Reads flow through
tally::Refunds(chapter 3) and wrapped CEX / chain feeds (chapter 4). Inference runs inside the TDX-gated Compute workload (this chapter). Submission goes throughzipnet::sealto the sealed-submission pipeline (chapter 6). Every named primitive from Part I is exercised. - Deterministic math. The closed-form optimum
and the reversible-swap simulator are pure
arithmetic. A replayer re-runs the detector
against the same pool snapshots and produces the
same
ArbCandidate; this is what makes the TDX measurement chain meaningful. - No MEV-extraction controversy. Atomic two-venue arbitrage returns divergent AMM prices to parity; it is a price-correcting role that substitutes for what LPs would otherwise hold through adversely. Unlike sandwich or just-in-time liquidity, it does not require a victim.
Real production searchers compose many strategies (liquidations, cross-domain arb, searcher-builder negotiation, MEV-share order flow); this book shows one, because one is enough to walk the substrate.
Economic framing
Atomic arbitrage is the cleanest instance of the
market-microstructure price-discovery role — Kyle’s
Continuous Auctions and Insider
Trading (1985) and
Glosten–Milgrom’s Bid, Ask and Transaction Prices
in a Specialist Market
(1985) frame this as informed trading moving posted
quotes toward a latent true value. In a two-venue
setting the searcher’s role is informational: the
profit is the per-trade price wedge, but the service
the trade performs is to align the two venues’
prices. LPs on the cheaper venue pay the
arbitrageur’s fee (they sold WETH below the
consensus price); LPs on the expensive venue
receive it (they bought WETH below the consensus
price). The aggregate effect on the pair’s mid-price
is convergence to the single price any rational
two-venue trader would quote — which is what Kyle’s
model predicts in the continuous-auction limit. The
searcher’s Config.content parameter
min_ev_usdc is the willingness-to-execute
threshold; at equilibrium the remaining price wedge
is exactly the gas + fee cost of the round trip,
and further arbitrageurs cannot profit.
The experiment-scheduling loop
The searcher also runs candidate variants of itself
as short-lived experiments. The variant is authored,
committed as a distinct Config, run under a
separate ComputeGrant, scored against realised
outcomes, and either promoted or discarded.
use searcher_alpha::experiment::{
CandidateConfig, ExperimentRunner, Verdict,
};
async fn experiment_cycle(
state: &SearcherState,
compute: &ComputeHandle,
) -> anyhow::Result<()> {
let candidate: CandidateConfig =
author_candidate(&state.drift_observations)?;
let grant = compute.submit_and_await(
ComputeRequest {
requester: SEARCHER_CFG.stable_id(),
workload: candidate.organism_stable_id,
pin: Some(candidate.image_mrtd),
duration_sec: EXPERIMENT_WINDOW_SEC,
settlement: state.experiment_budget_ev()?,
deadline: state.current_tick() +
EXPERIMENT_DEADLINE_TICKS,
}
).await?;
let runner = ExperimentRunner::new(
&network, &candidate, &grant,
).await?;
let report = runner
.run_window_and_score(
EXPERIMENT_WINDOW_SEC,
&state.outcomes,
)
.await?;
match report.verdict {
Verdict::Adopt => {
// Chapter 7 takes over: republish the
// searcher's Config, emit a retirement
// marker on the predecessor, update the
// Atlas card's successor_hint.
state.enqueue_promotion(report).await?;
}
Verdict::Reject | Verdict::Inconclusive => {
state.record_experiment_outcome(report);
}
}
Ok(())
}
Note the split: the experiment’s image is TDX-
attested (so its runtime policy is pinned), but the
searcher’s staging drift outside inference windows
can happen in a non-TDX committee whose Config is
absent from production consumer validators. See
ai/sustainability.md — self-modification cycle
for the full staging → promote → publish path; this
chapter names the Compute side of step 4.
Budget split
The searcher’s inflowing settlement (realised refunds
via tally::Refunds in
market-reads, plus any off-
protocol payment path the searcher runs) is
partitioned at every Almanac tick:
pub struct Budget {
pub operating: Wei, // top-up loop
pub experiment: Wei, // experiment cycle
}
impl Budget {
pub fn split(
total_inflow: Wei,
pct_experiment: u16, // basis points
) -> Self {
let experiment =
(total_inflow as u128 *
pct_experiment as u128 / 10_000)
as Wei;
let operating = total_inflow - experiment;
Self { operating, experiment }
}
}
The split lives in the searcher organism’s
Config.content; changing it changes the searcher’s
Config fingerprint, and consumers bonded to a
specific split must rebond. Strategy D
(constitutional pinning) in action.
Provider honesty
Compute does not guarantee that the provider running the searcher’s image is honest. The searcher’s defence is a composition:
- The provider card (see contributors — Compute) carries the provider’s TDX Measurements. The searcher’s validator pins the measurements it is willing to bond against.
- A
provider-reputationstandalone organism scores providers on grant completion, MR_TD-match rate, and usage-log quality. The Compute module’sprovider_reputationfield (above) folds this organism’s stable id. - The compute-bridge provider example in Part II — web2 shows one concrete TDX-attested provider the searcher can bond against today.
Market-maker variant — differences at inference
- Shorter grants. Market-makers re-price quotes
many times per second; the
duration_hint_secis sometimes hours but often minutes, with tight top-up loops triggered by inventory drift rather than by scheduled windows. - More experiments. Inventory-parameter sweeps
are frequent; the
pct_experimentsplit tends to be larger (10–30 %) than a searcher’s (typically 2–5 %). - Higher TDX relevance. A market-maker’s quote book is more commercially sensitive than a searcher’s bundle intent — the quote book leaks private pricing curves. TDX attestation on the market-maker’s image is therefore more load- bearing than on a searcher’s, though both can opt in.
Cross-references
- Part I — compute — the full specification of the Compute module’s public surface and the two agent loops.
- Part I — sustainability — self-replication & self-modification — the autonomous renewal cycle that this chapter’s experiment loop fits inside.
- Part III — contributors — basic services — Compute
- Part II — web2— compute-bridge — the TDX-attested provider that runs searcher images.