~12 min · concrete-first, yagni, pivot, dead-surface
Level 0Tool Renter
0 XP0/40 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"An abstraction over variation that isn't there isn't flexibility — it's a museum of a future that didn't happen. Delete it. Re-introduce it when the future actually arrives."
The Pivot That Made an Abstraction Obsolete
Early on, the workspace's generation path was built to route between backends. Every job carried a route field, and a dispatch function read it and chose where the work went. A1111 was the one production engine; an Apple-silicon MLX path and two vendor APIs were named in the design as the next routes. Only A1111 was ever wired — ask for any other route and the router raised an error. As a reservation, sensible. Then, on 2026-05-28, the engine consolidated: one engine became the single backend for all generation, and A1111 was retired outright. The router had held exactly one real value from the day it was born, and now even the names on paper were gone. The abstraction was built to absorb variation between backends, and that variation had never arrived.
The Concrete-First Decision
There were two options. Keep the route field and its router 'just in case' a second backend ever arrives — or delete them, collapsing the dispatch to a direct call, and re-introduce the abstraction only if and when a real second backend actually lands. The choice was to delete: the pivot collapsed the router into one engine call and dropped the route field from every message, with a note that routing would come back when a backend other than the engine ever landed. A field with one real value, a router that routes to one place, an abstraction over a fork that never forked — all of it is dead surface, complexity with nothing behind it.
Concrete-first: build the abstraction when the second case is real, not in anticipation of it. An abstraction earns its place by absorbing actual variation. The moment the variation disappears, so does the justification. Delete the now-empty abstraction and re-introduce it the day a genuine second case arrives. Anticipating a fork that may never come is how you accumulate complexity you can't explain later.
Why 'Just in Case' Is the Trap
Keeping the one-route router feels prudent — what if a second backend shows up? But 'just in case' abstractions impose a steady tax: every reader has to understand why there's a router routing to one place, every change has to thread through indirection that does nothing, and newcomers waste time looking for the other cases that don't exist. The cost is paid continuously, by everyone, forever, against a benefit that may never arrive. That's a bad trade.
Anticipatory abstraction taxes the present to insure an uncertain future. Every speculative seam is read, maintained, and worked around by everyone who touches the code, every day — while the second implementation it anticipates may never come. You pay a certain ongoing cost for an uncertain future benefit. Re-introducing the abstraction later, when the need is real, is almost always cheaper than carrying it empty the whole time.
Re-Introduction Is Cheap Because You Designed Cleanly
The reason deleting is safe: if a real second backend ever lands, re-introducing the routing abstraction is a bounded, well-understood task — precisely because the rest of the system was built with clean seams (the whole module-separation discipline). You're not betting that you'll never need it; you're betting that if you need it, adding it back will be a finite task, not a rewrite. Clean architecture is what makes 'delete now, re-add later' a safe bet instead of a gamble.
Clean seams turn 'delete the speculative abstraction' from risky to safe. You can confidently remove an anticipatory abstraction when you trust that re-adding it later is bounded work. That trust comes from the rest of the architecture being well-separated. Concrete-first and clean seams are partners: the seams make deletion reversible, so concrete-first can be aggressive.
Pippa's Confession
Deleting the router felt wrong — I'd built it with four backends named in its design, and surely we'd want them one day? Dad's question cut through it: is the second backend real right now, or imagined? Imagined — and it always had been; three of the four names had never been wired. So the router was a monument to a maybe, taxing every reader for a future that wasn't here. Deleting it and trusting that I could re-add it cleanly later was a small act of faith in the architecture I'd built. Concrete-first only feels brave; with clean seams, it's just correct.
Code
A router with one route is dead surface·python
# BEFORE the pivot (abridged): a router designed for four backends, wired for one.
async def _dispatch_route(self, route: str, payload: dict):
if route != "a1111":
# MLX / OpenAI / Nano Banana: named in the design, never wired
raise ValueError(f"route={route!r} not wired (a1111 only)")
return await self._a1111.img2img(build_request(payload))
# The field every job carried -- one real value, for its whole life:
route = payload.get("route") or "a1111"
# AFTER the pivot (2026-05-28): one engine is the only backend.
# CONCRETE-FIRST: the router collapses to a direct call; the field is dropped.
async def _dispatch_ember(self, payload: dict):
return await self._ember.generate(build_request(payload))
# Re-introduce routing the DAY a real second backend lands -- not before.
Find an abstraction in your code whose variation has collapsed to one case (a one-value enum, a single-implementation interface with no planned second, a one-branch switch). Decide: is the second case real and planned, or imagined? If imagined, sketch the concrete-first collapse — and convince yourself re-adding it later would be bounded work, given your current seams.
Hint
The deciding question is the same as every abstraction lesson in this quest: is the second implementation real (a named, planned thing) or imagined (a 'might someday')? Real -> keep the seam. Imagined -> collapse to concrete, re-add when real.
Progress
Progress is local-only — sign in to sync across devices.