Context and Prompting
Before you start
You need: Article 01, and a real project to prompt about.
Time: about 45 minutes, at the keyboard.
Learning objective
Write prompts that produce usable code the first time, by supplying the context the model cannot infer.
Topics
- Why context decides quality
- The five parts of a good prompt
- Showing your conventions
- Constraints
- Iterating rather than restarting
- Project instruction files
- Long conversations
- What never goes in a prompt
Why context decides quality
Bad: Write a method to get students.
The model has to guess the language, the data access approach, the entity shape, whether it is multi-tenant, whether soft delete exists, whether it should be async, and what "get" means. It will guess plausibly and be wrong about your system.
Good:
I'm working on a multi-tenant school management API in ASP.NET Core 9
using Dapper.
Entity:
public class Student
{
public int Id { get; set; }
public Guid PublicId { get; set; }
public int SchoolId { get; set; }
public string Name { get; set; }
public string RollNumber { get; set; }
public string ClassName { get; set; }
public string Section { get; set; }
public StudentStatus Status { get; set; }
public bool IsDeleted { get; set; }
}
Existing repository method for the pattern to follow:
public async Task<Student> GetByIdAsync(int schoolId, int id)
{
const string sql = @"SELECT Id, PublicId, SchoolId, Name, RollNumber,
ClassName, Section, Status
FROM Student
WHERE SchoolId = @SchoolId AND Id = @Id AND IsDeleted = 0";
using IDbConnection connection = _connectionFactory.Create();
return await connection.QuerySingleOrDefaultAsync<Student>(sql, new { SchoolId = schoolId, Id = id });
}
Write GetByClassAsync(int schoolId, string className, string section).
Requirements:
- SchoolId always filtered; it comes from the auth claim, never a request parameter
- Exclude soft-deleted rows
- Exclude students with Status = Transferred or Graduated
- Ordered by RollNumber
- Parameterised, no string concatenation
- Follow the style of the method above exactly
The difference is not prompt length. It is that the second one contains what cannot be guessed — the entity shape, the multi-tenant rule, the soft-delete rule, the status rule and the house style.
Every one of those requirements exists because omitting it produces a bug. The status rule in particular is a business rule no model can infer.
The five parts
| Part | Contains |
|---|---|
| Context | Stack, versions, what the system does |
| Code | Entities, existing methods, the current file |
| Task | What you want, in one sentence |
| Constraints | Rules, conventions, what to avoid |
| Format | What you want back |
Context: ASP.NET Core 9 Web API, Dapper, SQL Server, multi-tenant by SchoolId
Code: [entity + one existing repository method]
Task: Add a method returning fee defaulters for a school
Constraints: - Parameterised SQL only
- SchoolId filtered in every query
- DECIMAL for money, never float or double
- Full braces, no expression-bodied members
- Match the existing method's style
Format: Just the method, with a one-line comment explaining the query
Constraints are what separate usable output from output you must rewrite. Without "no expression-bodied members", you get => everywhere and spend ten minutes reformatting.
"Just the method" saves you scrolling past four paragraphs of explanation you did not need. Ask for the shape you want.
Showing your conventions
One example of your existing code teaches more than a paragraph of description.
Here's how we write repository methods:
[paste one complete method]
Write GetOverdueAccountsAsync in exactly this style.
The model picks up naming, brace style, async suffix, parameter order, how connections are created, how SQL is formatted, and how nulls are handled — from one example, without you listing any of it.
When output does not match your codebase, the cause is almost always that you described the convention instead of showing it.
Constraints
Standing constraints worth carrying into every prompt on this project:
- SchoolId comes from the auth claim, never from the request
- Every query filters by SchoolId
- Every read excludes soft-deleted rows
- Money is decimal / DECIMAL(18,2), never float or double
- Absent exam results are null, never 0, and the absent check comes first
- Composite uniqueness: (SchoolId, RollNumber), never RollNumber alone
- Parameterised SQL, never concatenation
- Full braces; no expression-bodied members in tutorial code
- Explicit types, not var, in examples
These are the project's non-negotiables, and a model has no way to know them. They are also exactly the rules whose violation produces a bug with no error message.
Negative constraints work:
- Don't add a caching layer; it's out of scope
- Don't change the entity; only the repository
- Don't add NuGet packages
- Don't reformat the surrounding code
"Don't change anything else" is worth including every time you paste an existing file. Without it, you get a rewritten file and a diff you cannot review.
Iterating rather than restarting
The first answer is a draft. Refine it.
Good, but SchoolId is a query parameter. Read it from the auth claim instead
and keep everything else the same.
The absent check needs to come before the pass check — right now an absent
student with null marks is marked Fail.
Use DECIMAL(18,2) rather than FLOAT for Amount, and explain why it matters here.
That's more than I need. Just the repository method, without the controller.
Correcting one thing keeps everything that was already right. Restarting the conversation throws away context you spent effort building and usually reintroduces a problem you already fixed.
Say what is wrong, not just "that's wrong". "This doesn't filter by SchoolId" gets a fix; "no, try again" gets a different guess.
Project instruction files
Tools that read your repository can read a standing instructions file:
| Tool | File |
|---|---|
| Claude Code | CLAUDE.md |
| Cursor | .cursorrules |
| Copilot | .github/copilot-instructions.md |
# School Portal — AI context
## Stack
ASP.NET Core 9 Web API, Dapper, SQL Server 2022, React 18 frontend.
## Non-negotiable rules
- Multi-tenant: SchoolId comes from the auth claim, never from a request parameter.
Every query filters by SchoolId.
- Soft delete: every read excludes IsDeleted = 1.
- Money: decimal in C#, DECIMAL(18,2) in SQL. Never float or double.
- Absent exam results: MarksObtained is NULL with IsAbsent = 1, never 0.
The absent check comes first in any grading chain.
- Uniqueness is composite: (SchoolId, RollNumber).
- Parameterised SQL only.
## Style
Full braces. No expression-bodied members. Explicit types over var.
Async methods end in Async. Repositories return entities, not DTOs.
## Layout
src/NexCoding.SchoolPortal.Api controllers, middleware
src/NexCoding.SchoolPortal.Core entities, interfaces
src/NexCoding.SchoolPortal.Data repositories
tests/NexCoding.SchoolPortal.Tests xUnit
## Out of scope unless asked
Caching, new NuGet packages, entity changes, reformatting.
This replaces the constraints you would otherwise retype in every prompt, and it makes the whole team's AI output consistent.
Keep it current. A stale instruction file produces confidently wrong code and is worse than none — the model follows it faithfully.
Long conversations
Quality degrades as a conversation grows: earlier constraints get diluted, and contradictions accumulate.
Start fresh when:
- Moving to an unrelated task
- The answers have drifted from your conventions
- You have corrected the same thing twice
- The conversation is mostly abandoned attempts
Carry forward a summary rather than the history:
Continuing from an earlier session. Established so far:
- StudentRepository uses Dapper with a connection factory
- SchoolId always from the claim, filtered in every query
- GetByIdAsync and GetByClassAsync are done and tested
- Next: GetFeeDefaultersAsync, joining FeeAccount and FeePayment
Re-state the critical constraints in a new conversation. The model has no memory of the last one.
What never goes in a prompt
| Never paste | Why |
|---|---|
| Connection strings | Credentials |
| API keys, tokens, passwords | Credentials |
| Real student or parent data | Personal data, and often a legal issue |
| Phone numbers, addresses, dates of birth | Personal data |
| Proprietary algorithms | Depends on your company's policy |
| Anything under NDA | Contractual |
// Wrong
"Server=prod-sql-01;Database=SchoolDb;User Id=sa;Password=P@ssw0rd123;"
// Fine
"Server=<server>;Database=<db>;User Id=<user>;Password=<password>;"
// Wrong
new Student { Name = "Aarav Menon", ParentPhone = "+91-9876543210", ... }
// Fine
new Student { Name = "Ravi Kumar", ParentPhone = "+91-9999999999", ... }
Anonymise before pasting. Replace real names with the standard example names, real phone numbers with placeholders, real ids with sequential ones. The bug reproduces identically.
Know your company's policy before pasting any production code. Many organisations have an approved tool and a prohibition on everything else, and "I didn't know" is not a defence.
Errors you will hit
| The mistake | Consequence |
|---|---|
| No stack or version in the prompt | Plausible code for the wrong framework |
| Describing conventions instead of showing one | Output that does not match your codebase |
| Omitting business rules | The tenant filter and the absent check go missing |
| "No, that's wrong" | A different guess, not a fix |
| Restarting instead of correcting | Loses context and reintroduces fixed problems |
| Pasting real student data | A privacy breach, and possibly a legal one |
Everything unique to your system has to come from you. The model knows ASP.NET Core; it does not know your rules.
Common mistakes
- Prompts with no context about the stack
- Describing conventions instead of showing an example
- Omitting business rules the model cannot infer
- No constraints, then rewriting the output
- Restarting instead of correcting one thing
- "No, that's wrong" without saying what
- A stale instruction file
- Continuing a conversation that has drifted
- Pasting connection strings or real personal data
- Not knowing the company policy
Practice
The course exercise is write a prompt that works first time.
- Write the vague prompt and the full five-part prompt for the same task. Compare the output.
- Give a description of your code style, then give one example method instead. Compare which produces matching code.
- Prompt without the multi-tenant rule and check whether the result filters by
SchoolId. - Prompt for grading logic without the absent rule. Check the order of the conditions.
- Add "don't change anything else" to a prompt that pastes a file, and compare the diffs.
- Iterate three times on one answer by correcting a single thing each time.
- Correct with "no, that's wrong", then with a specific statement of the problem. Compare.
- Write a
CLAUDE.mdfor a project of yours, including the non-negotiable rules. - Use it for three prompts and note what you no longer have to say.
- Deliberately let a stale rule sit in the file and observe the model following it.
- Continue a conversation past the point of drift, then start fresh with a summary. Compare quality.
- Take a real bug report containing personal data and rewrite it anonymised. Confirm it still reproduces.
- Find out what your company's or college's policy on AI tools actually is.
Exercises 3 and 4 are the ones to keep. They show what the model gets wrong when you omit exactly the rules that produce silent bugs.
You can now
- Give a model the context it cannot infer
- Show a convention by example rather than describing it
- State the non-negotiable rules in every prompt or a context file
- Correct one thing at a time
- Keep credentials and personal data out of prompts
Review questions
- Which parts of a prompt cannot be inferred from the code alone?
- Why does showing one example beat describing a convention?
- Why is correcting one thing better than starting over?
- What must never be pasted, and what do you do instead?
Next: Small verified changes