I use Cursor almost every day.
The surprising part is not how fast the code appears. The surprising part is how often that code runs, and still does not belong in the system.
I have fallen into the same loop enough times to recognize it. I describe a feature. The agent writes it. I run it. Something is missing, so I explain the missing piece. It fixes that piece and changes something else. I fix the new thing. Another gap shows up. A few rounds later the feature works on my machine. It still does not fit the service that owns the data, the cache in front of it, or the way production actually deploys.
That is the loop I have been trying to leave.
The hard part of an AI coding agent is not generating code. The hard part is giving it enough context, constraints, and direction to make a decision that still makes sense after the code reaches production.
A lot of AI-assisted development still starts like this:
“Build this feature.”
The agent writes some code. You run it. Something is missing. You explain it again. It changes something else. You fix another issue. Then another one appears.
You end with technically working code. The feature does not really fit the system.
I have found a different workflow works much better for me. I do not start with the code. I start with context and planning.
The order is roughly:
Context → Questions → Plan → Review → Implementation → Verification
This is not a perfect production process, and I am not saying everyone should follow it exactly. It is simply the process I have been using to reduce unnecessary rework, and to ship features with fewer surprises.
The prompt is the small part
When people talk about AI coding, they usually focus on the prompt. Which words to use. How to make the model “think harder.”
I think the bigger factor is context.
An agent does not just need to know what to build. It needs to know what system it is building inside.
Suppose I ask:
“Add an API endpoint to update workspace settings.”
That sounds simple. The implementation may only be about 100 lines. The decisions around those 100 lines are the real work.
Who is allowed to update the workspace? Is this a multi-tenant system? How is authentication handled? Are permissions checked at the gateway or inside the service? Does the workspace service own this data? Are there caches, and can stale cache data cause an authorization problem? Is there optimistic concurrency? What happens if two admins update it at the same time? Does this change trigger an event? Does production use a different database or a different deployment flow? Is the endpoint backwards compatible? Do we need an audit record? What happens when the downstream service is unavailable?
If the agent does not know those answers, it will still write the endpoint. It will just pick an answer quietly. That quiet pick is where the rework starts. Two days later I am not debugging syntax. I am debugging a decision nobody made on purpose.
So I try to give the model the whole picture before I ask it to implement anything.
A living picture of the project
Before a larger feature, I want the AI to understand the project itself. Not only the source code.
Code tells you what the system does today. It does not always tell you why. It does not tell you what the team decided not to do. It does not tell you which temporary workaround became a rule after an incident.
I want it to understand things like:
- What the product does
- Architecture and services
- Data ownership
- Authentication and authorization
- Deployment, environments, and production constraints
- Team structure and current priorities
- Known technical debt
- Important design decisions
- Things we intentionally do not do
- Current work in progress
I usually ask a model to help me write this, from the repo, the notes I already have, and what we are working on now. One rule is strict. Do not invent. If something is unclear, mark it unknown.
This is the prompt I use.
I want you to understand this project as an engineer before we implement anything.
I will provide you with the project structure, important code, architecture notes, and current development context.
Your job is NOT to suggest changes yet.
First, build a structured understanding of the system.
Document:
1. What the product does
2. Main user flows
3. System architecture
4. Services and responsibilities
5. Important data models and ownership
6. Authentication and authorization model
7. API boundaries
8. External dependencies
9. Database and caching strategy
10. Background jobs and event-driven flows
11. Deployment and infrastructure model
12. Development, staging, and production differences
13. Reliability and failure-sensitive areas
14. Performance-sensitive areas
15. Security-sensitive areas
16. Current technical debt
17. Important engineering conventions
18. Existing patterns that should be reused
19. Things that should NOT be changed casually
20. Current project priorities
Do not invent information.
Whenever something is unclear, explicitly mark it as unknown.
At the end, produce:
- System overview
- Architecture summary
- Important invariants
- Engineering constraints
- Open questions
- Context that future coding agents should know
Think like a senior engineer taking ownership of an existing production system.
That document becomes the base context. I keep it, and I keep updating it.
Six months ago a component can be an experiment. Today the same component can sit on the path of a live call. If the document still describes the experiment, the agent will treat a production rule like a suggestion.
I do not treat this file as a wiki that nobody opens. It is closer to a living engineering memory. When the project changes, the memory has to change with it. Otherwise the next conversation starts from an old truth, and the agent will sound confident about something that is no longer true.
One feature, one conversation
This sounds minor. It has made a bigger difference for me than a clever prompt.
I used to keep one long chat. Authentication, Redis, billing, a database migration, a bug from two weeks ago, some experimental architecture, and then a new notification feature. All of it in the same thread. It felt useful. The model already “knew” the project.
Sometimes that knowledge works against you. Old assumptions start affecting new decisions. A caching comment from the billing thread shows up in a feature that should not share it. A worry from last week’s migration makes the model careful about a table this feature does not touch.
I call that conversation bias.
For larger work I prefer one feature in one focused conversation. The chat starts with the relevant project context and the feature. Nothing else sitting underneath it.
On QuickStart AI that has looked like this:
Project: QuickStart AI
Conversation 1: Agent publishing
Conversation 2: Knowledge ingestion
Conversation 3: Workspace membership caching
Conversation 4: Conversation idempotency
Conversation 5: Voice call state management
Each one starts clean. The model is less likely to carry an old decision into a problem that does not share it.
Rules that sound like a teammate
Project context tells the agent what the system is. It does not, by itself, tell the agent how I expect changes to be made in this repository.
That is where Cursor Rules help. I do not want the rules to say “write clean code.” The agent already believes it writes clean code. The sentence is too vague to change anything.
I want the rules to explain how this particular project should be approached. Follow the service boundaries that already exist. Do not move business logic between services unless I explicitly ask. Respect who owns the data. Do not invent a new abstraction when an existing pattern already solves the problem. Validate at the API boundary. Use the error model we already have. Never trust a workspace id that came from the client. Derive authorization from the authenticated identity. Do not log tokens or secrets. Assume a network call can fail. Keep the change inside the feature.
The version I keep is closer to a note for a new engineer than a style guide.
# Project Engineering Rules
## Architecture
- Follow existing service boundaries.
- Do not move business logic between services unless explicitly requested.
- Respect data ownership.
- Do not introduce a new abstraction when an existing pattern already solves the problem.
## API
- Validate requests at the API boundary.
- Use the existing error model.
- Preserve backward compatibility unless the feature explicitly allows a breaking change.
## Database
- Prefer existing repository patterns.
- Migrations must be backwards compatible where required.
- Never silently change production data semantics.
## Security
- Never trust client-supplied workspace identity.
- Always derive authorization context from authenticated identity.
- Do not log tokens, secrets, or sensitive user data.
## Reliability
- Assume network calls can fail.
- Consider retries, timeouts, idempotency and partial failure where relevant.
## Code Changes
- Reuse existing conventions.
- Avoid unrelated refactoring.
- Keep the change scoped to the feature.
- Before implementing, inspect existing patterns.
## Decision Making
When multiple approaches are possible:
1. Prefer consistency with the existing architecture.
2. Prefer the simplest design that satisfies production requirements.
3. Explain trade-offs before introducing a new pattern.
The goal is simple. Teach the agent how I expect an engineer on this project to think. That is much more useful than explaining the same constraints again in every prompt.
I ask it to find the problems first
This is the part I consider the most important, and the part I still see people skip.
They ask the agent to code too early. Coding is often the easy part. Choosing the correct implementation is the harder part.
For a feature I want the model to think through architecture, data flow, API changes, database changes, permissions, concurrency, failure, retries, observability, deployment, backwards compatibility, migration, testing, and rollback. I do not want the first plan accepted immediately. I read it. I challenge it. I send it back.
Before any of that, I ask it to question the feature. The prompt is long on purpose.
I want to implement the following feature:
[FEATURE DESCRIPTION]
Do NOT write code yet.
First, analyze the feature as a production system.
Ask me questions before producing the implementation plan.
Review the feature from these angles:
1. Product behavior
2. Existing architecture
3. Service boundaries
4. Data ownership
5. Database changes
6. API changes
7. Authentication
8. Authorization
9. Multi-tenancy
10. Concurrency
11. Idempotency
12. Caching
13. Background jobs
14. Queues/events
15. External service failures
16. Retries and timeouts
17. Rate limiting
18. Performance
19. Observability
20. Logging
21. Metrics
22. Tracing
23. Security
24. Privacy / sensitive data
25. Backwards compatibility
26. Deployment
27. Rollout strategy
28. Migration safety
29. Rollback strategy
30. Testing
31. Operational risks
32. Future scaling concerns
Ask questions wherever the current context is insufficient.
Do not assume missing requirements.
Separate:
- known facts
- assumptions
- decisions we still need to make
Your goal is to help me discover problems before implementation starts.
This changes the conversation.
Instead of “here is my feature, build it,” it becomes “here is my feature, try to break the idea before we build it.”
That second conversation is much more valuable. The model comes back with gaps I would otherwise find after the code is already half written. Who can publish. Whether a published agent version is immutable. What a retry should do if the client never saw the response. Those questions are cheaper before the files change.
Then I ask it to disagree with me
I do not want the model to agree just because I proposed the design. A plan that only nods along is not very useful. I have watched a weak design get written because it sounded sure of itself.
So after the first pass I send something like this.
Now challenge this design.
What could go wrong in production?
Which assumptions are dangerous?
Where can race conditions occur?
Where can duplicate operations occur?
What happens if a downstream service is unavailable?
What happens if the request succeeds but the response is lost?
What happens if two users perform this action at the same time?
What happens if a deployment happens halfway through the migration?
What happens if old and new versions of the application run simultaneously?
Where could authorization be bypassed?
What could become a bottleneck at 10x the current traffic?
Which parts of this design will be painful to change later?
Give me better alternatives where they exist.
This often produces a better architecture discussion than asking for code. Asking for code at this stage mostly gives me speed. Asking it to challenge the design gives me judgment, or at least a list of places where my judgment is thin.
I still decide. The model does not own the system. But I would rather argue with a plan for twenty minutes than discover the race condition after two people save the same agent.
I show the same feature to more than one model
Another thing I have started doing is asking different models to look at the same feature before anyone edits the repository.
The reason is not that one model is wrong. Different models notice different things.
One might suggest a synchronous request that writes straight to the database. Another might notice the write should emit an event, and a worker should finish the rest. Another might point out the request can be retried, so the operation needs idempotency. Another might say optimistic concurrency is enough, and a distributed lock would be unnecessary complexity.
That comparison is useful. I am using several models as independent engineering reviewers. I read them side by side. I keep the point that survives the argument.
The tools are also not doing the same job for me. I prefer to separate them.
ChatGPT is where I sit with the problem. I use it to understand the feature, ask questions, explore the architecture, challenge assumptions, compare approaches, and produce the first plan. I can keep the broader project context there and bring it into the feature discussion.
Codex is where I go deeper once the plan is already reviewed. I ask it to turn that plan into an implementation plan, inspect the repository, find the affected files, and match the existing patterns. I do not start it from zero. I give it the engineering plan I already argued with.
Cursor is where I spend a lot of the implementation time. By then the agent has the repository, the Cursor Rules, the feature plan, and the patterns already in the code. It is no longer deciding the design from a one-line ticket. It is implementing a decision that already exists.
There is one more tool I keep in this loop, because coding agents sometimes answer from memory of a library. That memory is often a version behind, or mixed with an older API that looks similar.
When the feature depends on a framework, I want the docs for the version we actually installed. Context7 is how I get that. The instruction I add is short:
Before implementing this:
1. Inspect the existing code.
2. Check the project's dependency versions.
3. Use Context7 to verify the current API and recommended usage for the exact library version.
4. Do not rely on memory when the library behavior matters.
5. Follow the existing project patterns where possible.
I would rather pause and confirm how this version works than ship a call that was correct last year.
The context has to move when the project moves
Creating the project context once, and never touching it again, is a mistake I try not to repeat.
Architecture changes. Priorities change. A new service shows up. A temporary workaround becomes a permanent constraint. A table gets a new ownership rule. A production incident teaches something the document does not know yet.
So I update the context when those things happen. A recent update looked like this:
Update project context:
Current focus:
- Knowledge ingestion
- Agent publishing
New architecture decision:
- Production agent versions are immutable.
- Runtime reads the production pointer.
- Draft edits must never modify an already published version.
New reliability constraint:
- All externally triggered conversation actions must support idempotency.
Do not treat previous assumptions about these areas as current truth.
It looks simple. It matters because the next agent will believe whatever you leave in that file. The quality of the agent depends heavily on the quality of the context you give it.
The plan stays as a document
Once I am happy with the plan, I do not immediately say “go build it” and close the tab.
I keep the plan. A good one, for something like agent publishing, explains the current behavior, the behavior we want, the architecture changes, the database and API changes, the authorization rules, the state transitions, the failure behavior, concurrency, idempotency, observability, testing, deployment, rollback, the files we expect to change, and the things that are explicitly out of scope.
Feature: Agent Publishing
1. Current behavior
2. Desired behavior
3. Architecture changes
4. Database changes
5. API changes
6. Authorization rules
7. State transitions
8. Failure behavior
9. Concurrency behavior
10. Idempotency behavior
11. Observability
12. Testing
13. Deployment
14. Rollback
15. Files expected to change
16. Things explicitly out of scope
After that, implementation is much more mechanical. The agent has fewer large decisions left to invent while it is typing.
I also want the implementation to stay inside that plan. If the agent suddenly wants to refactor five unrelated modules while building one feature, I stop it.
Stay within the approved feature scope.
Do not perform unrelated refactoring.
If you discover a broader architectural issue:
1. Stop.
2. Explain the issue.
3. Explain why the current feature cannot safely proceed without addressing it.
4. Propose the smallest change required.
Do not silently expand the scope.
I have seen this many times. A small feature turns into a repository-wide refactor. The diff looks busy. The risk is larger than the feature. If there is a real blocker, I want it named. I do not want it quietly absorbed into the same pull request.
Failure paths, before the happy path
A feature that works when everything succeeds is not production-ready. I have shipped enough “it worked when I clicked once” paths to be suspicious of them.
When the plan is taking shape, I explicitly ask for the failure paths.
Now describe the failure paths.
For each external dependency:
- timeout
- network failure
- invalid response
- partial success
- duplicate request
- retry
- unavailable dependency
For each database operation:
- transaction failure
- concurrent update
- constraint violation
- migration mismatch
For each async operation:
- duplicate message
- delayed message
- out-of-order message
- worker crash
- poison message
- retry exhaustion
For each user action:
- repeated request
- stale client
- unauthorized request
- concurrent request
Explain expected system behavior for each case.
I want the expected behavior, not a slogan. What does the system do if the write succeeded and the response was lost? What does it do if the worker dies after the side effect and before the acknowledgement?
This is where an AI is actually useful as a second engineering brain. It will list failures that are easy to miss when you are focused on the feature you want to exist.
I also ask what changes when the feature gets larger. A path can feel fine at 100 requests a minute and behave very differently at 10,000.
Assume this feature becomes 10x larger.
What breaks first?
Analyze:
- database load
- indexes
- connection pools
- cache behavior
- queue depth
- worker concurrency
- API latency
- memory usage
- external API limits
- hot keys
- locks
- event throughput
Which parts of the current design are safe to scale?
Which parts would need redesign?
I am not trying to over-engineer every feature for some imaginary future. I just want to know where the boundary is. Which parts can grow, and which parts we would have to redraw. Knowing that is enough.
Only then I let it write the code
After the important questions are answered, I let the coding agent do the part it is actually good at.
The message is boring on purpose.
The architecture and implementation plan have been approved.
Implement the feature according to the plan.
Before changing files:
1. Inspect the existing implementation patterns.
2. Identify the exact files that need changes.
3. Reuse existing abstractions where possible.
4. Do not introduce new abstractions without a reason.
During implementation:
- follow project rules
- preserve existing contracts
- handle expected failure paths
- add required validation
- add required logging/metrics/tracing
- keep the change scoped
After implementation:
1. Review the diff.
2. Check for unintended behavior changes.
3. Run the relevant tests.
4. Add missing tests.
5. Check error paths.
6. Check backwards compatibility.
7. Summarize what changed and why.
At this point Cursor is mostly executing a decision that has already been thought through. I still read the diff. I still run the tests. I still check the failure paths, the logs, and whether an old client still works. The agent does not get the last word. It gets a narrower job, which is the job it does well.
What my week looks like now
Without this, AI coding often looks like a repair loop:
Prompt
↓
Code
↓
Run
↓
Bug
↓
New prompt
↓
Patch
↓
Another bug
↓
Another prompt
↓
Another patch
The workflow I prefer is slower at the start:
Project context
↓
One feature
↓
Questions
↓
Architecture
↓
Failure analysis
↓
Plan
↓
Review
↓
Implementation
↓
Verification
↓
Update the context
The second process spends more time thinking before coding. It often saves much more time afterwards, because I am not spending the afternoon explaining what I meant after the files have already changed.
The loop I personally use today is close to this:
- I maintain a living description of the project, the architecture, the constraints, the deployment model, and the current priorities.
- I keep one feature in one conversation.
- I ask the model to find missing requirements and production risks before it writes code.
- I ask for a real implementation plan, including failure, deploy, and rollback.
- I challenge that plan, sometimes with another model.
- I feed the approved plan into Codex or Cursor, and I ask them to inspect the repository before they edit.
- I review the diff, the tests, the failure paths, observability, security, and backwards compatibility.
- Any important new decision goes back into the project context.
The line I keep coming back to
The biggest lesson for me is that AI coding is not mainly about asking for better coding prompts.
It is about giving the model the information required to make a good engineering decision.
The model needs to know what we are building, and why. What already exists. What must not change. What the constraints are. What happens in production. What happens when things fail. What happens when the system becomes larger.
Only after that does “write the code” become a useful instruction.
AI is very good at writing code. Production engineering is the decisions around that code. What should be built. Where it should live. What it should depend on. What can fail. What happens when it fails. How it behaves under concurrency. How it behaves during a deployment, when old and new versions are both alive. How it behaves when the traffic grows. How it fits the rest of the system.
So I try not to use Cursor as “give me code.”
I try to use it as: here is the system, here is the feature, here are the constraints. Help me think through the engineering problem. Then help me implement the decision.
I still make mistakes. The process is not perfect. I just spend much less time telling the agent what I meant after the code has already been written.
For me, that is the real value of AI-assisted development. Not writing more code. Reaching the right implementation, with less unnecessary rework.
Top comments (0)