The Stack I Hand My AI Agent
- ai-agents
- developer-tools
- next-js
- workflow
A fixed, opinionated stack handed to an AI coding agent at the start of every session beats letting it pick dependencies each time. Fewer decisions, reusable conventions, compounding familiarity across projects. Here is what I give it and why.
Every time I start a new project with an AI coding agent I hand it the same stack. Not a list of suggestions. A fixed, mandatory configuration the agent is not allowed to deviate from without explicit sign-off. This sounds rigid. It is. That rigidity is the point.
When the agent knows the stack up front, it stops guessing. It reuses patterns it has seen in the same context window, defers to the helpers I have already told it to use, and produces output that slots into the rest of the codebase without friction. When the agent gets to pick the stack, you spend the first three hours litigating whether it chose the right ORM.

The Stack
The choices are not arbitrary. Each one was made after shipping at least two production projects with it and deciding it was good enough to stop reconsidering. That is the bar: not perfect, good enough to stop reconsidering.
- Next.js App Router + TypeScript strict -- server components, server actions, no separate API server for most projects
- Tailwind CSS -- utility classes only, no CSS-in-JS, zero runtime overhead
- MySQL 8 or Postgres 16 via Prisma -- Prisma for the schema, migrations, and type-safe client; MySQL for projects that need it on Ploi, Postgres otherwise
- Better Auth -- session and OAuth in a few lines, the auth handler at one fixed path the agent already knows
- Resend -- transactional email, excellent SDK, does not require a dedicated server
- MinIO -- self-hosted S3-compatible object storage, runs in Docker Compose locally and on Hetzner in production
- Redis + BullMQ -- background jobs with retries, concurrency controls, and a simple job class pattern
- Docker Compose -- one command spins up the database, Redis, and MinIO locally; same image versions in production
- Ploi + Hetzner -- VPS hosting, server management without the ops overhead, predictable cost
- Cloudflare -- DNS, CDN, SSL termination, DDoS protection in front of everything
Why Fixed Beats Flexible
The AI agent is a stateless executor. It does not carry opinion, loyalty, or habit from session to session. Given a blank slate and a vague instruction like "add a background job", it will pick whatever BullMQ alternative it saw most often in its training data. That might be a different abstraction each time. It might introduce a second Redis client. It might conflict with the auth library's session store.
A fixed stack eliminates that class of decision entirely. The agent does not choose the queue library. It uses BullMQ because the AGENTS.md says so and because the base job class already exists in the codebase. The decision was made once, it is written down, and the agent reads it.
The AGENTS.md: Encoding the Stack as Agent Memory
An AI coding agent has no memory between sessions. The agent that configured your BullMQ worker last week has no idea it exists today. Without a mechanism to persist context, every new session starts from zero and the agent reinvents patterns you already settled.
The mechanism is a file. One markdown file at the project root, loaded into the agent context at the start of every session. I call it AGENTS.md. It is not documentation for humans (the README does that). It is working memory for the agent: the stack is fixed, here are the conventions, here are the commands, here are the patterns that are explicitly forbidden.
The AGENTS.md is the single artifact that turns a stateless LLM into a project-aware collaborator. Write it like it has to survive a context window wipe between every session, because it does.
Below is the actual stack section I paste into every new project's AGENTS.md. The agent reads this before touching any code. Substitute your database choice (MySQL vs Postgres), keep everything else fixed.
# AGENTS.md -- Stack & Conventions for AI Coding Agents
## Stack (fixed, do not substitute without explicit approval)
- Framework : Next.js App Router, TypeScript strict mode
- Styles : Tailwind CSS utility classes only -- no CSS-in-JS, no plain CSS files
- Database : MySQL 8 or Postgres 16 via Prisma (schema in prisma/schema.prisma)
- Auth : Better Auth (src/lib/auth.ts) -- do NOT roll a custom session system
- Email : Resend SDK (src/lib/email.ts) -- transactional only, no bulk marketing
- Object store: MinIO, S3-compatible (src/lib/storage.ts)
- Queues : BullMQ backed by Redis (src/queues/) -- background jobs only
- Environment : Docker Compose (docker-compose.yml at repo root) -- run locally with:
docker compose up -d
- Hosting : Ploi on Hetzner CX21+, Cloudflare in front (proxied, SSL full-strict)
## House conventions
- All server actions in src/app/actions/ -- never in route handlers or components
- API routes under src/app/api/v1/ return { data, error } -- use ApiResponse type
- BullMQ jobs extend BaseJob (src/queues/base-job.ts) -- no bare Queue instantiation
- File naming : kebab-case files, PascalCase components, camelCase utils
- No any in TypeScript -- use unknown and narrow explicitly
- No console.log in committed code -- use src/lib/logger.ts
## Commands
npx prisma migrate dev # apply local migrations
npx prisma generate # regenerate client after schema change
docker compose up -d # start MySQL/Postgres, Redis, MinIO
npm run dev # Next.js dev server
npm run typecheck # tsc --noEmit
npm run lint # ESLint + Prettier check
## Known gotchas
- Better Auth requires the auth handler at src/app/api/auth/[...all]/route.ts exactly
- MinIO presigned URLs expire in 1 h by default -- adjust in src/lib/storage.ts
- BullMQ concurrency defaults to 1 per worker -- set explicitly for each queue
- Prisma Client is a singleton -- import from src/lib/prisma.ts, never instantiate inline
Three things make this file effective. First, it is prescriptive: "do NOT roll a custom session system" is not a suggestion. Second, it names the exact file path for each abstraction (src/lib/auth.ts, src/lib/prisma.ts). The agent does not have to go looking. Third, it lists the gotchas: the ones you hit the hard way so the agent does not have to hit them again.
The Local Environment in One Command
Docker Compose is part of the stack for a specific reason: it makes the local environment a first-class artifact the agent can reason about. When the agent reads AGENTS.md and sees that docker compose up -d starts MySQL, Redis, and MinIO, it knows exactly what to tell you when something is not running. It does not guess whether you have a local MySQL install or a cloud database in development.
# docker-compose.yml (excerpt -- services the agent can rely on)
services:
db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: 'root'
MYSQL_DATABASE: 'app'
ports:
- '3306:3306'
volumes:
- db_data:/var/lib/mysql
redis:
image: redis:7-alpine
ports:
- '6379:6379'
minio:
image: minio/minio
command: server /data --console-address ':9001'
environment:
MINIO_ROOT_USER: 'minio'
MINIO_ROOT_PASSWORD: 'minio123'
ports:
- '9000:9000'
- '9001:9001'
volumes:
- minio_data:/data
volumes:
db_data:
minio_data:
The same image versions run locally and in production on Hetzner. The agent knows the connection strings from the environment variables it is told to expect. There is no "works on my machine" surface area.

Compounding Familiarity Across Projects
The real payoff is not the first project. It is the fifth. By the time you have used this stack five times, the AGENTS.md is refined. The gotchas section has caught real bugs. The forbidden patterns section has prevented at least two architectural mistakes. The base job class has been extracted into a shared template you can clone.
Every new project inherits the accumulated discipline of the previous ones. The agent walks in on day one already knowing the patterns that took you two projects to settle. That is not magic. It is just documentation that is actually read.
- Start each project by cloning the AGENTS.md template and adjusting the database choice.
- Update the AGENTS.md whenever you add a new abstraction, ban a pattern, or hit a gotcha.
- Treat the file as a living spec: when the codebase changes, the AGENTS.md changes first.
- Load it explicitly at the start of every agent session -- do not assume the agent will find it.
The Review Gets Faster Too
A secondary benefit worth naming: code review becomes faster when you know the stack. You are not evaluating whether the agent picked a sensible library. You are only evaluating whether it used the agreed library correctly. That is a much narrower question, and it compounds too: the longer you use the stack, the faster you spot a deviation.
The thesis is simple: a fixed stack is a force multiplier for AI-assisted development. The agent knows the tools, you know the tools, the AGENTS.md bridges the sessions. You stop debating infrastructure and start shipping features. That is the whole point.