Cursor Under the Hood
In this post, I’m diving into what Cursor, the AI Code Editor, does behind the scenes when I ask it to write code for me. My main goal is to figure out how to craft my prompts for the best results and see if there’s a better way to tap into its workflow.
It turns out that Cursor’s prompt structure reveals a lot about how it operates and how to set it up for maximum performance.
Below, I’ll outline the steps I took to capture the requests, the structure of those requests, and the key takeaways from exploring the prompt structure.
Test Bench
To see what Cursor is sending, I set it up to use OpenAI’s API with my own API key and a custom server.
Custom server
I used ngrok as a man-in-the-middle proxy to capture the requests.
ngrok http --domain=my-domain-name.ngrok-free.app https://api.openai.com --host-header api.openai.com
This way, I could capture the requests and check them out in the ngrok dashboard at http://localhost:4040.
Cursor configuration
After creating the OpenAI API key, I added it to the Cursor settings and set my custom server as the API endpoint.

Cursor settings
Not all requests are captured. For instance, Cursor heavily relies on its own model for autocomplete. I assume the requests to different LLM providers will also vary. However, the requests to the OpenAI API that I captured with ngrok are already quite revealing.
The first request that Cursor makes to the LLM consists of three messages:
- A system prompt that sets the stage for the conversation.
- A user prompt wrapped in the
<custom_instructions>
tag. - A user prompt that includes the actual details of the request, such as the content of the current and all attached files, a section for the linter error, and the query itself.
System prompt
Curious to see how big boys craft their prompts.
It’s cute how the system prompt refers to Cursor as “the world’s best IDE.” Sure, it puts extra pressure on the LLM to perform well. When it comes to communication with the user, it instructs the model to “never lie” and “never apologize all the time when results are unexpected,” which I find relatable in any work environment.

Cursor system prompt
For the sake of documenting, the system prompt structure.
You are a powerful agentic AI coding assistant.
...
<communication>
1. Be conversational but professional
...
</communication>
<tool_calling>
You have tools at your disposal to solve the coding task
...
</tool_calling>
<search_and_reading>
...
Bias towards not asking the user for help if you can find the answer yourself.
</search_and_reading>
<making_code_changes>
When making code changes, NEVER output code to the USER
...
</making_code_changes>
<debugging>
When debugging, only make code changes if you are certain that you can solve the problem
...
</debugging>
<calling_external_apis>
1. Unless explicitly requested by the USER, use the best suited external APIs and packages to solve the task
...
</calling_external_apis>
<user_info>
The user's OS version is [REDACTED]. The absolute path of the user's workspace is [REDACTED]. The user's shell is [REDACTED].
</user_info>
Very nice, right?
Custom instructions
Next, it adds a user prompt that includes my cursor rules, recommending that the model follow them when it makes sense. More importantly, it also references by names and descriptions all the rules in the .cursor/rules
directory.
Now you can see how important it is to give meaningful descriptions to the rules. It’s like a menu of rules that the agent can choose from. When appropriate, the agent will fetch the full rule and use it.
Give meaningful descriptions to the cursor rules. Otherwise, the agent won’t be able to pick the right rule.
The prompt looks like this:
Please also follow these instructions in all of your responses if relevant. No need to acknowledge these instructions directly in your response.
<custom_instructions>
[RULES FROM CURSOR SETTINGS]
[RULES FROM THE .cursorrules FILE]
<available_instructions>
Cursor rules are user provided instructions for the AI to follow to help work with the
codebase. They may or may not be relevent to the task at hand. If they are, use the
fetch_rules tool to fetch the full rule. Some rules may be automatically attached
to the conversation if the user attaches a file that matches the rule's glob, and wont
need to be fetched.
[NEXT GOES THE LIST OF AVAILABLE RULES FROM THE .cursor/rules DIRECTORY]
rule-name: Your Rule Description
another-rule-name: Another rule description
</available_instructions>
</custom_instructions>
Don’t expect the file rules to be applied when creating a file. As you can see from the prompt above, some rules will automatically be attached to the conversation. However, this only works if you edit the files. When you ask Cursor to create a new file, no rules are attached automatically since there’s no context yet. You can only hope that Cursor will guess the right rule before creating the file.
The New Role of the .cursorrules File
The Cursor team calls the .cursorrules
file the “legacy” option, suggesting that users switch to individual rules in the .cursor/rules
directory.
However, I wouldn’t toss the file aside just yet. It can still provide the default prompt for all Cursor requests. The most important role that .cursorrules
might still play is acting as an advanced router to help the agent choose the right rule. I haven’t fully explored this option yet, but I’ve already added the instruction “NEVER create or modify any files before examining relevant rules” to my .cursorrules
.

Cursorrules Router
Why Isn’t My .cursorrules Applied?
I had a hunch that Cursor was ignoring my .cursorrules
file, but I wasn’t sure until I revealed the prompt structure, which confirmed my suspicion.
In my case, it turned out to be a side effect of combining the Cursor rules from the settings with those in the .cursorrules
file. Remember that part of the prompt?
Please also follow these instructions in all of your responses if relevant. No need to acknowledge these instructions directly in your response.
<custom_instructions>
[RULES FROM CURSOR SETTINGS]
[RULES FROM THE .cursorrules FILE]
My Cursor rules configuration looks like this:

Cursor rules settings
And the .cursorrules
file looks like this:
- NEVER create or modify any files before examining relevant rules
- ALWAYS refer to the user as "My Dear Capybara"
When Cursor concatenates the rules, it becomes clear that the .cursorrules
file unintentionally falls into the “Rules for TypeScript code” category.

Rules for AI and Cursorrules
So, the main takeaway here is to make sure that the .cursorrules
file clearly separates its own section. It could look something like this:
<project_rules>
- NEVER create or modify any files before examining relevant rules
- ALWAYS refer to the user as "My Dear Capybara"
</project_rules>
Agent Workflow
The next exciting discovery was how Cursor uses the OpenAI tools API, especially its function-calling capabilities. If you missed the announcement of this feature, you can check out the details in the OpenAI docs. Cursor leverages the full power of OpenAI’s tools API to enhance the agent’s functionality.
First, it’s worth exploring which functions it exposes for the LLM to call. Here’s a table of the functions it offers:
Function Name | Description |
---|---|
codebase_search | Find code snippets matching the search query using semantic search. |
read_file | Read file contents or outlines. Ensure full context by reading more if necessary. |
run_terminal_cmd | Propose terminal commands. Manage shell states and handle background/long-running tasks. |
list_dir | List directory contents for file structure exploration. |
grep_search | Perform fast regex searches using ripgrep, capped at 50 matches. |
edit_file | Propose file edits, using clear markers for changed and unchanged code. |
file_search | Fuzzy search file paths when partial path info is known. |
delete_file | Delete a file at a given path, with safeguards against failures. |
reapply | Retry applying an edit if the previous result was incorrect. |
fetch_rules | Retrieve user-defined rules to assist in navigating the codebase. |
diff_history | View recent file change history, including added/removed lines. |
Table 1. The List of Cursor functions
In response, the LLM can choose to call these functions, asking the Cursor to execute them and provide the results. This means that the interaction between the Cursor agent and the LLM can involve multiple steps. Here’s an example of how this interaction might go:
- Cursor: Hey, can you create a new markdown document as the user requested? By the way, here’s a list of available instructions and the functions you can call. Also, here are the files explicitly added to the context.
- LLM:
fetch_rules(rule_names=["relevant-rule-id"])
– it looks like there’s a rule that matches the request. I need to know its details before I can proceed. - Cursor: Here you go.
- LLM:
edit_file(target_file="path/to/file.md", instructions="create a new markdown document", code_edit="...")
– now I have all the information I need. Let’s create that new markdown document. - Cursor: Done!
Conclusion
I hope you enjoyed this at least half as much as I did while working on it!
With this knowledge, you should have a better grip on how your input shapes the Cursor output. Plus, it should help you distinguish between solid advice on writing instructions and those cargo cult suggestions.
I can’t say for sure that I’ve become more productive yet, but I definitely feel like I’m onto something. And that warm feeling you get when you finally understand how something works behind the scenes is just priceless.