Skip to content

The first architecture drift

Up to this point, the journey had been surprisingly productive.

An idea for a small tournament app turned into a running technical skeleton. The preview environment starts. Frontend, API gateway, service, database, and RabbitMQ all run. CI’s static quality gates pass. Keycloak is integrated. The API gateway validates JWTs and only accepts tokens meant for the tournament API.

For a young project, that’s not a bad state.

And yet, this is where the first clearly visible architecture drift showed up.

Not a dramatic one. Not one that endangers the project. But one that shows exactly why AI-generated code isn’t simply “done” just because it runs.

The repository already has an established app structure.

Apps don’t sit arbitrarily directly under apps/ — they’re grouped by role:

apps/
├─ apis/
├─ hosts/
├─ remotes/
└─ services/

This structure isn’t just cosmetic. It helps you quickly recognize, in the monorepo, what kind of application you’re looking at.

An API gateway belongs under apps/apis.

A service belongs under apps/services.

Frontend apps or remotes belong in the corresponding frontend area.

After the skeleton and auth steps, however, several new apps ended up directly at the top level:

apps/connect-four-remote
apps/tournament
apps/tournament-api
apps/tournament-service

Functionally, this wasn’t the end of the world at first.

The apps could be built. The containers ran. Preview worked. As a short-term technical vertical slice, it accomplished a lot.

From the perspective of the repository structure, it was still drift.

It matters to me: this isn’t bashing.

Up to this point, the coding agent had worked quickly and effectively. It set up a new project skeleton, prepared Docker and preview structures, integrated Keycloak, and secured the API gateway.

The drift didn’t happen because “AI can’t do anything.”

It happened because the agent learned from the context that was already there.

There was already one app in the repository sitting at the top level. That apparently created a pattern:

A new app can live directly under apps/.

From a human perspective, it was clear: that wasn’t an intended new standard pattern — it was itself an earlier structural drift.

For the agent, that distinction wasn’t necessarily obvious.

And that’s an important point in AI-assisted development:

A coding agent doesn’t automatically recognize which existing patterns are good architecture and which ones are just historical accidents.

It sees context. It sees examples. It imitates.

If a repository contains old compromises, transitional solutions, or drift, those things can turn into apparent conventions.

Architecture drift doesn’t always show up as a broken build, a red test, or a container that won’t start.

Sometimes everything is green.

And the structure shifts anyway.

That’s exactly what makes drift so unpleasant. It’s often not technically wrong, just architecturally imprecise.

In this case, the drift was small:

Apps ended up in the wrong place.

But small shifts like this add up.

Today, one app sits at the root level. Tomorrow, the next prompt orients itself on that. The day after, new paths, new edge cases, new documentation, and new exceptions appear.

Eventually, nobody knows anymore what was intentional and what was accidental.

That’s why it’s worth correcting things like this early.

The fix is conceptually simple:

apps/tournament
→ apps/remotes/tournament
apps/tournament-api
→ apps/apis/tournament-api
apps/tournament-service
→ apps/services/tournament-service
apps/connect-four-remote
→ apps/remotes/connect-four-remote

The point isn’t to change the application’s domain behavior. It’s just to bring the structure back in line with the existing monorepo convention.

But it’s important: in an Nx monorepo, you shouldn’t make moves like this blindly through a file manager.

Paths are referenced in a lot of places:

  • project configurations
  • TypeScript configurations
  • build targets
  • Dockerfiles
  • Docker Compose
  • CI/CD
  • deployables matrix
  • preview configuration
  • imports
  • agent files or documentation

That’s why a move like this is a small architecture fix, not just folder tidying.

Where possible, an Nx move generator should be used. If that doesn’t work cleanly, the paths have to be adjusted manually and checked consistently.

This drift would have been easy to miss, because the result looked functionally fine.

This is exactly where a central task of human review lies.

Not just asking:

Does it run?

But also:

Is it in the right place?
Does it match the conventions?
Is a wrong pattern being established here?
Did the agent adopt something from the context that shouldn't have been a rule at all?

With AI-generated code, review isn’t just bug hunting. It’s architecture maintenance.

The agent can produce code very quickly. But speed amplifies both good and bad patterns.

When the guardrails are clear, that’s an advantage. When the guardrails are blurry, drift builds up faster than you could write it by hand.

I actually find this drift valuable.

It shows early where the way of working needs to be sharpened.

For future prompts, that means:

  • name the existing folder structure explicitly
  • specify not just target apps, but target paths
  • don’t leave old exceptions standing as a model
  • update agent files
  • document architecture decisions in the project context, not just in chat
  • don’t reduce review to build success

The agent didn’t “think wrong.” It plausibly kept following the context it was given.

The task now is to improve that context.

This is exactly where agent files become more important.

If the project context clearly states:

API gateways live under apps/apis.
Services live under apps/services.
Frontend apps live under apps/remotes or an explicitly defined frontend category.
New apps must not be created directly under apps/.

then this rule doesn’t have to be re-explained in every single prompt.

Agent files aren’t a guarantee. But they lower the likelihood that the agent will interpret old drift as a new standard.

The goal isn’t to make every mistake impossible.

The goal is to make recurring mistakes less likely.

Despite this drift, the overall project state is positive.

For the MVP project, the central technical components are now in place:

Frontend app
API gateway
Backend service
Preview database
Preview RabbitMQ

All relevant components are deployable and running in the preview environment.

CI’s static quality gates pass. That means the project isn’t just runnable locally — it’s plugged into the existing quality and deployment process.

The auth foundation is in place too:

Angular login via Keycloak
Bearer token via interceptor
JWT strategy in the API gateway
Guard for protected routes
sub as the stable technical user ID
Audience check for tournament-api

That’s an important milestone.

The system can start. It can be deployed. It can authenticate requests. It has a stable technical user identity. And it’s connected to the existing CI/CD structure.

It’s not a tournament app in the domain sense yet.

But it’s now a solid project skeleton.

The first drift essentially confirms the approach taken so far.

Larger prompts would have been more dangerous. If the agent had built infrastructure, auth, players, tournaments, drag and drop, and scheduling in one step, a structural drift like this probably would have gotten lost among a lot of domain code.

Because of the small steps, it became visible.

That’s the real advantage of Prompt Light:

small task
small diff
clearer review
faster fix

The drift wasn’t bad, because it was caught early.

And it was catchable early, because the task was bounded.

AI-generated development isn’t automatically clean. But it’s also not automatically chaotic.

Up to this point, the coding agent has quickly and effectively built a working project skeleton. That should be acknowledged.

At the same time, this first architecture drift shows why human review remains indispensable.

A build can be green while the architecture still shifts underneath it.

The decisive point isn’t avoiding drift entirely. That’s barely achievable in real projects — with humans or with agents.

The decisive point is catching drift early, keeping it small, and correcting it consistently.

For this project, that means:

The technical foundation is in place. The first drift has been caught. The structure is being corrected. Now domain work can begin.

And that’s exactly how the current state feels right.