The Prompt Is Not the Source of Truth

Published

The prompt is not the source of truth.

The speed at which LLMs generate code is remarkable; the trustworthiness of what they generate is the real problem. When the generation space is near-infinite — when any intent can be expressed in ten thousand valid ways — no prompt, however careful, can pin the result down. Trust cannot be built on better prompts. The problem space has to be narrowed at its root.

Abstractions and domain-specific languages provide exactly that mechanism. A DSL is not a supporting tool; it is a structural set of reins, constraining the LLM in the right direction from the outset and making the result checkable after the fact. This article is about how DSLs work with LLMs — and why, in the age of LLMs, the DSL may become the true source of truth for a software system.

The spec is a hypothesis, not a blueprint

Building a large system involves an enormous number of small design decisions. None can be known in advance; none can be driven entirely from a high-level specification. A specification is at best a starting hypothesis: the real constraints, trade-offs and edge cases are discovered iteratively, in the course of continued implementation. The first spec is never a finished blueprint. It is a guess awaiting correction.

The natural response is to iterate: revise the specification, generate code, review the result, feed what you learned into the next round. That loop works — as long as each round produces a small, reviewable change.

Reviewing is not designing

There is a crucial distinction here: reviewing code is not the same as writing code. Reviewing generated code, we verify section by section that it matches the intent and look for traps. But review rarely forces us to wrestle with a design decision.

Writing code does. It forces us to decide where a responsibility belongs, which boundary has to be exposed for the design to extend further — and it is in making those decisions that the design reveals itself most fully.

Code serves two distinct but intertwined purposes: it is instruction for a machine, and it is a conceptual model of the problem domain. A well-designed codebase is the concrete expression of a domain’s vocabulary — the very vocabulary the LLM will later be asked to write in. The language and paradigm we choose also shape the design insight we get: a functional approach and an object-oriented approach reveal different facets of a design, along with the idioms and patterns that follow naturally from each.

Two roles for the LLM

Within this frame, an LLM plays two distinct roles:

  • Co-designer: while we are shaping the design and its vocabulary, the LLM is a brainstorming partner, helping explore the design space and find the right abstractions.
  • Natural language interface: once the vocabulary is established, the LLM becomes an excellent entry point to it, turning natural language requests into precise domain expressions.

These two roles correspond to different phases of work. We return to that division below.

Reins, a gate, and the words of the domain

A useful lens is domain-driven design. Its core insight: build a shared conceptual model of the domain in code — DDD calls it the ubiquitous language — and use that model to evolve the codebase, giving the team a vocabulary to think and communicate in. A DSL is a constrained syntax built on top of that model: a language for expressing the concepts and operations of the domain, and nothing else.

And DSLs pair extremely well with LLMs. The evidence is everywhere. PlantUML, Mermaid and Graphviz are DSLs for visual modeling; SQL is a DSL for querying databases; Kubernetes YAML is a DSL for describing cloud infrastructure. None of these are general-purpose languages. All are deliberately constrained, designed to express a narrow set of concepts in a single domain. LLMs are particularly good at generating valid output in such languages from a natural language description.

Why? Three structural reasons.

  1. A vocabulary with the variation removed. A general-purpose language like Java offers many valid ways to express the same intent. A DSL strips that variation away. What remains is a narrow set of valid expressions — small enough that a handful of in-context examples conveys how to use it, and familiar enough that frontier models, which have seen common DSLs extensively during training, are not starting from zero. The quality standard of a DSL’s vocabulary is simple: the fewer examples needed to convey it, the more reliable the generation.
  2. A gate that never blinks. A DSL almost always comes with a deterministic validator: a parser, a JSON schema, a type checker or a compiler. For an agent operating in an autonomous generate-and-check loop, this is a decisive advantage. The agent produces a candidate, runs it through the validator, and repairs it from the error message — with no human in the loop. Built as an internal DSL, the host compiler validates for free: malformed output comes back as a compile error pointing precisely at the offending location, rather than as a surprise at runtime.
  3. Errors that speak the domain’s language. Validation errors from a DSL are expressed in the vocabulary of the domain, rather than buried in a stack trace deep inside generated code. That makes the agent’s self-correction loop far more precise and effective. The error message is not a wall the agent must climb; it is a direction.

Candor is required: none of this is one-size-fits-all. The advantage holds only while the DSL stays small and constrained enough that a few in-context examples convey how to use it. Designing and maintaining a language and its semantic model carries a real up-front cost. The returns concentrate in DSLs that are well structured, genuinely constrained, and backed by a validator.

Under the syntax: the semantic model

In more complex domains, syntax alone is not enough. We need a richer semantic model to represent the concepts of the domain and the design decisions we have made in the codebase.

The semantic model is itself a form of context. The concepts a prompt refers to exist in the codebase as concrete types, so the LLM is not inventing a threading model or a networking layer — it is filling in domain logic on a fixed, well-understood substrate. Threading, timing and network delivery stop being open questions to be re-decided in every prompt. What is left for the developer is the actual domain logic.

The lightweight version

A DSL sits at one end of this spectrum, and it is not easy to build. Before setting out to create your own language, note that a clean set of abstractions is a lightweight version of the same idea. The named types and methods of a library are already a vocabulary the model can ground itself in. Good abstractions — not only DSLs — pair well with LLMs precisely because they narrow the state space that can be explored.

This is the value of abstraction itself, not of syntax.

The loop beneath the two roles

A clear pattern emerges from all of the above: the LLM contributes in two distinct ways, in two distinct phases.

Phase one: designing the abstraction. Here the LLM is a brainstorming partner, not a code generator. The design decisions that make up a semantic model cannot all be specified in advance — we discover the constraints, trade-offs and edge cases while implementing. This phase is inherently iterative and feedback-driven: propose a structure, try it against real cases, see where it becomes awkward, feed what you learned into the next round. An LLM accelerates that loop — sketching alternatives, critiquing a design, porting an idea from one language to another — but the human stays firmly in the driver’s seat, because these are exactly the decisions that need to be understood and owned.

Phase two: the DSL as a natural language interface. Once the abstraction is in place, an English description maps almost directly onto the defined vocabulary. The LLM is a reliable generator precisely because the abstraction provides both the context that grounds the prompt and the reins that check the result.

Where the source of truth lives

There is a growing tendency to treat the prompt as the primary source of truth. A well-designed DSL changes that dynamic fundamentally.

One of the key advantages of a DSL is that the generated program itself tends to become the artifact humans maintain. Because a DSL is dense, expressive and essentially free of boilerplate, it captures the core intent of the solution in a form that remains readable long after it was generated. If that output needs to change later, there is no need to retrieve the original prompt and regenerate everything — the DSL itself carries enough context for the LLM to understand the intent and work with it.

The durable asset is not the prompt; it is the DSL and its semantic model.

The strategic investment

The synergy between DSLs and LLMs is, at heart, a strategy for managing complexity. The generative capability of an LLM is formidable, but the larger the generation space, the harder trust is to establish. By narrowing the vocabulary, providing deterministic validation, and expressing errors in the language of the domain, a DSL turns an LLM from an unpredictable code generator into a predictable, verifiable, iterable domain tool.

Building abstractions and DSLs is itself a design activity — it takes iteration, feedback and human judgment. The LLM is an accelerator in that process, not a replacement. And once a DSL is in place, its value outlasts any single generation: it becomes the long-term source of truth for the system, capturing intent in domain vocabulary and providing a solid foundation for future evolution.

In the age of LLMs, building a good DSL is not a luxury. It is a strategic technical investment.

Back to Research & Insights