Skeleton first, domain second
After the first architecture decision, the temptation is to jump straight into the first feature.
Player management sounds tangible. So does a tournament list. A drag-and-drop group assignment would even be visible enough to create a tangible sense of early progress. So you could just say:
Build me the first version of the tournament app.
But that’s precisely the path I don’t want to take in this project.
The first step after the architecture decision isn’t the first feature. The first step is a technical skeleton.
A skeleton.
Not because skeletons are especially exciting. But because they reduce friction before that friction gets expensive.
Preamble: AI-generated code, human review
Section titled “Preamble: AI-generated code, human review”This field-report series follows a small, real software project: a tournament app for a club. At the same time, it’s an experiment in AI-assisted software development.
All the code in this project is deliberately generated by coding agents. My role isn’t to write every line myself, but to own the domain direction, the architecture, the prompts, the reviews, and the corrections.
The goal isn’t to pretend you can just tell an agent, “Build an app” — and end up with a clean product.
In practice, it usually doesn’t.
Good results don’t come from magic prompts. They come from clear tasks, tight boundaries, suitable context files, repeated review, and deliberate steering.
This series is therefore about two levels:
-
The product A small tournament app that lets a club manage players, prepare tournaments, form groups, record results, and track tournament standings.
-
The way of working How far can you get today with AI-generated code? Where does architecture drift come from? What mistakes should you expect? How precise do prompts need to be? What role do agent files, project conventions, and review loops play?
A recurring theme will be architecture drift.
Coding agents are good at picking up existing patterns. But they interpret gaps. If a task is imprecise, you quickly end up with invented folder structures, unrequested abstractions, premature features, or architecture decisions that were never actually made.
That’s why the prompts in this project are deliberately kept small, verifiable, and bounded.
Not:
Build the complete tournament app.But rather:
Only set up the technical skeleton:Angular app, API, service, database, Docker Compose, and preview deployment.No domain logic.Prompts like these are less spectacular, but much more controllable. They reduce room for interpretation and make results reviewable.
Agent files play a particular role here. They document conventions, architecture boundaries, project decisions, and recurring rules directly in the agent’s working context. They don’t replace review, but they reduce friction. The agent has to guess less, and I have to correct less.
Why not go straight to the domain?
Section titled “Why not go straight to the domain?”A new project starts out with a lot of open questions.
Not only domain questions, but technical ones too:
- What’s the project called?
- Where does the Angular app live?
- Is it a classic app, or a microfrontend after all?
- Where do domain, model, and presentation code live?
- How does the API start?
- How does the API reach the service?
- Is there a database?
- Is there RabbitMQ?
- What does Docker Compose look like?
- How does preview work?
- Which deployables need to go into the matrix?
- Which checks need to be green?
If these questions only get resolved once the first feature is built, two things that are better kept apart get mixed together:
- Can the project actually work reliably on a technical level?
- Does the feature solve the right problem from a domain perspective?
Answering both at the same time is possible. But it increases friction.
If Docker, routing, API communication, CI, preview deployment, and folder structure all take shape at the same time as the first player CRUD, every review gets blurry. Is the bug a domain issue now? Is it an infrastructure problem? Did the agent misunderstand the architecture? Or is an environment variable just missing?
A skeleton forces these questions to be answered earlier.
It doesn’t answer any domain question yet. But it makes sure that later domain work starts from a more stable baseline.
A good skeleton proves operability, not domain functionality.
Reducing friction before more people get involved
Section titled “Reducing friction before more people get involved”The skeleton isn’t just helpful for the coding agent. It’s also a step that prepares for later collaboration.
As long as only one person is experimenting, a lot of it can stay in your head. You somehow know which app is meant. You know which compose command currently fits. You know which folders matter and which ones only exist temporarily.
As soon as more people, reviews, or more agents get involved, that implicit knowledge isn’t enough anymore.
Then the project needs a clear starting line:
- How do you start it locally?
- How does it get built?
- How does it get deployed?
- Which containers belong to the system?
- Which paths are part of the convention?
- Which architecture decisions have already been made?
- What explicitly doesn’t belong in this step?
- Which checks prove that the current state is usable?
The skeleton reduces exactly this friction.
It turns an idea into a project you can actually walk into.
Not finished. Not valuable from a domain standpoint. But accessible.
That’s a difference.
What the skeleton is supposed to deliver
Section titled “What the skeleton is supposed to deliver”For this tournament app, the goal of the skeleton step was deliberately technical:
Angular App ↓API Gateway ↓Service ↓PostgreSQLOn top of that come the infrastructure building blocks that get used in my architecture lab anyway:
DockerDocker ComposePreview DeploymentRabbitMQCI/CDDeployables MatrixSo the task wasn’t:
Build players, tournaments, and results.
But:
Set up a deployable project skeleton that can later hold domain functionality.
Concretely, this was supposed to come out of it:
- a classic Angular standalone app
- no microfrontend
- no Module Federation
- a NestJS API gateway
- a NestJS service
- its own database
- a preview configuration
- Dockerfiles and compose files
- entries in the deployables infrastructure
- minimal health/hello endpoints
- prepared libraries for
domain,model, andpresentation
That doesn’t make a tournament possible yet. But the system now exists as a runnable whole.
What explicitly doesn’t belong in the skeleton
Section titled “What explicitly doesn’t belong in the skeleton”At least as important as the positive scope is the negative scope.
The agent was explicitly not supposed to build any domain logic.
Not included:
- no player management
- no tournament management
- no group assignment
- no drag and drop
- no schedule generator
- no result recording
- no standings calculation
- no parent live view
- no detailed auth/role logic
That might sound pedantic. It isn’t, though.
Coding agents are trained to be helpful. If you leave them room, they fill it. “Set up a structure” quickly turns into “I already added a few components, services, models, example routes, and some domain logic.”
That’s well-intentioned. But it isn’t always good.
For this step, I didn’t want half a tournament app. I wanted a skeleton I could review cleanly.
A skeleton isn’t half a feature.
Prompt Light: small tasks, hard boundaries
Section titled “Prompt Light: small tasks, hard boundaries”The term “Prompt Light” doesn’t describe some magic formula to me. It describes more of a way of working:
Small, precise, verifiable tasks instead of big wish lists.
A good prompt for a coding agent shouldn’t just say what’s supposed to be built. It should also say what isn’t.
For the skeleton step, the task therefore had several clear parts.
1. Target state
Section titled “1. Target state”The prompt describes the result first, not just the activity.
Goal is a deployable infrastructure/scaffold skeleton.The preview app should be deployable.The containers should start cleanly.That makes success verifiable.
Not subjective, like:
Build a good foundation.But concrete:
App starts.API responds.Service starts.Database runs.Preview is deployable.Docker Compose is valid.2. Architecture decision
Section titled “2. Architecture decision”The previous decision gets repeated:
No microfrontend should result.The frontend is a classic Angular standalone app.This matters because microfrontend and SCS patterns already exist in the architecture lab. Out of habit, an agent could reuse exactly those patterns, even though they were deliberately ruled out for this project.
Context is helpful. But context can also pull in the wrong direction.
3. Positive scope
Section titled “3. Positive scope”The prompt explicitly lists the expected building blocks:
apps/tournamentapps/tournament-apiapps/tournament-service
libs/tournament/domainlibs/tournament/modellibs/tournament/presentationThis way, the agent doesn’t have to guess roughly what the target structure might be called.
4. Negative scope
Section titled “4. Negative scope”The prompt rules things out:
Please explicitly do not implement any domain logic.And then more concretely:
No player management.No tournament management.No group assignment.No drag and drop.No scheduling.No result logic.No parent view.Negative requirements aren’t pedantry. They’re guardrails.
5. References in the repository
Section titled “5. References in the repository”The agent shouldn’t use some random standard from the internet, but check existing patterns in the repository:
Orient yourself on existing stacks like todos, tasks, or members-directory.This raises the likelihood that Docker, Compose, CI, and the project structure fit the existing system.
6. Verification criteria
Section titled “6. Verification criteria”At the end of the prompt are checks:
FormatBuildDocker Compose configDocker build targetsPreview composeDeployables matrix completeThe agent isn’t just supposed to produce code, but also show whether the result actually holds up.
7. Reporting obligation
Section titled “7. Reporting obligation”At the end, the agent is supposed to summarize:
- which apps were created
- which libraries were created
- which Docker/compose files were changed
- which CI/matrix files were changed
- which checks succeeded
- which points deliberately remain open
That sounds like formalism. In practice, it’s a review aid.
A clean closing report quickly shows whether the agent understood the task or veered off somewhere.
Agent files as project memory
Section titled “Agent files as project memory”Prompts matter. But prompts alone aren’t enough.
If every prompt has to repeat all the architecture rules, naming conventions, project boundaries, and past decisions, the work becomes tedious. It also raises the likelihood that important rules get forgotten.
That’s where agent files help.
They’re not a substitute for architectural thinking. But they’re a project memory for the agent.
They can contain things like:
This project uses Angular Standalone.The app stays thin.Presentation code lives in libs/tournament/presentation.Models live in libs/tournament/model.Domain-adjacent frontend logic lives in libs/tournament/domain.No microfrontend for this project.No Module Federation.Backend starts as a deployment monolith.Agent files reduce friction because they pull recurring context out of the individual prompt.
That makes the individual prompt smaller and more focused.
Instead of explaining the entire project philosophy every time, the prompt can just say:
Follow the existing agent files and just implement this next step.
Review is still necessary, of course. An agent file doesn’t prevent any mistake. But it makes some mistakes less likely.
Result of the skeleton step
Section titled “Result of the skeleton step”By the end of this infrastructure step, no player had been created yet. There was no tournament. No groups. No pairings. No results. No standings.
Still, the step was successful.
All the required Docker systems were running:
tournamenttournament-apitournament-servicepreview-dbpreview-rabbitThat completed the technical vertical slice.
The application no longer existed only as a folder structure in the repository, but as a running preview system.
That’s unspectacular. And at the same time, that’s exactly the point.
Because new projects often don’t fail at the first domain rule. They fail on friction:
- Why doesn’t the app start?
- Which Node version is expected?
- Why can’t the API reach the service?
- Which compose file is relevant?
- Why are there different URLs locally than in preview?
- Why is a matrix entry missing?
- Why doesn’t a Docker image get built?
- Why does it run on my machine but not in the deployment?
The skeleton step answers these questions early.
Not perfectly. But early enough that they don’t end up blocking every feature later.
What I review in this result
Section titled “What I review in this result”A running preview system is good. But it doesn’t replace review.
Especially with AI-generated code, you have to check whether the agent built something that runs, while shifting the architecture along the way.
Typical review questions at this step:
- Did Module Federation really not get introduced?
- Is the Angular app a classic standalone app?
- Does the app stay thin?
- Does presentation code live in the right library?
- Were domain, model, and presentation cleanly separated?
- Are Docker and compose names consistent?
- Are there any hardcoded production URLs?
- Are preview configurations actually suitable for preview deployments?
- Were existing conventions adopted?
- Did any unnecessary features get introduced?
- Are the deployables complete in the matrix?
- Do the containers start only by chance, or reproducibly?
The goal isn’t to trust the agent blindly. The goal is to break the work down far enough that review stays realistic at all.
The bigger the prompt, the bigger the diff.
The bigger the diff, the harder the review.
The harder the review, the more easily architecture drift slips through.
Why this helps later features
Section titled “Why this helps later features”After the skeleton, domain work can be broken into smaller tasks.
Instead of saying, going forward:
Build the tournament app.the next task can read:
Implement player management within the existing structure.Use libs/tournament/model for models.Use libs/tournament/presentation for UI.Use the existing API/service structure.No tournament logic.Or:
Implement only the tournament list.No group assignment.No result logic.No schedule generation.That’s the real payoff.
The skeleton makes later prompts smaller, because the fundamental questions are already answered.
The project structure exists. The containers run. The preview is up. The names are set. The boundaries are described.
Now domain functionality can be added without dragging infrastructure along every time.
Conclusion
Section titled “Conclusion”At first glance, the skeleton step looks like preparatory work. A bit of folder structure, a bit of Docker, a bit of CI, a few placeholder endpoints.
But in an AI-assisted project, this exact step is decisive.
It reduces room for interpretation. It makes results verifiable. It lowers friction for later collaboration. It separates technical soundness from domain implementation. And it makes sure coding agents don’t have to guess architecture, infrastructure, and product logic all at the same time.
The first task after an architecture decision therefore shouldn’t be the first feature.
It should prove that this project works as a project.
Skeleton first. Domain second.