Monday, 29 June 2026

Whoever Sets the Default Sets the Architecture

In my last post I asked whether AI would keep us stuck in 2020 architectures. I argued that the model has no opinion about which architecture is better. What it has is a confidence gradient, shaped entirely by how well-specified each option is, and so the fix is to make new patterns legible: document them thoroughly enough that an assistant can follow them even though it has never seen ten thousand examples.

I still believe that. But I've kept turning the argument over, and I've become convinced the conclusion is less comfortable than “so write better docs.” If I take my own premise seriously, it leads somewhere with real consequences for who actually gets to decide what software looks like from here. Three steps get there.

“Just write better docs” is a trap door, not an exit

The strongest objection to my last post is deflationary, and I want to meet it head on because I nearly talked myself into it. If documentation is the lever, then document your existing stack better and skip the new framework entirely. Hand the model a thorough rules file for the Spring conventions you already use, and you get AI-legible code without adopting anything new.

It's a fair point, and it's wrong in an instructive way. Notice which direction that lever points. Improving the legibility of the thing that is already winning only entrenches it further. “Write better Spring docs so the AI uses Spring well” doesn't rebut my argument. It is my argument, aimed at the status quo. The deflation collapses into the claim it was meant to puncture. Documentation is never neutral; it always advantages whatever it documents. Pointed backward, it is a force for staying exactly where we are.

The lock-in is to a vintage, not a vendor

I framed last time as new-framework-versus-Spring, with OfficeFloor as the test case. That framing was too small, and it let Spring off the hook unfairly. The same gravity drags Spring's own modern patterns.

Ask an assistant to “add a REST endpoint” with no further steering and you'll reliably get an imperative @RestController, blocking JPA, and quite possibly RestTemplate, a client Spring has effectively retired. You will not, by default, get WebFlux, the functional RouterFunction style, RestClient, or anything touching the AOT and native-image work. Not because those are wrong, and not because Spring failed to document them, but because the corpus's center of mass is older than Spring's current recommendations.

I don't say this to knock Spring. I built Spring Boot in as the base of OfficeFloor v4 precisely because the ecosystem is far too good to replace. The point is that even the incumbent can't easily pull its own users forward. What AI locks us into isn't a vendor at all. It is a vintage, roughly mainstream Java at peak Stack Overflow. Spring-introducing-WebFlux faces a gentler version of the exact headwind OfficeFloor faces. This is not a competitive disadvantage for newcomers; it is a structural tax on novelty itself.

Documentation lifts the ceiling, not the floor

This is the distinction I understated last time, and my own experiment shows it. When I worked with an assistant to convert Spring PetClinic REST over to the OfficeFloor YAML approach, documentation was the bottleneck, and once the docs were good enough the conversion went through. But there was a quieter result I didn't dwell on: left to its own judgement, with no steering, the assistant reached for @RestController every single time.

That's the whole thing in miniature. Documentation does real work, but only one kind: it makes a pattern usable when explicitly asked for. That's the ceiling. It does nothing to the pattern the model reaches for unprompted. That's the floor. The docs are what let the assistant produce something other than a controller once I told it to. They never changed what it volunteered when I didn't. So even a perfectly documented new architecture stays opt-in, competing against a default that is opt-out. Docs let an innovation be chosen. They don't make it get chosen by default, and they don't make it get discovered by anyone who didn't already know to ask. Necessary, but nowhere near sufficient.

And the floor is sinking

Now make it dynamic, which is the part that worries me most. If AI writes a growing share of new code, and that code clusters on the center of mass, then the next training corpus is more concentrated there, and the next model's default is stronger. The patterns a model reaches for unprompted don't merely persist. They compound, as models increasingly train on their own most common output.

That isn't static lock-in. It is a ratchet toward the mean: the space of architectures a model will volunteer narrows over time, generation by generation. The conservative force I identified last time is self-reinforcing. Left alone, “what's common” and “what's good” don't just differ. They actively diverge.

So who sets the default?

If documentation only lifts the ceiling, then the thing that actually decides which architectures survive is whatever sets the floor: the agent's system prompt, the project's rules file, the scaffolding and templates a tool ships with, and underneath all of it, the model's priors. A single line in a rules file can flip the default outright:

# AGENTS.md / rules file
When adding a REST endpoint, define it as a YAML endpoint file
under src/main/resources/officefloor/rest/, one function per step.
Do not generate @RestController classes.

That relocates architectural power in a way worth naming plainly. It used to sit, in theory at least, with “the best idea wins on merit.” It now sits with whoever configures the agents. My “is this idea legible to an AI” was the entry ticket; it only gets you into the room. The harder version of my own point is this: legibility gets you considered; only the default gets you chosen.

What follows, including for me

This changes what I think I owe you as the author of OfficeFloor. The job isn't finished when an assistant can use YAML endpoints because I documented them well. It's finished when an assistant reaches for them without being told, which means the starter should ship with the agent rules and scaffolding that make function injection the default, not just tutorials that make it possible. The battleground is the configuration my users actually load, not the tutorial they may never read.

For teams, the implication is broader: your architectural decisions increasingly live in your agent configuration, not only in your codebase. An unmanaged default is still a decision, a decision to ship 2020. The rules file deserves to be treated as a first-class architectural artifact and reviewed like one.

And for all of us: be a little wary of the ratchet. “The model wrote it this way” is evidence that a pattern is common, not that it is good, and those two properties are now drifting apart on purpose.

AI won't keep us in 2020 by holding opinions. It will keep us there by holding defaults, and defaults are always set by someone. The question I opened isn't really whether my idea is legible. It's who sets the default when nobody is steering, and that turns out to be less a technical question than a political one. I'd rather we decided it on purpose than inherited it by accident. As before, I'd be glad to hear from anyone seeing the same thing, especially anyone who has tried to win the default for a new pattern and found out what it costs.

Sunday, 28 June 2026

Will AI keep us stuck in 2020 architectures?

Every time I sit down with an AI coding assistant, I notice the same thing: it is very good at Spring. Annotations, profiles, @Autowired, the whole call-stack-driven dance of beans wiring into beans. AI has seen twenty years of this. It guesses well, even when it has to infer how a profile-specific bean is going to be selected at runtime. This is because it has seen ten thousand examples of exactly that pattern.

Which raises an uncomfortable question for anyone working on a new architecture: if AI is this fluent in 2020-era patterns, are we as an industry going to stay locked into those patterns simply because that's what the model knows? Is AI a conservative force that quietly drags software architecture backwards to its training data's centre of mass, no matter how good a newer idea might be?

I wanted to find out, using my own project as the test case.

The bet: an explicit index beats an implicit one

OfficeFloor version 4 added a feature I think is genuinely interesting for the AI era: REST endpoints can now be defined in YAML files, sitting alongside your existing Spring Boot code, with the directory structure following the URL structure. A file at greeting.POST.yml defines POST /greeting. A file at greeting/{name}.GET.yml defines GET /greeting/{name}. Inside that file, you compose the small functions that handle the request:

# greeting.POST.yml
validate:
  class: ValidateGreetingLogic
  outputs:
    valid: build
build:
  class: PostGreetingLogic
  next: audit
audit:
  class: AuditGreetingLogic

Each function still gets its dependencies injected by Spring exactly as it always has. OfficeFloor doesn't replace Spring's DI, persistence, security, or actuator setup. What changes is the flow. In a typical @RestController, the order in which validation, business logic, and auditing run is implicit: it lives in the call stack (in if statements and which methods call which other methods). To understand it, you read code. To change it, you read more code, because the wiring isn't written down anywhere as data; it's compiled into control flow.

In the OfficeFloor YAML version, that wiring is the file. Conditional branches, sequencing, error flows: they're declared, not buried. No function in the chain knows about the others. No annotation is describing the relationship from inside a class. The YAML is a complete, readable specification of how the endpoint behaves, sitting right next to the endpoint's own URL path in the directory tree.

This is essentially Function Injection, the same move Dependency Injection (DI) made decades ago, but one level up. DI took "what do I depend on" out of imperative constructor code and made it an explicit, configured, first-class concern. Function Injection takes "what happens next" out of the implicit call stack and makes that explicit and configured too. It's a continuation of the Inversion of Coupling Control idea I've been writing about for years: Dependency Injection only ever solved one slice of the coupling problem. Control flow coupling was always still there, just invisible.

For a human reading the code, this might feel like a wash, maybe even a step backwards.  This is exactly why the industry settled on annotations next to code in the first place; developers wanted the wiring close to the implementation, not off in some separate descriptor. That preference made sense when humans were the primary readers doing the navigating.

But an AI assistant isn't a human reading top to bottom. An AI assistant is trying to find the minimum context needed to make a correct, surgical change, and that's a search and navigation problem, not a stylistic one. A YAML file that names every function in an endpoint's execution path, in order, with explicit conditional branches, is a search index. The AI doesn't need to read the rest of the code base to be confident it has found everything relevant to that endpoint. It opens one small file and the entire behavioural contract of that URL is sitting right there.

That's the theory, anyway. I wanted to know if it would actually hold up against a model that has been trained almost exclusively on the other way of doing things.

The experiment: converting Spring PetClinic REST

To test this for real, rather than on a toy example, I took Spring PetClinic REST, the long-standing reference REST implementation of the PetClinic sample app that the Spring community has used for years, and worked with AI to convert its endpoints over to the OfficeFloor REST YAML approach.

It did not work on the first attempt. It took about five iterations to get a clean conversion, and the bottleneck wasn't OfficeFloor's runtime, and it wasn't really the AI's coding ability either. It was documentation. Each attempt surfaced a gap in the tutorials: some assumption I'd left implicit because it was obvious to me, a place where the YAML schema's possibilities weren't spelled out, an edge case in how a Spring @RestController-style behaviour should map across. I used the AI's confusion as a signal: where it guessed wrong or asked the wrong question, that was exactly where the tutorial needed another paragraph, another example, another explicit rule. Five rounds of "AI gets stuck, tutorial gets fixed, try again" later, the conversion went through cleanly.

I recorded the final working conversion. You can watch it here:


Spring PetClinic REST to OfficeFloor REST YAML

You can also see the resulting changes in the forked repository pull request.

So which is it: does AI lock in 2020 architecture, or not?

Both things turned out to be true, depending on what's actually being asked of the AI.

Where AI defaults to what it knows: left to its own judgement, an AI assistant will reach for Spring conventions, because Spring conventions are the statistically dominant pattern in its training data. If you ask it to "add a REST endpoint" with no further steering, you'll get an @RestController and an @Autowired field, every time. That's not a flaw in the model. It's just what twenty years of public code looks like, averaged.

Where AI happily adopts something new: the moment the new pattern is clearly and completely specified, the model's prior training stopped being an obstacle and became almost irrelevant. AI doesn't need to have seen ten thousand examples of a YAML-driven REST framework to use one correctly. It needs an accurate, complete description of the schema and the conventions, and then it follows that description. The five-iteration process wasn't really "teaching the AI to think differently." It was closing the gaps between what I assumed was obvious and what was actually written down anywhere the AI could read it.

That reframes the original question. The risk isn't that AI is architecturally conservative by nature. The risk is that new architectures rarely come with documentation anywhere near as exhaustive as Spring's, because Spring's documentation had two decades and a vast community writing tutorials, blog posts, Stack Overflow answers, and books about it. A new approach starts that race from zero. If its docs stay thin, AI will keep defaulting to Spring patterns, not out of preference, but because Spring is simply the only option it has enough information about to be confident in.

So the honest answer is: AI won't keep us in 2020 architectures by itself. But it will, by default, if nobody does the work of making the alternative legible to it. The model doesn't have an opinion about which architecture is better. It has a confidence gradient shaped entirely by how well-specified each option is in what it's been able to learn or been given.

The interesting part for framework and tool authors

If this holds generally, and I'd be curious whether others doing similar work see the same thing, it changes the calculus for anyone designing a new way of building software in the AI era.

It used to be that the cost of an explicit, separated configuration artifact (think XML wiring files, or graphical configuration tools) was paid almost entirely by human developers, who found it slower to read and slower to navigate than code-adjacent annotations. That cost was real, and it's a big part of why annotation-driven frameworks like Spring won the last decade.

AI changes that cost calculation. An explicit, structured index, a YAML file that names every function and every transition in an endpoint, located exactly where the URL structure says it should be, costs an AI assistant almost nothing to read and a great deal less to get wrong, because there's no implicit call-stack archaeology required. The structure that used to be a tax on humans is now a gift to the thing increasingly doing a large share of the maintenance work.

But that gift only arrives if someone pays a different tax: writing the documentation thoroughly enough, and unambiguously enough, that an AI assistant can pick up the new pattern from the docs alone, the way it picked up Spring from a decade of incidental exposure. Architecture innovation in the AI era may end up being gated less by "is this a good idea" and more by "is this idea legible to an AI that has never seen it before." That's a genuinely different bar than the one we used to optimise for, and it's one I think is worth more people paying attention to.

If you want to look at the actual schema, the tutorials, or try the conversion yourself, the starting point is the OfficeFloor REST tutorials, and the Spring PetClinic REST source is on GitHub if you want to attempt your own conversion and see where your AI assistant gets stuck. That's usually exactly where the next improvement to the docs needs to go.

OfficeFloor v4: from one graphical file to one YAML file per endpoint

OfficeFloor has always been about Inversion of Coupling Control: not just injecting objects, but injecting the functions and threading too. For a long time, the way you expressed that wiring was graphical, a single configuration file with boxes and lines showing how your functions connected together.

It looked great in a demo. It did not survive contact with a team.

The problem with one big graphical file

A single graphical configuration file is fine when one person is building the application. The moment a second or third developer starts working on the same project, that file becomes a bottleneck. Two developers add two different endpoints, both touch the same file, and now someone has to merge a diagram. Graphical formats don't merge the way text does. There's no good way for a merge tool to reconcile two sets of moved boxes and re-routed lines. You end up resolving it by hand, in the underlying XML, squinting at coordinates and generated identifiers to figure out what actually changed.

That's a hard sell to any team used to git merge just working.

One YAML file per endpoint

OfficeFloor v4 replaces the single graphical file with one YAML file per REST endpoint. The file name itself encodes the HTTP method and the URL path, so the configuration and the routing are the same thing:

src/main/resources/officefloor/rest/
  greeting.GET.yml        →  GET  /greeting
  greeting.POST.yml       →  POST /greeting
  greeting/{name}.GET.yml →  GET  /greeting/{name}

Inside the file, named steps wire functions together, each naming the Java class (and, where needed, the method) that implements it:

validate:
  class: ValidateGreetingLogic
  outputs:
    valid: build

build:
  class: PostGreetingLogic
  next: audit

audit:
  class: AuditGreetingLogic

That's the entire specification for a three step pipeline of validate, build, and audit, with conditional branching and sequential composition both declared right there in the file. None of the three classes knows about the other two. The YAML is the only place that knows the order and the wiring.

Why this works so much better for teams

Each endpoint now lives in its own small text file. Two developers adding two different endpoints touch two different files, so there's nothing to merge. Even when two people do need to touch the same endpoint, it's a small YAML file, not a generated diagram, so a normal text merge actually works. When it doesn't, the conflict is a few readable lines rather than a tangle of graphical coordinates.

It also turns out this same property of small, explicit, readable files is exactly what helps AI coding tools. An endpoint's steps, their order, and their branches are all explicit in one file rather than implied by a call stack. That gives AI tooling (and, frankly, any developer new to the codebase) a direct index into the small, focused functions behind each step, rather than something it has to reconstruct by reading framework conventions.

Built on Spring Boot, not instead of it

This change comes with v4 moving OfficeFloor onto Spring Boot as its base. Add the starter to your existing pom.xml, and you can start declaring endpoints as YAML files alongside your existing @RestController classes, with no migration required. Spring's dependency injection, security, persistence, and actuator configuration are untouched. Spring beans are injected into your step methods exactly as they would be into a controller. OfficeFloor isn't replacing Spring; it's taking over just the wiring of REST endpoints, where the graphical file used to live.

<!-- Add to existing pom.xml -->
<dependency>
  <groupId>net.officefloor.springboot</groupId>
  <artifactId>officefloor-rest-spring-boot-starter</artifactId>
  <version>4.0.2</version>
</dependency>

Where to go next

The tutorials walk through this in detail, starting with Spring REST for the basics of YAML endpoint files, and the Function Injection tutorial for multi step pipelines with conditional branching. From there the tutorials cover validation, exception handling, Spring Security, OpenAPI generation, thread injection, and more, all configured the same way, one YAML file per endpoint.

If you've used OfficeFloor's graphical configuration before, this should feel familiar in spirit and much easier in practice. The functions are still composed, not coded together. They're just composed in a format your team's tools already know how to handle.