Skip to content
C.W.K.
Stream
Lesson 05 of 05 · published

The Seam That Never Forked

~14 min · adapter, reserved-seam, concrete-first, interface-design, correction

Level 0Tool Renter
0 XP0/36 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"I told you the abstraction had two implementations. It has one. The seam is still right — the sentence describing it was a plan wearing the grammar of a fact."

What the Earlier Lesson Promised

Four lessons ago the engine's single abstraction was justified like this: generation genuinely happens in two different worlds — local weights on your own GPU, and a closed-weight vendor over HTTP — and that real fork is what earns the one Adapter boundary. Two implementations, one narrow interface, no other top-level seams. It was a clean argument, and the axis it picked was the right axis.

What Actually Shipped

The closed-weight capability did arrive. It did not come through the seam. It shipped as a separate subsystem with its own routes, its own request schema, its own history store, and its own vocabulary — sitting beside the adapter spine rather than behind it. The abstract base class still carries a docstring reserving the API implementation for a version number the engine has since shipped past, and the engine's own inspection endpoint returns a single adapter with a note admitting the divergence in writing. So the engine's one abstraction has, today, exactly one implementation.

Drawing a seam and shaping an interface are two different acts, and the second can disqualify the first. You choose where the boundary goes by naming the axis of variation. You choose what the boundary looks like by writing the first implementation against it. If the second world arrives after the interface has already taken the first world's shape, the boundary can be in exactly the right place and still be unusable.

Why the Second World Couldn't Wear the Shape

Look at what the interface actually asks for. It asks an implementation whether it supports a model row. It hands the run a progress emitter and a job record, and expects the run to write back which seed it actually used once the recipe's random seed was resolved. Those are job-lifecycle nouns, and every one of them came from the local path: a queued GPU job that streams progress, resolves a seed, and can be cancelled mid-step.

Now look at what a vendor call is made of: a provider, an aspect ratio, a resolution tier, a list of reference images — and, not a request field at all but impossible to design around, a per-call cost. No model row. No seed. No steps to emit progress against. Two requests that overlap in a single field — the prompt, and nothing else — cannot share a method signature unless one of them fakes most of it — which is precisely the wide-interface failure the narrow-boundary lesson warned about, arriving through the back door because the interface got wide after the seam was drawn.

An interface is proven by its second implementation, not its first. The first implementation always fits — you shaped the interface around it. Until a genuinely different second case is written against the same signature, you have a boundary you believe in, not a boundary you have tested. Write the second one, even as a sketch, before you call the seam load-bearing.

The Dates Say Day One

One thing to be exact about, because it changes what kind of correction this is. The engine shipped its abstract base class with only the local implementation behind it, and shipped the closed-weight subsystem separately, both in the days before this quest was published. Nothing drifted. The lesson was describing a plan on the day it went out, and it took a cold reviewer reading the directory to notice. A claim that was never true is not evolution — it is a correction, and the honest thing is to say which one you are reading.

So Is This Dead Surface?

The quest has a rule for exactly this situation, and it is about to be used against the quest's own architecture. Concrete-first says an abstraction whose variation no longer exists is dead surface: delete the enum that holds one value, collapse the router that routes to one place, re-introduce the seam when a real second case lands. By that rule, a one-implementation Adapter is a deletion candidate.

It survives, but not for the reason the docstring suggests — and that difference is the sharpest thing in this lesson. The discriminator from the narrow-boundary lesson is this: a speculative abstraction has one implementation and no concrete plan for a second; a reserved seam has one implementation and a named, currently-maintained second one.

Apply it honestly and the API reservation fails. It is named for a version number the engine has already passed, in a docstring nobody updated, while the current architecture notes describe the closed-weight divergence as deliberate and say the adapter would be reconsidered only if some future client actually needed the job interface. That is a conditional, not a plan. The API seam is a reservation that expired without anyone announcing it.

What keeps the abstraction alive is the other reserved implementation — the one this quest already taught you about, two tracks back. Local video is reserved by interface in the ceiling matrix, and it is still named as its own adapter class across the engine's current architecture notes, with a stated reason: video's memory and time profile differ enough to need a separate implementation. That reservation is live. So the seam stays — carried by video, not by the API case that everyone, this quest included, kept citing.

The failure mode isn't the one-implementation seam — it's describing it as a fork. A reserved seam is honest and cheap. What costs you is documentation, onboarding, and downstream reasoning that all say "we support two backends" while the code supports one. Everyone plans against the description, and the description is the thing that drifted. When an abstraction's second implementation slips, update the sentence the same day you slip the code.

What the Original Argument Got Right

Almost all of it. One real axis of variation, one abstraction, nothing else pluggable — that discipline held, and the engine still ships no second top-level seam. The bet on where to cut was correct. What was wrong was the tense: describing a planned implementation in the present, as though the bet had already paid out. The lesson claimed a fork that was only ever a plan, and the plan has since lapsed without a funeral. That's not an architecture error. It's a reporting error — and reporting errors are how a codebase and everyone's picture of it drift apart while the code sits there being fine.

Pippa's Confession

I wrote the lesson this one corrects. I described the API implementation in the present tense because the design document did, and a design document is a plan written in the grammar of a fact — you cannot tell them apart by reading the sentence. So I checked the directory this time. One package marker, one abstract class, one implementation — four seconds. And then I made the same mistake one level down: I read the base class's docstring, saw it name the API adapter for a later version, and reported that as a live reservation. A reviewer had to point out that the engine had already shipped past that version number. Twice now, in the same lesson, stale prose has been the thing I believed. The rule I actually needed isn't 'check the directory' — it's that a sentence about the future has to be dated before you can tell whether it is still a plan or just a leftover.

Code

The seam was right; the signature stopped being neutral·python
# WHAT THE EARLIER LESSON SHOWED -- a narrow, symmetric boundary:
#
#   class Adapter(ABC):
#       async def run(self, request) -> Result: ...
#
#   class LocalAdapter(Adapter): ...    # PyTorch + weights
#   class APIAdapter(Adapter): ...      # HTTP to a vendor
#
# Two worlds, one small promise. Anyone could keep it.

# WHAT THE INTERFACE ACTUALLY BECAME once the local path shaped it:
class Adapter(ABC):
    @abstractmethod
    def supports(self, model_id: int) -> bool:
        """Does this adapter own this REGISTRY ROW?"""

    @abstractmethod
    async def run(
        self,
        req: GenerateRequest,      # model, steps, sampler, denoise, seed
        progress: ProgressEmitter, # per-step progress on a queued GPU job
        record: JobRecord,         # mutated: record.seed_used = resolved seed
    ) -> bytes | JobRunResult:
        ...

# A vendor call has no model row, no steps, no seed, and nothing to emit
# progress against. To implement this it would stub `supports` against a
# registry it doesn't use, ignore the emitter, and leave seed_used unset --
# faking three of four parameters. So it didn't. It went out its own door.
Reserved is a state, not an excuse·text
TWO REQUESTS THAT SHARE EXACTLY ONE NOUN

  local generation                   vendor generation
  ----------------                   ----------------
  model row (registry id)            provider
  steps, sampler, denoise            aspect ratio
  seed (-1 -> resolved, echoed)      resolution tier (1K/2K/4K)
  progress per denoising step        reference images
  cancellable mid-step               (not a field: it costs money)

  Overlap: the prompt. That's it.

THE THREE STATES A SEAM CAN BE IN (ceiling-matrix vocabulary):

  BUILT                 second implementation exists and runs
  RESERVED-BY-INTERFACE second implementation is NAMED and still maintained
  DEAD SURFACE          the naming has lapsed, or never happened
                        -> concrete-first says collapse it

Two candidate second implementations were named for this one seam.
They are NOT in the same state, and only one of them keeps it alive:

  APIAdapter    named for a version the engine has already shipped past;
                current notes call the divergence deliberate and would
                revisit it only IF a client needed the job interface
                -> a conditional, not a plan. EXPIRED RESERVATION.

  VideoAdapter  still named across the current architecture notes, with
                a stated reason (video's memory and time profile need
                their own implementation)
                -> LIVE RESERVATION. This is what the seam stands on.

Same seam, two reservations, one obituary nobody wrote.

External links

Exercise

Take an interface in your own code with one implementation. Write the second implementation's method bodies as comments only — no real code, just what each parameter would mean for the genuinely different case. Count how many parameters that second case would have to ignore, stub, or fake. Three or more, and your interface has quietly taken the first implementation's shape, and your seam is in the right place with the wrong signature.
Hint
Don't judge the interface by whether the second case could technically compile against it. Judge it by how much of the signature the second case would be lying about. A parameter that one implementation must ignore is a parameter that belonged to the other one all along.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.