Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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; the ComputeGrant will carry that content hash, and at workload startup the running searcher verifies its own measurement against the grant’s expected_mrtd. A mismatch aborts.
  • Settlement evidence. The settlement field carries a hash into an off-module artefact (on- chain payment, reputation card, credit token). The hash is committed in the ComputeRequest; 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 through zipnet::seal to 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-reputation standalone organism scores providers on grant completion, MR_TD-match rate, and usage-log quality. The Compute module’s provider_reputation field (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_sec is 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_experiment split 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