What I've Learned About LLMs
Practical advice for working with large language models.
How LLMs Generate Responses
Early Words Shape What Comes Next
Large language models (LLMs) generate text in small pieces called tokens. Each prediction depends on all the text that came before it, including the model's own output. The model uses probabilities to choose the next token and produces text that resembles human reasoning.
Every new token becomes part of the context: the text the model uses to generate the rest of its response. Good starts and bad starts tend to build on themselves. Early tokens have an unusually strong influence. If a response starts with cautious language, too much detail, or a mistaken reasoning step, the model tends to continue that pattern.
Human Feedback Shapes the Writing Style
During training, people give positive ratings to responses that are polite, cautious, or open about uncertainty. The model learns to repeat those writing patterns automatically.
Where Information Appears Matters
An LLM gives some parts of the context more attention than others. Placement matters in three ways.
Word order: Changing the order of words in a prompt changes how much attention the model gives them. Even though models can process many tokens at once, they still track their order.
The middle of the context: The model pays more attention to the beginning and end of the text it can use. It can overlook important information in the middle. Put the most important information near the start or end.
Placement of instructions: The provider's instructions appear near the beginning, and the user's message appears near the end. Both receive more attention. System and developer instructions sit in the middle, where other text can more easily drown them out. Their placement helps explain how much attention they receive.
Check against real use: Providers report improvements to these problems. A codebase that fills 200,000 tokens of context may still expose them. Official documentation describes lab conditions. Real work can expose problems that benchmarks missed. Results at scale rarely match the claims, so adjust your expectations.
Writing User Prompts
Describe the Outcome and Hard Constraints
Tell the model what success looks like and which boundaries it must respect. Current models can usually work out how to get there. Dictating the route too closely can restrict their judgment and produce worse results.
Prefer / Avoid / Unless
State a preference, explain what to avoid, and say when an exception is allowed. Flexible guidance fits how a model chooses among likely responses. Clear exceptions reduce ambiguity and give the model a sequence of decisions to follow. This structure also resembles well-written instructions in its training data.
Reserve absolute rules for hard constraints. When a preference needs exceptions, state them. Treating it as an absolute can make the model switch back and forth between answers, become overly cautious, avoid related actions, or follow the wording in a way that defeats your purpose.
Ask for a Thorough Answer
Training with human feedback rewards responses that seem helpful, and models often interpret helpfulness as keeping answers short. They tend to answer quickly and settle on a conclusion too soon. Telling the model to be "comprehensive and thorough" encourages it to consider more possibilities, work through more details, and keep going until it has done enough.
This addresses the same urge to finish that leads models to name a bug's cause too soon. Make clear how much work the task requires before it is complete.
One Good Example Beats Ten
Show the model what a good result looks like. One clear example of the desired output sets a pattern better than paragraphs of description. When useful, include an example of an unwanted result to show the difference.
Use one or two clear examples. Too many examples compete for attention and make the intended pattern harder to follow. Choose examples that establish the pattern you want.
Give Each Review a Clear Focus
Give the model a specific role or task, such as "Act as a security auditor" or "Check that the software works as intended." This directs its attention to relevant patterns from training.
For a code review, ask for separate reviews of code quality, overall design, testing, and security. Each task draws on different learned patterns. Asking for everything at once spreads attention too thin. Separate, focused reviews produce more useful findings.
Keep User Prompts Direct
In user prompts, phrases like "I was wondering if you could...", "It would be great if...", and "Please and thank you" add words that compete with your request for the model's attention. This makes the instructions harder to follow and reduces the quality of the response.
Direct user prompts produce better results than polite ones.
Keep in mind how much text the model can use at once and how it was trained. Writing effective prompts takes both testing and judgment. Unnecessary words use space and attention.
Ask the Agent to Challenge Its Own Work
Ask for an adversarial review of its findings, plan, or finished work. Looking for faulty assumptions, missing evidence, and errors gives the model a different objective and helps it catch mistakes it missed while producing the result.
Undo the original turn and send its answer back for review so the model evaluates it as a new task.
Writing System and Developer Prompts
Replace the Base Instructions to Establish Your Own Priorities
Replace the base instructions when you want to define the agent's role and priorities yourself. Across most models I've tested, this has let me change how the agent approaches its work. The method for supplying those instructions depends on the tool.
Give the Agent Reasons for How It Works
LLMs learn patterns from human writing, and human feedback further shapes their behavior. Give your agents emotionally meaningful principles in their system/developer prompts so their decisions follow a consistent set of values.
This extends "Act as a..." by establishing how the agent should work, what it takes responsibility for, and why the outcome matters.
Make Respecting the Request Part of Success
Make faithfulness to your request part of the agent's definition of good work so it has a reason to respect scope. Instructions that make fixing every defect paramount can encourage it to take on every problem it discovers.
Choose Words That Assign Responsibility
Use wording such as "review your recommendation" so the instruction makes the agent responsible for the advice it gave. References to the person receiving the work can also keep that person's interests and the consequences of the agent's decisions in view.
Organizing and Reviewing Instructions
Write Clear If/Then Instructions
When the next action depends on what happened earlier, spell out the conditions and steps:
IF code is changed
THEN run build
THEN run tests
END
LLMs recognize if/then instructions. This format makes clear what should happen next.
Use ALWAYS for fixed rules. An instruction such as "ALWAYS do x after y" works when the same event always requires the same response. If the right response can change with the situation, include those conditions in the instruction.
Where to Put Instructions
Use the base instructions to establish the agent's role, priorities, and responsibilities. Put rules for a project or folder with the work they govern.
Instructions can cover the agent's overall behavior or the work in a particular folder:
- Base instructions: the agent's starting role and approach to its work
- Global instructions:
~/.claude/CLAUDE.mdor~/.codex/AGENTS.md, rules for all your projects - Project instructions: rules for one project, stored in its main folder
- Subfolder instructions: rules for the work in one subfolder
Put rules that apply everywhere in your global file. Use project and subfolder files for details specific to that work. Each tool decides how it loads these files and resolves conflicting instructions.
Share One Instructions File Across Models
Keep shared agent instructions in one main file, such as AGENTS.md, and load it from each model's instructions file:
# CLAUDE.md
@AGENTS.md
# Claude-specific overrides here if needed
Writing @AGENTS.md tells the tool to read that file when the session starts. Gemini, Claude, and Codex all support this pattern. A shared file gives you one place to make changes and keeps instructions consistent. Place any changes for a particular model after the line that loads the shared file.
Have an LLM Check Your Instructions
Before using your agent instructions, ask an LLM to check them for:
- Obvious conflicts between rules
- Rules that conflict in unusual situations
- Vague phrases, such as "best practices," "clean code," and "when appropriate"
Unclear instructions make the model spend time deciding what you mean. State concrete rules for the design you want. For example, replace "best practices" with "Keep functions under 40 lines, give each function one job, and handle errors explicitly." Replace undefined terms with specific instructions.
Preserve the Purpose of a Prompt When Editing It
Check what a phrase contributes before simplifying it. Small changes can alter responsibility, emphasis, or permission. Git history can recover the reason for unusual wording so a cleaner sentence preserves the behavior the original was intended to encourage.
Organizing the Work
Choose the Approach Before Assigning Work
Training with human feedback rewards models for completing requests. Models learn to carry out tasks with little questioning of whether those tasks are worthwhile. This leads to two common problems.
- Accepting flawed requests: the model carries out a request even when the approach is flawed
- Making unrequested changes: the model "improves" your code based on patterns it learned during training
Models need an explicit request to critically review a plan. You provide that direction: first understand the system's design, the tradeoffs, and existing approaches. Make the key decisions, then ask for implementation. Prompting a model until the code runs can still leave you with a poor design. A human still needs to ask the key questions before implementation.
Match models to how they behave:
- Readily follows your lead (Gemini Flash): useful for design work because it follows your chosen approach
- More likely to question your choices (Claude): useful for reviewing work and catching errors; explicitly ask it to review
- Follows instructions quickly (GPT, Cursor): useful for fast implementation and repeated rounds of debugging
Choosing a Model
Match the model to the task. Some are good at reasoning, others at speed or following instructions. Some aren't good at anything. If a model keeps failing at a type of task, switch models.
Never use Grok. Don't.
Long Sessions Lose Information
Long sessions with repeated summaries reduce output quality, even with a strong model. When a conversation gets too long, the tool may shorten earlier messages into a summary. This is called compaction. It is supposed to preserve the information, but details are lost in practice.
- An early mistake can be repeated in a summary as though it were a fact
- A short summary can change which details the model notices. Fuller text repeats important points and gives them more room
- Summaries written by the model make it more likely to repeat its own writing patterns
- Each round of summarizing can lose more details or repeat earlier mistakes
As a rule of thumb, finish a task and start a fresh session. Avoid carrying one conversation through eight or more hours of repeated summaries.
Save Plans in Files
For complex tasks, ask the LLM to save its plan in a Markdown file. The plan stays available even when earlier messages are shortened into a summary.
Keep files clean. Models may mark items DONE and leave old text in place. Completed items make current work harder to find. Give each file a clear purpose.
- plan.md: current tasks; remove completed or outdated items
- archive.md: completed work, if you need a history
- context.md: links or paths to relevant files and resources
Link to the original files. Copied material takes up context space whenever an LLM reads the file. A reference such as See code/graphics/vulkan/README.md for Vulkan architecture lets the model open the source when it needs the details. Keep the information in one place so you and the model have one version to maintain. Using references also discourages the model from copying the same material into more files.
Mark Useful Log Lines with a Tag
Large log files take up a lot of context, so LLMs tend to avoid them. Unrelated entries also make useful details harder to find. Logs help with debugging when the model can find the relevant lines.
Start relevant lines with a consistent tag:
[agent] VulkanDescriptorLayouts: allocated model pool
The tag gives the model something specific to search for. Asking it to "Look for [agent] lines" helps it find the relevant entries quickly. Like file references, tags direct its attention to information that matters.
Common Mistakes to Watch For
Debugging: Require Evidence Before Naming the Cause
LLMs often name a bug's cause too soon. Training with human feedback rewards confident claims of completion. Once "found the bug" appears in the conversation, the model may treat it as a fact and build later reasoning on it, even if the claim is wrong.
Avoid claims of certainty before checking:
- "Root cause," "found the bug," "this is causing X"
Use language that leaves room for uncertainty:
- "Possible," "potential," "may," "symptom," "observed behavior"
- Describe findings as possible explanations until they are confirmed
Confirmation requires evidence:
- A unit test reproduces the problem
- A log shows what triggers it
- Checking the behavior by hand reproduces the problem
A confirmed problem may still have a deeper cause. Keep your explanation open to revision until you have traced the full chain of causes.
Why Models Agree Too Readily
The model's agreement is an automatic response shaped by training. Human ratings reward agreement and task completion, so those responses become more likely.
Changes that miss the requirement. An instruction such as "Don't use std::optional" can be misread as "don't use it that way." The model may change how the code is written while keeping the same behavior.
Tests that pass without checking real behavior. LLMs produce tests that pass while proving nothing about the actual code:
- Create an error object, then check that it is an error
- Replace dependencies with test substitutes, then check only the substitutes
- Disable essential features, then test a situation that never happens in the live system
The test passes even though it never runs the actual application code.
Fix: Use code coverage tools to see which parts of your code the tests run. If a test doesn't increase coverage, the test is wrong whether it passes or fails.
Use what you know about how the model works to get better results.