Making prompts code-like without making them look like code
I wanted prompts to behave like code without looking like code. Every syntax I was offered got rejected against that rule for ten weeks.
In March 2025 I spent two weeks trying to give prompts the properties of code while refusing to let them look like code, and every syntax I was offered got rejected against that one rule.
Start with what a prompt is, because the word has drifted. A prompt is the block of text you send a language model, and everything the model knows about your request lives in it. People treat prompts as instructions, written fresh each time, the way you would write a note to an assistant. That works and it does not scale, because the same request written twice produces two different runs, and nothing about the note tells you what order things will happen in or whether they will happen at all.
Code has the opposite properties. A program does the same thing twice. It has structure you can point at, scopes that say which names are visible where, and a defined order of execution. The appeal of making prompts more code-like is obvious once you have written a long one and watched it behave differently on Tuesday.
The reason people stop at the analogy is that the two things want opposite properties, and the tempting resolution is to smuggle a programming language into the text box. I did not want that. If the thing you write looks like a program, you have not made prompts better, you have made a programming language with an unusually chatty runtime, and the people I wanted to write these are people who currently write prose.
Motivation
The question underneath is narrower than "can prompts be code", and I wrote it down on the first day: "how can flow be predictable in a non deterministic system?"
A language model samples its output. Ask it the same question twice and you get two answers, and that variability is the reason it can handle the cases you did not anticipate. Any structure you add is a bet that you would rather have predictability than adaptability at that particular spot.
Two days in I stated the target as a quantity rather than an absolute, which is the sentence the whole design turns on: "what we really want is MORE determinism not absolute determinism."
Later, when I had a clearer view of what I was rejecting, the same target came out as a pair of opposites: "we don't want rigidity and we don't want verbosity or unpredictability, we want fluidity while keeping determinism."
That is a real position and it rules things out. It rules out a programming language, because that buys absolute determinism at the cost of everything the model is good at. It also rules out plain prose, because prose buys fluidity and gives no guarantees at all. What is left is a document that is mostly prose with a few places where you have said exactly what you mean.
Design constraints
The rule was that it must not read like code, and I applied it to every proposal I was given.
In March, offered a conditional: "i actualyl don't want any actual code elements in v1, the if statement u provided feels wrong."
In May, offered something more elaborate: "we don't want to make it look like code, focus on inline directives that are like structures that can be picked up on the parser, it must not read like code."
The day after that, more compactly: "remember our prompts don't look like code they look like notion documents."
Ten weeks and three rejections is what separates a constraint from a preference. A preference bends when the alternative is convenient, and this one did not bend once, including on the occasions when the offered syntax was better than anything I had. The positive version arrived in the same May session and is the whole design in one sentence: "it should be light stuff embedded into text."
Syntax design
The principle that made the syntax decidable rather than a matter of taste is one line from March: "we want to keep the inline deterministic prompting extensions natural language compatible, the grammar should make sense in terms of flow. so they have to be played off nouns, adjectives, adverbs, conjunctions, etc."
English already has slots. A noun names a thing, a modifier qualifies it, a conjunction joins two
clauses. If every construct in the language occupies a slot that a sentence already has, the result
reads as a sentence rather than as a sentence with machinery bolted to it. That is why the surviving
forms were @mentions and #directives: an @mention is a proper noun, and a directive behaves
like a modifier. Both can sit inside an ordinary sentence without breaking it.
Compare the alternative. An if block does not occupy a slot English has. It is a control structure
with a body, and putting one in a paragraph forces the paragraph to become a program around it,
which is exactly the outcome the constraint exists to prevent.
Control flow
Removing if leaves a real gap, since a document that cannot branch cannot do very much.
The replacement arrived in the same message as the rejection. Having said the if felt wrong, I
wrote what I thought a more abstract version would look like:
decision {
case: there are too many documents {
...
}
case: if I am tired {
...
}
}
The cases are sentences. Nothing in that block can be evaluated by a runtime, because "there are
too many documents" has no truth value until something reads the situation and judges it. An
earlier sketch of mine used arrows for the same shape, Urgent => ... and Just Interesting => ..., which is the same idea with less punctuation.
Later I mapped the programming constructs onto their equivalents to see whether the pattern held anywhere else. It did:
| Programming construct | Prompt-language equivalent |
|---|---|
| objects (pass by reference) | semantic objects |
| reactive programming | watch statements |
| pattern matching | decision (LLM does the pattern matching) |
| scopes | context scopes |
| functions | AI interpolated tool calling |
| macros | AI dynamic JIT interpolation |
The third row is the one that matters, and the parenthesis is doing the work. A conditional in a programming language evaluates a boolean: the runtime computes true or false and jumps accordingly. A decision hands the branch to the model, which reads the cases and picks. The runtime is not evaluating anything, so there is nothing for it to be wrong about, and the model is doing the only part it was ever going to do well.
That parenthesis is what turns the rest of the table into a design instead of an analogy table. Reactive bindings become watch statements because something has to notice when a value changed. Lexical scope becomes context scope because the question "what is visible here" is the same question whether the answer is variables or paragraphs.
Runtime interpolation
The construct with no equivalent in any programming language is the one I am fondest of.
Here is a program I wrote in March, in full:
@Load {5 - 20} latest messages from @ConversationHistory
@Telegram.SendMessage to reply to the user
Update @ConversationHistory
Look at all tokens I listed in @CoinsILike and check their prices on @Coinbase
@Telegram.SendMessage write {the best 5 performing tokens}
Update @ConversationHistory
Read that as English and it is a list of instructions you might leave for an assistant. Read it as a
program and every line has a defined effect. The @ names resolve to real things: a conversation
history, a messaging integration, a list of coins, a price source.
The braces are the interesting part. {5 - 20} does not mean a number between five and twenty
chosen at random, it means the model decides how many messages this situation calls for. And
{the best 5 performing tokens} is not a query in any query language, it is a sentence describing a
selection, evaluated at the moment the line runs.
In a programming language that construct is meaningless, because there is nothing in the runtime capable of reading a description and returning the thing described. In a prompt language it is almost free, because interpretation is the one operation you have an abundance of. Finding the constructs that are cheap in your medium and impossible in the neighbouring one is most of what language design is, and this is the clearest example of it I have found.
Execution model
The architectural fork arrived in the middle of March, and I wrote it down as an either-or: "we're exploring 2 models exactly, precompiled and on-the-fly interpretation."
Precompiled means parsing the document ahead of time into a structure with fixed evaluation rules, which gives you predictability and forces the writer to be precise before anything runs. On-the-fly means handing each part to the model as you reach it, which handles anything the writer left vague and guarantees nothing.
The resolution was to stop choosing: "we should execute the plaintext sections agentically, basically if the user hasn't reified it into structured document blocks. this means we get a mix of dynamic interpretation and structured predictability."
So the document has two kinds of content. Parts you have structured run predictably. Parts you left as prose get handed to the model when execution reaches them. The syntax tree holds both, as I noted at the time: dynamic text blocks that the agent runs, alongside structured elements.
What I like about this is that the determinism is opt-in and local. You are not choosing a mode for your program, you are choosing per paragraph, by writing more precisely where precision matters and leaving prose where it does not. The gradient from prose to program runs through a single document, and you walk along it as you learn what you actually wanted. My reaction at the time was that I had been handed a fork and had built a third road out of pieces of both, which is a nicer thing to have done than picking correctly.
The other timing decision that fell out of it: parts are not expanded ahead of time, they are expanded when execution reaches them, so a dynamic block sees the state that actually exists when it runs rather than the state predicted for it.
What it cost
The thing I keep from this is the sentence that arrived after a long session in the middle of March: "we don't need code as prompts we just need prompts to be code-like enough that people could treat it like code."
Code-like enough is a design target you can hit. It means the reader can predict what happens, the writer can point at a piece and say what it does, and the whole thing still reads as a document. It does not mean a compiler, and it does not mean guarantees, and trying for either of those produces something nobody wants to write in.
There is a constraint that comes with it and I wrote that down too, in capitals, because it is the part that is easy to lose: people have to be able to understand the semantics of how prompts execute, and that has to be easy to learn. A prompt language whose execution model takes a week to learn has no advantage over the programming language it was avoiding.
A way of deciding what to build survived too. Faced with retrieval, the standard
move is to design a retrieval subsystem. What I did instead was ask what retrieval eventually turns
into, and the answer is that it is always injected into a prompt in the end. So the language did not
need retrieval. It needed a way to name a source inside a sentence, which is what @mention already
was. Asking what a feature eventually becomes, and building for that instead of for its category, is
a move I have used on every project since.