
Discussion paper
Witchcraft
What a Programming Language Would Look Like If It Took AI Seriously
Introduction
"AI-native programming language" is a phrase that gets used a lot and almost always means one of two unremarkable things: a language that models happen to generate well, or a language with nice bindings to an inference API. Neither is native in any deep sense. JavaScript with a copilot is still JavaScript. Python with an agent framework is still Python. To the compiler, a call to a model is just a function that does something to the network and hands back a string — indistinguishable from any other side-effecting call. The model lives outside the language, and the language has no idea it exists.
Here's a more interesting question. What would a language look like if inference, memory, embeddings, and agents were primitives — first-class citizens of the type system and the runtime, the way integers and strings and arrays are today? Not wrapped in a library and called from a language that's unaware of them, but built into the thing itself, so the compiler actually knows when it's dealing with a model and can reason about what makes a model different from an ordinary function.
I've been poking at this through a deliberately silly-sounding concept language called Witchcraft. The name is doing real work, and I'll defend it later. But first, the thought experiment that gets you there — and the way that experiment, taken honestly, nearly destroys the whole idea before rebuilding it.
The counterfactual, and the trap in it
The provocation behind Witchcraft is: what if large-scale AI had existed before programming languages were designed? If intelligence were a primitive of computing from the start, what would have been built into the foundations instead of bolted on later?
Run that literally and it cuts your own throat. An AI-first world wouldn't have a high-level language at all. High-level languages exist for humans — assembly was a mnemonic over machine code, Fortran was a concession to the fact that people can't hold raw instructions in their heads, and the compiler's entire job is to throw that human-friendliness away and emit what the machine actually runs. An intelligence that doesn't think in source code has no more reason to exchange annotated source than two people have to swap each other's neural firing patterns. It would trade weights, embeddings, latent state — or compile intent straight to binary. The honest product of a truly AI-first history isn't Witchcraft. It's the absence of any readable language at all, because nobody who mattered needed to read.
That sounds fatal, and it would be — if the language were for the AI. It isn't. The moment a human has to stay in the loop — to specify, to constrain, to audit, to answer for what the thing does — a readable, checkable artefact becomes necessary again, for the human. So the counterfactual isn't a prediction. It's a scalpel. It strips away the assumption that these concerns have to live in libraries, and leaves you looking at what's genuinely new. Witchcraft, then, is not the language AIs would write for themselves. It's a human-authored language that makes AI a first-class primitive, for the collaboration layer where a person still has to read, constrain, and own what an intelligence does. Every design choice below serves that human reader, not the model.
Primitive or just sugar?
Before proposing any new primitive you have to face the cheap-trick objection: most "first-class" features are cosmetic — a keyword wrapping a library call, syntax sugar that compiles to exactly what you'd have written by hand. The same oracle keyword could be a genuine primitive or a thin alias for an HTTP client. If the distinction is going to mean anything, you need to be able to tell them apart.
Something is a real primitive, not sugar, to the degree that the type system treats it specially (the compiler reasons about what it consumes, emits, and how uncertain it is, rather than seeing an opaque handle); it changes what's statically checkable, catching at compile time errors a library would only surface at runtime; the runtime is genuinely built around it; it composes with the rest of the language instead of living in a walled-off idiom; and removing it would force every program in the domain to rebuild the same scaffolding by hand. That last one is the real test of a missing primitive: if every non-trivial program re-implements the same retry-and-validate loop, the same scoped memory store, the same embedding-similarity helper, that scaffolding is a primitive the language is missing. A construct that satisfies none of these is sugar. One that satisfies most is doing something a conventional language can't express.
Four things worth making primitive
With that test in hand, four candidates — each currently a library concern, each (I'd argue) now pervasive enough to earn elevation. Witchcraft gives them occult names, which I'll justify shortly.
The model as a value (oracle). Today you reach a model through a client object — a handle to a service. The native move is to make the model itself a value you can bind, pass, and invoke, with a type the compiler understands:
oracle muse = summon "llama3"
let response = muse.invoke("Explain quantum computing")
The win isn't brevity. It's that muse has a model type, not a client type. The compiler can know muse.invoke performs inference, that its result is uncertain and has to be handled as such, and that the call has provenance worth tracking. It can refuse to let you make an authoritative decision on a raw, undischarged inference result. None of that is available when the model is an opaque client.
Memory as a governed resource (memory). Right now memory is a vector database bolted to the side of an app, queried through calls the language doesn't understand. Make it a primitive with declared scope, retention, retrieval policy, and access rules:
memory customer_context:
scope tenant
retention 18 months
retrieval semantic + recency
audit required
Now a read outside the declared scope is a type error, not a silent data leak; retention and audit are enforced by the runtime, not hoped for in application code. As a library, all that governance depends on discipline, and is absent exactly when it matters most.
The embedding (embedding). Embeddings are already a basic unit of model-mediated software, yet no mainstream language treats them as a type — they're just arrays of floats, which means the most common embedding bug, comparing vectors from incompatible models or spaces, is invisible until it silently produces wrong answers. Make the embedding carry its space as part of its type and the compiler can refuse a comparison across incompatible provenance, the way a good type system refuses to add a length to a duration.
The agent (familiar). The most speculative one: the autonomous, persistent, goal-directed process. In practice today it's a loop in application code wrapped around model calls and tools. The native move is to make it a first-class schedulable entity with declared capabilities, permissions, and lifecycle — so that what it may see, do, and persist is part of the program's checkable structure, not an emergent property of a prompt. A defensible familiar is a bounded process, and it earns its keep only if it makes agent behaviour more legible than a hand-rolled loop, not less.
I should admit two of these don't survive scrutiny cleanly. A single oracle type may be too coarse to be honest — a deterministic zero-temperature classifier and a frontier model with tool access are wildly different things, and collapsing them into one type repeats the sin of the opaque client it replaces. And the familiar might not be a primitive at all but a composite — an orchestration of the other three plus tools — in which case elevating it is a category error. My most exciting-sounding primitive is my least defensible. Worth saying out loud.
The part that isn't a prompt library in a costume
Four primitives are only interesting if they compose, and the composition only earns the name "AI-native" if inference becomes the computation itself — not a value fetched by hand-written control flow. This is the line between Witchcraft and a fancy prompt library, and from a distance the two look identical, so it has to be drawn carefully.
Here's what a conventional "AI-native" example actually does:
urgency = classifier.invoke(msg) # model fills a value
if urgency == "routine": # human wrote the algorithm
draft = drafter.invoke(msg)
elif urgency == "critical":
escalate(msg)
The model fills a hole. The reasoning — the routing, the decision structure — is hand-coded. That's orchestration. Useful, and not the point: the AI is called by the computation, it isn't doing it.
Now the inversion. The hard part of triage is a judgement nobody can write as an algorithm: given a garbled, multi-issue, emotionally loaded message and a customer's messy history, what's actually being asked, how urgent is it really, and what should happen? You can't express that as if/elif. In Witchcraft it becomes a single typed inference region — a divine block — whose output type is the specification, and the model's job is to inhabit it:
oracle triage = summon "support-reasoner-v3"
memory tickets:
scope tenant
retention 24 months
retrieval semantic + recency
# the decision the model must produce — this type IS the program logic
type Disposition = {
issue: one_of { Billing, Outage, HowTo, Abuse, Unclear }
urgency: spark in 0..10
action: one_of { Draft(reply: glyph), Escalate(to: Team), AskClarify(q: glyph) }
rationale: glyph
}
familiar support_triage(msg: glyph, customer: tenant_id)
permits { read tickets, invoke triage, escalate }
embedding q = triage.embed(msg)
let history = tickets.nearest(q, k: 5) within customer
# inference IS the computation: the model resolves the whole judgement,
# constrained to inhabit Disposition. there is no hand-written branch tree.
divine decision: Disposition
from (msg, history)
using triage
with confidence >= 0.80
fallback escalate(msg, customer, reason: "low_confidence")
enact decision.action # runtime executes the typed action;
# Escalate/Draft/AskClarify are the only shapes possible
end
There's no if. The routing logic is the inference, bounded by Disposition. The human hasn't written the algorithm — they've written the space of acceptable answers and handed the reasoning to the model.
The obvious objection: isn't divine just sugar for "assemble a prompt, call the model, parse the JSON, validate against a schema"? If it were, Witchcraft would be a costume over prompt() and the whole thesis would collapse. Two mechanisms make the difference real.
First, the type constrains generation, not validation. Disposition isn't checked after the model speaks — it's enforced during decoding. The runtime compiles the output type into a generation-time grammar the decoder has to satisfy token by token, so (assuming the runtime enforces this correctly) the model cannot emit a value outside the type. urgency can't come back as "quite high"; action can't be a fourth hallucinated shape. Illegal outputs aren't rejected after the fact — they're unreachable. (The qualifier is real: constrained decoding depends on grammar support and the model interface, so this is a design intention the runtime must honour, not something that comes for free.)
Second, confidence and provenance are part of the value and flow downstream. divine doesn't return a bare Disposition; it returns one carrying its confidence and its provenance — which model, which retrieved history, which prompt lineage. The with confidence >= 0.80 clause is a typed discharge: below threshold the value never materialises and the fallback fires. The provenance rides along into enact, so the audit trail is structural, not logged by hand.
The clean test that separates real inference-as-primitive from a prompt library wearing types: if you deleted the type, would the computation at the moment of inference change? In a prompt library, no — the type is post-hoc validation, the model generated the same tokens either way, and stripping the type only changes whether you catch a bad output afterward. In Witchcraft, yes — delete Disposition and you remove the grammar constraining the decoder, so the model's actual generation changes: it's now free to emit prose, malformed actions, out-of-range numbers. The type isn't a check bolted on after the computation; it's part of the computation, shaping what the model may produce as it produces it. Pass that test and you've got a primitive. Fail it and you've got a prompt library, and you should call it one.
On the whimsy
Witchcraft's most conspicuous feature is its vocabulary — oracles, familiars, sigils, a runtime called WitchCore, a package registry called Coven. Easy to dismiss as a gimmick, easy to over-defend as essential. Neither's right, and the line between them is the actual design principle.
The whimsy does real work where it names something new. "Oracle" captures what "client" hides — that you consult it, that its answer isn't guaranteed, that it speaks with an authority it hasn't earned. Memorable words lower the cost of thinking about unfamiliar ideas, and a language is a culture as much as a grammar — Python, Ruby, and Perl all carry playful names and outlasted soberly-named rivals. The framing turns an arid question (should inference be a primitive?) into one a reader will actually engage with, and adoption follows comprehension.
But a metaphor that lights up a new idea obscures a familiar one. Theming the mundane — whilst for while, chant for print, summon for construction — costs more than it pays: a while loop is the same loop it always was, and renaming it just subtracts the recognition decades of convention bought you. Worse, blanket theming makes the whole language feel like magic, which quietly encourages you to treat its non-determinism as mystical rather than managed. An oracle that "just knows things" is a seductive way to avoid noticing that the model is frequently, confidently wrong. A metaphor that makes unreliability feel like enchantment isn't a hook — it's an anaesthetic.
So: the whimsy is a good front door and a bad uniform. Keep it for the brand and for the genuinely new primitives, where it makes novel constructs graspable. Drop it everywhere it re-costumes the familiar or makes the hard parts feel easy. Keep the name; let the plumbing look like plumbing.
What the compiler can't promise
A compiled AI-native language raises a hope it can't fully honour: that model-mediated programs could be checked the way ordinary types are. They can — up to a boundary. The compiler can verify structural properties: that an inferred value isn't used authoritatively without discharge, that an embedding comparison stays in one space, that a memory access respects its scope, that a familiar stays within its permissions. Those are exactly the runtime errors the primitives convert to compile-time ones.
It cannot verify the semantic ones: that the output is correct, the confidence calibrated, the retrieved context relevant, the agent's plan sound. Those are runtime, model-dependent facts. Nativeness reaches as far as the structure of the computation and no further. The trap is mistaking the structural guarantee for a semantic one — a program that type-checks is not a program whose model outputs are true. Treating the green build as correctness reintroduces the original sin, presenting an uncertain operation as settled, one level up and with more authority than before.
Why bother
The set of things a language treats as primitive has never been fixed — it accretes. Lisp made the list primitive; later languages made the string, the hash map, the coroutine, the async task first-class. Each one started as a library pattern and got promoted once it became so pervasive that library treatment caused more friction than it removed. Asking whether inference belongs in that set isn't exotic. It's the same question the field has answered repeatedly, aimed at the newest pervasive pattern. The bet might be wrong — maybe inference changes too fast to fix in a language and should stay a library. But it's the right kind of argument, and it's been made many times before.
The goal, despite the vocabulary, is to make intelligence less magical in software, not more. The question isn't how do we let a program call a model? It's: what must a language make primitive for the human who stays responsible for what the intelligence does — and have we earned the right to build it in?
Further reading
The closest existing family of languages to Witchcraft is probably probabilistic programming: systems such as Turing.jl, Church, Stan, and Pyro already treat inference as part of the programming model rather than as a separate external step. They do not solve the same problem, but they provide useful precedents for how uncertainty, sampling, and model-driven computation can be expressed in code.
Effect systems and effect handlers are also relevant, because they offer a way to treat a model call as a first-class computational effect: something the language can type, track, suspend, resume, retry, constrain, or route at runtime.
Finally, McCarthy’s 1960 Lisp paper matters because it shows the deeper pattern this essay is interested in: taking something that might otherwise remain a library convention and lifting it into the language itself. Witchcraft asks whether inference, model interaction, and AI-mediated execution deserve the same kind of elevation.
Witchcraft as a project
This article is the argument. The project page is where the language itself starts to take shape — primitives, syntax, compiler ideas, runtime notes, and the evolving design of an AI-first compiled language.
Discussion
Threaded comments below — sign in to participate. All comments are moderated.
Comments
Loading comments...