Strategy seams — the substrate map and how to add a strategy¶
Companion to ADR-054 (frame evidence seams), ADR-055 (pending-transaction strategy seams), ADR-019 (strategy-vs-engine), ADR-025 (execution strategy), ADR-018 (engine-family trigger, pulled by decision at ADR-055 Phase C), and ADR-057 (the strategy host).
The runbook¶
The step-by-step companion to this map is adding-a-strategy.md: it carries the real signatures, the admission/lane/ledger/hub wiring, the driver partitions, the config-facet additions, and the test-surface pattern.
The two reaction kinds¶
A strategy picks exactly ONE:
Pending-transaction strategies (
PendingTxReactiontrait,degenbot-strategy/src/pending_tx.rs) react to observed mempool transactions. The pending-transaction driver owns the substrate loop: simulate the pending tx (ScratchEvm::replay) → recover pool post-states (extract_pool_post_states) → admit → discover → evaluate → compose → bundle-sim gate → decide → submit.BackrunStrategy(degenbot-strategy/src/backrun_strategy.rs) is the reference implementation.Settled-block strategies react to sealed blocks via the block pump /
StageMachineseam; settlement arbitrage is the only one today. Their product types are deliberately settlement-shaped; generalization is Phase C work (ADR-055 D5).
Adding a pending-transaction strategy — the checklist¶
Write one
PendingTxReactionimpl indegenbot-strategy: youradmitselection (from recovered pool post-states),discover(anchored DFS over the connector index, or otherwise),evaluatepricing,composepayload policy,decidegate. State provisioning is a plane capability, not a private one (ADR-061;adding-a-strategy.md§0.1): the boot resolves aStrategyKitonce per strategy (StrategyKit::resolve) and hands it in, so a strategy composeskit.provision.ingress/kit.discoveryand never an ingress it built itself. Tick maps enter the sandbox only through the sealedTickMapSeedboundary — Db/Chain seeds are minted insidedegenbot-substrate::pool_ingressalone, and replay admission is an ingress operation; strategies do not construct a seed or register directly into the workspace. Verification is a typedVerifyLevelpolicy (defaultbootstrap) on the provisioning cell; the Tracked intake reconciliation and the two-stamp clock are unconditional integrity, never gated by the knob. Replay, journal extraction, the pathfinding primitive, the sim executor, liveness, and the submission channel are provided.V4 works out of the box: the journal extracts V4 post-states for pools whose identities your descriptors carry (
V4PoolSet), andPoolIngress::admit_v4_replayowns staging, backfill, bitmap merge, verification, and registration. The full-map verifier targets thePoolManagerandPoolId;StateViewis optional scalar/bootstrap configuration, not a verification target.Declare your strategy facet in the typed config (each
strategy.<name>facet’sactivekey is its activation — one declaration site).Choose your
SubmissionTarget(Bundle/Public).Register your launcher/console row so the operator can select the strategy explicitly.
Loud-abort rule (ADR-055 D4): if your strategy asks the substrate for a pool family it cannot serve, that surfaces as a loud typed failure immediately — silent skips are reserved for transient I/O failure, never for capability gaps.
The strategy host (Phase C, landed)¶
The dynamic strategy host landed 2026-09-19
(ADR-057): StrategyHost
(degenbot-bot/src/strategy_host.rs) owns the hub, the boot-snapshot
RouteRegistry, and the NonceAuthority
(degenbot-bot/src/nonce_authority.rs), and drivers attach through the bound
HostHub pair (EngineDriver::from_stages_with_hub,
degenbot-bot/src/arb_engine/driver.rs). The per-strategy submission ledger,
NonceLane, and the default HeadPolicy live in
degenbot-submission/src/submission_ledger.rs; the backrun arm is a
registrable driver (degenbot-strategy/src/backrun_driver.rs); and the
operator drives admission through the engine adapter’s enable_strategy /
disable_strategy / strategies verbs. The driver FSM is
Registered → Enabled → Running → {Halted, Disabled} with terminal tombstones,
and nonces are leased at sign time (lowest-free, contiguous above the confirmed
chain nonce).
Forward-looking statements reserved to the deferred work: shadow-feedback between drivers (reconciliation observes the nonce-level shadow and acts on nothing) and more than one outstanding nonce lease per strategy. Both are post-v1 revisits, as are process-level submission arbitration and lane hot registration.
Adding a settled-block strategy — on-demand engine generalization¶
The pump/stage seam and its payload typing are settlement-specific today by
design (a sample of one). When a second settled-block strategy exists, the
ADR-018-named extraction runs: parameterized stage payloads, a generic
EngineDriver, per-family fleet globals. The strategy host is landed and can
run such a driver; the payload generalization is not pulled by a sample of one,
so it stays on-demand.
Phase B landed (2026-09-19): degenbot-eventhub owns per-process intake
fan-out with declared overflow policies (OverflowPolicy); the backrun feed
ring and head watch subscribe through it (B1/B2), both engine→driver channels
are hub-registered UnboundedFlagged sources with observable depth via
Hub::named_pending (B3), the gated serving seam was retired (B4, ADR-056),
and the boot-snapshot RouteRegistry answers pool membership for strategies
(B5). A second pending transaction strategy now implements
PendingTxReaction and subscribes; no intake wiring. The Phase C runtime host
now lands on top of it (ADR-057).
Where the seams are (exact owners)¶
Seam |
Lives at |
|---|---|
Strategy plane ( |
|
Concrete compositions ( |
|
|
|
|
|
|
|
Pending-tx driver (replay/extract/stages/gate) |
|
|
|
Journal extraction (V2/V3/V4) |
|
Sandbox + |
|
Strategy facets (activation + knobs) |
|
Strategy host (hub + registry + authority + FSM) |
|
Nonce authority |
|
Hub hoist / driver attach |
|
Submission ledger + |
|
Registrable backrun driver |
|
Settled-block seam today |
|