# ADR-023: `PyBotIo` end-state is a shrunk translator + documented `stays-python` seam; full retirement deferred to the ERC-20/Curve core port (VK3YDM) **Status: accepted (architecture).** Recorded during the `LWKLMP` grilling (2026-08-06), settling decisions D0–D5: the disposition of `PyBotIo` and the Python `builders/` tree once the construction choreography moved core-side. ## Context `PyBotIo` (the `#[pyclass]` I/O façade) was framed in the `ConstructionIo` slice-A migration guide as something that "retires fully once the 27 choreography wrappers move core-side." The wrappers did move core-side (FJQRH3 / F2R2OC / 3FVZF4, epic `Z5CNPB`): every choreography method now delegates to `degenbot_bot::bot_core::pool_builder::{choreography,curve_choreography,builder}` over the `ConstructionIo` trait, and V2/V3/V4/Aerodrome/Balancer pool construction is Rust-delegated via `PyBot.build_*_pool`. Investigation corrected the premise: `PyBotIo` is **not** vestigial. It remains the live I/O executor for genuine Python construction that has no Rust twin yet — the `Erc20Builder` (token construction + balance/allowance/supply reads), `CurvePoolBuilder` (Curve construction + update), `BalancerBuilder` (Balancer `update()`), the `tick_data_fetcher`/`type_resolution` helpers, and `_bot.py`'s own slot0/reserves refresh. Only the V3/V4/Aerodrome Python pool builders are deleted, and `v2_builder_base.py` is dead in `src/`. ## Decision ### D0 — LWKLMP scopes to the defensible end-state, not full retirement. `PyBotIo` is trimmed to a strict `extract → detach → core call → wrap` translator; the already-vestigial Python builder surface is deleted; and the residual `PyBotIo` + `Py*Row` surface is documented **per-method** as `stays-python` under the ADR-005 rubric. Full `PyBotIo` retirement is **out of scope** — it is blocked behind the live ERC-20/Curve/Balancer-update Python construction and is owned by follow-up epic **`VK3YDM`** ("Rust ERC-20 + Curve construction port"). This is a deliberate deviation from the slice-A "retire fully" framing. ### D1 — Delete the non-alloy Python fallback; block-tag support is a VK3YDM trait change. The temporary seam (`call_kw`, `provider.getattr`, and the `provider`/`self.alloy` routing branches in `get_block_number` / `get_block` / `get_block_timestamp` / `get_code` / `get_balance` / `call` / `call_raw`) is deleted; all RPC methods route through the `ConstructionIo` handle as the single path (with the transient `(NoDb, AlloyRpcConstruction)` over `self.alloy` for bare fixtures). The `"latest"` block-tag fallthrough dies with it — `RpcConstruction` has no tag support. Tag support is a core-trait change tracked in `VK3YDM`, not a LWKLMP blocker. Legacy Mock/MagicMock test doubles move onto alloy fixtures. ### D2 — `Py*Row` mirrors are `stays-python` for this epic. `Erc20TokenRow` + the six `db/pool_read.py` classes are live (Python builders read `.id/.address/.decimals/.kind/.token0`). They stay; their retirement folds into `VK3YDM` with their consumers. ### D3 — Delete the inlined `self.alloy` RPC duplication; keep the `alloy_provider()` accessor. The redundant inlined RPC bodies are removed (the `ConstructionIo` handle is the one path), but the `alloy_provider()` accessor + the underlying `Arc` field are retained — external chain-arm wiring builds an `AlloyTickBootstrapRpc` from it, and `required_construction_io()` synthesizes the transient handle over it. ### D4 — The `build_curve_pool` umbrella/Tier-1 gap is VK3YDM's work (closed). `build_curve_pool`/`build_balancer_*` exist in core but were not re-exported from the umbrella and not exposed on `PyBot`; no Rust ERC-20 builder existed. LWKLMP only records the gap so `VK3YDM` closes it. It is now closed: VK3YDM-S2 added a a core Rust ERC-20 builder (`build_erc20_metadata`) + `PyBot.build_erc20_token`, and VK3YDM-S3 flat-re-exported `build_aerodrome_v2`/`build_balancer_*`/`build_curve_pool` from the umbrella (`degenbot` lib.rs) and exposed `PyBot.build_curve_pool` (construction + update already delegated core-side via `build_curve_pool` + `fetch_curve_*`). ### D5 — Delete the vestigial Python surface + fix doc rot. `v2_builder_base.py` (dead in `src/`) is deleted. Stale pointers are corrected: `pool_builder/mod.rs`'s "future, task 3FVZF4" comment; `CONTEXT.md`'s "retires fully once the 27 wrappers move core-side" (now false); and `LWKLMP`'s References claim that builders "receive `&ConstructionIo`" (they receive `PyBotIo`). ## Consequences - A future reader will see `PyBotIo` fully intact and (without this ADR) conclude the "retirement" was abandoned. This ADR states it was a deliberate scope cut, not failure, and points at `VK3YDM`. - `tests/builders/test_pybot_io.py` models the removed three-tier fallback with four dead double classes (`_FactoryCallProvider` / `_Erc20MetadataProvider` / `_AddressArgProvider` / `_V2PoolProvider`) — they are never instantiated; the live tests already run on offline alloy fixtures (where the transient `(NoDb, AlloyRpcConstruction)` handle serves them). D1/D3 remove the path those doubles modeled, so no test change was required (S1). - `_bot.py` remains a hybrid: a delegating shell for the Rust-built families and a `PyBotIo`-driven host for Curve/ERC-20/Balancer-update until `VK3YDM` lands.