For most of my career, when I wanted a machine to do something, I wrote instructions in a language designed for that purpose. Now I write them in English.
That shift deserves more respect than it usually gets. We spent decades building layers of abstraction so we would not have to think in intermediate language or assembly. C# and TypeScript let us express intent at a level close to how we think. Prompts push that one step further: plain spoken language, straight into something that executes.
The trouble is that we carried over the habits of conversation and left behind the habits of engineering. A prompt is not a message. It is a small program, and it deserves the same care as one.
Six parts of a request that holds
I write prompts in a loose structure now. It is not a template to fill in mechanically. It is a checklist for the things that, when missing, cause the request to come back wrong.
A distinct task. One bounded change or one bounded analysis. “Fix this” is not a task. “Make the order details read path use the new pricing service without changing the order of downstream events” is a task. If I cannot state the task in one sentence, the model will state it for me, and it will guess.
A role and a reason. Starting with the why is my preference. Why does this matter, what am I trying to protect, what is the lens. Then the role: a QA specialist, a platform engineer, a senior developer, a business analyst. The role sets what the model pays attention to. Without it, everything gets equal weight, and in a regulated system the data handling is not equal to the formatting.
Constraints. The project patterns to follow, the boundaries the data lives inside, and the things that are explicitly out of scope. That last one carries more weight than people expect. If the model can plausibly do something adjacent, it might. Saying “and do not touch the migrations” removes an option.
Acceptance. What has to be observably true for the result to be accepted, including the negative cases. If I do not define this, the model defines it, and its definition will be generous.
Output. What form the answer takes and, importantly, what assumptions it made. Asking the model to list its assumptions is the highest-value line in the whole prompt. Assumptions are where the gap between my intent and its understanding becomes visible, and they are much cheaper to read than a diff.
Risks and open questions. Tell me what you think I should have told you. The model will sometimes come back with a question that reframes the whole task. That is not a failure of the prompt. It is the prompt doing its job.
Here is what that looks like assembled:
Act as a senior engineer reviewing a legacy order flow. I need to change the order details read path so it uses the new pricing service. Constraint: the downstream event sequence must not change, because the warehouse depends on it. Out of scope: database migrations, the pricing service itself, and the notification worker. Acceptance: the read path returns the same fields plus the new price, existing tests pass unchanged, and no event is emitted in a different order. Show me the assumptions you made before showing me the change. Then list what you think I should have specified and did not.
Notice how much of that prompt is business knowledge. The event sequence requirement did not come from the code. It came from someone who knows the warehouse. If I do not know it, I go ask. The prompt is where that knowledge finally has to be written down, which is one reason writing good prompts makes teams better at describing their own systems.
The part that is not about code at all
Everything above is about getting good output. There is a separate discipline about what you should never put in the input, and it matters more in regulated work than anywhere else.
Never put live credentials into a prompt. That means tokens, bearer tokens copied out of a browser, passwords, API keys, connection strings, session identifiers. Not even short-lived ones. The reasoning is often that a token which expires in five minutes is harmless. Five minutes is not short. A model that has the token and an instruction can make a great many requests in five minutes.
The same applies to personal and regulated data. A stack trace pasted into a chat window, a console dump from a customer’s session, a row from a production table. The intent is usually benign: I need help understanding this error. The effect is that the data left the system you were protecting it inside.
So the rules I work by:
- Synthetic data. If I need to show the model the shape of a record, I make up a record that looks like the real one and is not. The model does not need a real customer to reason about a real problem.
- Non-production data. If synthetic is not enough, use data from an environment that is allowed to be shared.
- Know what the editor already sees. The most common leak is not something pasted. It is something that was already open. An editor assistant can read the files in your workspace, and a tab with a config file is part of that workspace. Close it or move it. This is one reason I care more about secrets management than about secret-scanning in prompts: the durable fix is that the secret is not sitting in a file the tool can read.
- Prefer the identity path over the credential path. If the platform supports managed identity or single sign-on, use it. A prompt can leak a string. It cannot leak a role assignment it never saw.
When it goes wrong anyway
It will. Someone will paste the wrong thing, or a log file will contain more than the error, or a workspace will have a file nobody remembered. The plan for that moment is worth writing before you need it.
Pause. Stop using the exposed credential. Not at the end of the ticket. Now.
Notify the owner. Every credential and every data set has someone accountable for it. That person needs to know within minutes, not at the next standup. This is the same instinct as the breach notices we all receive from vendors: they exist because someone had a defined path for telling the people who could act.
Revoke and rotate. Revoking is the only thing that makes the exposure stop mattering. Rotating is what restores the ability to work. Do both, and let the owner decide the order if there is any doubt.
Write down the route. If there was no path for this, that is the finding. Define one, name the people, and put it somewhere people will look.
The teams that handle exposure well are not the ones with the strictest prohibition. They are the ones where someone knows exactly what to do in the first ten minutes.
One thing tokenization does not fix
Tokenization is a good control and it removes a category of risk from the server side. It does not remove risk from the page. The scripts running in the browser still interact with the payment fields, and the code that handles that interaction is code like any other code. If it is generated, reviewed, and deployed like the rest of your front end, it needs the same treatment as the rest of your front end. Tokenization is not a reason to relax there. It is a reason to look harder there, because the assumption that the risk is solved can keep people from looking at all.
What this buys you
A prompt written this way is bounded. It has one task, it says what it must not do, it defines acceptance, and it asks for assumptions and open questions. That is a smaller surface than an open-ended conversation, and smaller surfaces are easier to verify.
Which brings up the thing I said at the start. A prompt is a program. Programs get tested. The next post is about the output side of that: why code that passes a casual look is not the same as code you can defend, and what the specific differences are.





Leave a Reply