Today let’s talk about “Vibe Coding”, the current trend of writing code by chatting with autonomous AI agents.
I see a lot happening around Vibe Coding, from people getting hacked because their site falls apart, to people bootstrapping entire apps in hours instead of weeks.
I’ve been big into vibe-coding myself and wanted to share some tips and tricks on how to get the most value out of your AI agents.
Who am I and why should you even listen to me?
I’ve been working in tech for the past 14 or so years. 10+ in various roles from Engineer, TL, architect, and 3~4+ years as Engineering Manager in charge of teams of ~10 engineers. I’ve built sensitive high-availability systems used by millions of people, handling all kinds of sensitive data such as financial and banking information.
I want to believe that I’m fairly good with coding and designing complex systems, and those skills just happen to apply quite nicely to telling AI agents what to do as well.
Wtf is “Vibe Coding”?
Let’s clear this up first - Vibe Coding roughly translates to: Building apps/websites/features by chatting with an AI agent instead of writing code yourself.
Vibe Coding can be asking an AI agent to do simple edits like “Add a new function that does X to this file”, or complex multi-file features in the lines of “Add a new button to this page that does X”.
In a very rough sense, Vibe Coding is similar to asking an engineer to do something and assigning them a task: The engineer has to figure out what to do, where the files are and how to accomplish the task.
The problem with Vibe Coding: Context
Because of how agents work, each task is effectively similar to a new engineer that just joined your team: You have to teach them everything they need to know about your project to onboard them.
Sure, the engineer, if smart enough, can still be productive from day 1, but the likelihood of them doing something they shouldn’t, or re-implementing something that already exists is high. Combine that with the superhuman speed of spitting out code in seconds and you have a recipe for disaster if not supervised: Broken conventions, re-implemented functions, security holes.
And the bad thing: Your agent won’t learn by itself. Once ‘onboarded’ it can be productive for the task you’ve given it, but once you create a new session, we have to start from scratch.
In an optimal world, we could just give the agent the entire codebase as context, but with current limitations, that won’t be possible anytime soon. The opposite is true: The more files we add to an agent’s context, the more chaotic and unfocused it will get. Not good.
Everything competes for context. Even before we write a single word, the context has already been pre-filled with instructions by your editor, available tools how to use those tools, MCP, etc.
To give you an idea, the system prompts of different editors (remember when the max context window we had was under 4000 tokens?):
- Cursor: 3900 words
- Cline: 2990 words
- Copilot: 1400 words
So what we have to do: Onboard an agent as efficiently as possible - the least amount of tokens that covers everything the agent needs to know.
I’ll structure this post into 2 parts: 1 is talking about the basics of getting more quality out of Vibe Coding, part 2 will be going into more advanced task orchestration
1. The basics of stronger agents
In 1. We’ll talk about how to guide agents and bring them up to speed on your code
1.1 A strong entry point
The first thing I do in every project I work on is to generate a strong entry point for the agent. Think of it as a document that has a condensed, high-information density overview of what the project is about. Previously I called this “LLM.md”, but recently I renamed them to “CLAUDE.md” since more and more tools automatically read CLAUDE.md on start.
I build each part of this document with the question: If I give this document to someone who has never seen the code in here, will they have enough information to research the rest themselves?
Everything not in this document will require the agent to research it on their own, increasing the likelihood of messing something up, missing something altogether or misunderstanding.
- What is the project
- 1-2 lines about what the purpose and goal of the project is
- Key architecture
- “Uses clean architecture”
- “Uses MVVN”
- “All data fetching logic is done with react-query”
- “Uses bun”, “NodeJS 18+”
- “Uses jest for tests”
- Key commands to interact with the repository
- Build command: How to build the app “bun build”, “make build”
- Lint: How to check the code for errors “make lint”, “bun run lint”
- Test: How to run tests “bun test”, “make test”, etc
- What are the key parts and where to find them
- “/app” - NextJS app directory with routing and pages
- “/app/components” - React components, includes UI
- “/repository” - Database layer, contains functions for fetching data
- “/services” - Directory for connectors to external services and APIs
- Very simple guides on key questions, with more documents going in detail
- “How to write tests”
- “All tests go into /test and have to use jest as test runner
- “Always mock with X. All database interaction has to be mocked”
- “For react-query testing, read xxx.md”
- “For React component testing, read yyy.md”
- “How to create a new API route”
- “App routes always go into /app/routes and have to use Hono”
- “Routes are to be imported and mounted into the router at /app/routes/routes.tsx”
The point of this document is not to be the knowledge dump for everything going on, but more a booster: It gives a quick refresher of what the project is about, where to find what, what the stack and architecture is, and more importantly: Where to continue checking if something is missing.
Thanks to this, an agent doesn’t have to re-scan the entire repository and cd+ls
a dozen times, but can focus on targeted scans in the directories that it needs to depending on the task.
1.2 A strong ruleset
On top of the starting point, I have a separate rule file that is very explicit about how this repository should get handled. This usually goes into whatever agent’s rule definition I currently use. For Cursor, that’s .cursor/rules
, for Cline that’s .clinerules
, for Roo that’s .roo/rules
, for Zed that’s .zed/
and so on.
While the starting point is for getting the agent onboarded, the rules are for defining constraints the agent must adhere to. So:
- Code conventions
- Test conventions
- Architecture decisions
- Git & branching rules
- Acceptance criteria for all tasks
I keep the rule file without format, numbered. I can write rules in markdown but often combine this with the starting point document, so we’re losing precious context for each token.
Here’s an example from a recent project:
1. Follow TDD: Write a test first, make sure the test fails, then implement the feature. See guides: [Component Testing Guide](mdc:docs/component-testing.md) and [React Query Testing](mdc:docs/react-query-testing.md). Place component tests in test/components/*.test.tsx.
2. Always use `bun` as the package manager and build tool. Run tests with `bun test` and format code with `bun format`. Never use other testing frameworks (like vitest). This project uses bun for testing.
3. Always make sure the build passes before committing code.
4. KISS (Keep It Simple, Stupid): Favor the simplest solution that meets the requirements.
5. DRY (Don't Repeat Yourself): Avoid code duplication. Extract reusable logic into functions or classes.
6. Docstrings: Write clear and concise docstrings for all classes, functions, and methods, explaining their purpose, parameters, and return values.
7. Create a new branch when working on a bigger feature. Small edits don't need a new branch.
8. Commit messages start with a verb (first letter capitalized). Include a Linear ticket reference (MIC-XXX format) when available. Be descriptive about what changed and why.
9. Always create a design doc first when creating a new feature. Small edits don't need this. Read docs/guides/design-doc-guide.md on how to write a design doc.
10. Always break down a design doc into small logical tasks that can be given to an engineer. Think stories. Add this to the design doc as a checklist and check them off as you finish work. Commit into your branch after each task is completed.
11. Use path aliases (`@/*`) instead of relative paths.
12. Follow the exact directory structure and import patterns from existing code
13. Run `bun format` after finishing all changes, and `bun run build` after a feature has been completed
14. Always write a log entry in logs/ with what you did. Name it YYYY-MM-DD-feature-name.md.
15. **All database operations must be implemented exclusively in `/lib/microfn` modules.**. **API routes (`/app/api`) and React components must never contain direct Prisma calls or any database logic.**. **API routes must call exported functions from `/lib/microfn` to perform any database operations.**.
16. Always run the vscode task "format", or if you don't have access to it, run `bun format` after each task is completed.
17. DO NOT fix lint errors about formatting, spacing, import order manually. Always run a `bun format` first as that may fix most issues. `bun format` will fix: Import order, removing unused imports, spacing, whitespace, punctuation.
18. Use shadcn and tailwind. Always look at other files in the same directory to understand style and structure.
1.3 Use scripts for an automatic feedback loop to the agent
I consider this an absolute essential for Vibe Coding + AI automation, and maybe even more important than the rules we just talked about.
Agents need to have a way to understand when the build is broken, the tests fail or the code is wrong. Linting can do that automatically if your editor+agent combo supports it, but even better to have it editor agnostic.
These are simple scripts and tools I sprinkled into the previous 2 sections already: make build
, bun build
, bun lint
, make test
, bun test
- you name it.
In my rule I always have something like this:
14. Run `bun format` after finishing all changes, and `bun run build` after a feature has been completed
No matter what, the agent has to run the build script after each task, which will immediately report back if something is broken. This feeds back into the agent that something went wrong, and prompts it to fix the problems.
- In Swift, I do a
xcodebuild
- In Golang, I do a
go build
- In NextJS, I do a
next build
Similarly, I always have a strong formatter active in my projects that reformats everything and enforces coding style. Agents tend to do what they want when it comes to coding style, and instead of adding more and more rules to instruct them about small things (and taking up precious context space), it’s much easier to just re-format everything through prettier
, xcformat
, mix format
, and add that to the acceptance criteria of every task.
If the agent runs bun format && bun build
after each task, I can be sure that the code is formatted correctly and the build runs before a task is considered complete.
1.4 Improving agents through self-improvement and reflection
Another good thing to do, to make sure entry point and rules are updated, is to let the agents do it themselves.
I often include an additional rule like the following. Depending on context size I either have this as a separate prompt somewhere that I can reference (aka “read @prompts/self-improve.md”), or part of my ruleset.
If during a session the agent breaks convention and does something I don’t want it to do, I correct it and teach it the correct way. The purpose of the reflection is to review the conversation history up to this point and put the learnings back into the documents we have.
# Self-Improving Cursor Rules Reflection
**Objective:** Offer opportunities to continuously improve `.cursor/rules` or [CLAUDE.md](mdc:CLAUDE.md) based on user interactions and feedback.
**Trigger:** Before completing any task that involved user feedback provided at any point during the conversation, or involved multiple non-trivial steps (e.g., multiple file edits, complex logic generation).
**Process:**
1. **Offer Reflection:** Ask the user: "Before I complete the task, would you like me to reflect on our interaction and suggest potential improvements to the active `.cursor/rules` or `CLAUDE.md`?"
2. **Await User Confirmation:** Proceed to completion immediately if the user declines or doesn't respond affirmatively.
3. **If User Confirms:**
a. **Review Interaction:** Synthesize all feedback provided by the user throughout the entire conversation history for the task. Analyze how this feedback relates to the active rules in `.cursor/rules` or [CLAUDE.md](mdc:CLAUDE.md) and identify areas where modified instructions could have improved the outcome or better aligned with user preferences.
b. **Identify Active Rules:** List the specific global and workspace `.cursor/rules` (or CLAUDE.md) files active during the task.
c. **Formulate & Propose Improvements:** Generate specific, actionable suggestions for improving the *content* of the relevant active rule files. Prioritize suggestions directly addressing user feedback. Output a change thatset that is usable with `edit_file` to write these suggestions into `.cursor/rules` or `CLAUDE.md`, for the user to review. Don't execute the tool yet..
d. **Await User Action on Suggestions:** Ask the user if they agree with the proposed improvements and if they'd like me to apply them *now* using the appropriate tool (`edit_file`). Apply changes if approved, then proceed to complete the task.
**Constraint:** Do not offer reflection if:
* No `.cursor/rules` or `CLAUDE.md` were active.
* The task was very simple and involved no feedback.
A prompt like will then cause the agent to offer reflection after an edit session, if any feedback has been given, neat!
It’s of course important to not blindly accept all improvements. LLMs just generate text and aren’t properly smart (yet), so if you tell them to improve your rules, they will almost always suggest stuff to add to it even if not needed. Make sure you approach the rule file with care and keep it clean.
Follow these tips and you should have a strong foundation for more complex code changes that are consistent with your existing (and evolving) codebase. Especially when working together with other people, across models and tools, agents need to be aligned on the same guardrails to not go completely nuts.
In the next post, we’ll dive into more advanced Vibe Coding: how to handle complex tasks, planning, hand-off, and multi-agent workflows.