DSLs Enable Reliable Use of LLMs Domain-specific languages (DSLs) and abstractions enable reliable use of large language models (LLMs) by providing clear boundaries that guide code generation, as demonstrated by the Tickloom DSL for distributed systems. LLMs serve as partners to iteratively build DSLs and as natural language interfaces to use them, making the DSL a key source of truth for software systems. DSLs Enable Reliable Use of LLMs LLMs generate code incredibly fast, but to ensure they generate exactly what is intended, they need clear boundaries. Abstractions and Domain-Specific Languages DSLs provide a strong harness that guides LLMs right from the start. The example of Tickloom - a domain model and DSL for illustrating distributed system behavior - shows how we can use an LLM as a partner to iteratively build a DSL and as a natural language interface to use it. Such a DSL can act as the key source of truth for software systems in the world of LLMs. 14 July 2026 Contents The Limits of Upfront Specification TheLimitsOfUpfrontSpecification Design Is Discovered Through Implementation DesignIsDiscoveredThroughImplementation Domain Abstractions and DSLs DomainAbstractionsAndDsls Why DSLs work so well with LLMs WhyDslsWorkSoWellWithLlms Example: Using LLMs to generate diagram rich powerpoint presentations ExampleUsingLlmsToGenerateDiagramRichPowerpointPresentations Building the Semantic Model BuildingTheSemanticModel Example: Tickloom — a semantic model for distributed systems ExampleTickloomx2014ASemanticModelForDistributedSystems Even good abstractions help — without a DSL EvenGoodAbstractionsHelpx2014WithoutADsl Example: Building a DSL for testing distributed system scenarios ExampleBuildingADslForTestingDistributedSystemScenarios Two phases working with LLMs TwoPhasesWorkingWithLlms The DSL as the Source of Truth TheDslAsTheSourceOfTruth Modern LLMs possess an incredible capability. They can generate large amounts of code, and sometimes entire systems, from just a high-level natural language description. An important assumption here is that the 'intent' of what needs to be built is well articulated, using precise words that LLMs can map to coding building blocks. However, there are two important points worth noting: the limits of upfront specification, and how design is discovered through implementation. The Limits of Upfront Specification Building large systems involves a great many small design decisions, and these cannot all be known in advance or driven entirely from a high-level spec. A specification is at best a starting hypothesis: the real constraints, trade-offs, and edge cases are discovered iteratively, as we proceed with the implementation. We discussed this at length in an earlier article, where we called it Upfront Specification Impossibility https://martinfowler.com/articles/convo-llm-abstractions.html . The point is not that specs are worthless, but that the first one is a hypothesis to be revised, never a finished blueprint. The natural response is to iterate: refine the spec, generate code, review what comes back, and feed what we learn into the next round. That loop works well when each round produces a small, reviewable change. Design Is Discovered Through Implementation Reviewing code, particularly while we are still discovering the design, is not the same as writing it. While reviewing the generated code, we review through the chunks validating if it maps to our intent and looking for possible pitfalls. But reviewing rarely forces us to wrestle with the design decisions. Writing code, by contrast, forces us to think through concrete decisions—such as where a responsibility belongs or what boundaries should be exposed so the design can be extended further. It is in making those decisions that a design most fully reveals itself. The programming language and paradigm we code in shapes the design insight we get /articles/what-is-code.html . A functional design approach or an object-oriented design approach reveals different aspects of the design, along with idioms and patterns that are natural to the paradigm. So where do LLMs fit in? I see LLMs playing two roles. They are a great help while we shape the design and its vocabulary, acting as brainstorming partners to help us explore the design space and discover the right abstractions. Once the vocabulary is established, LLMs work as an excellent natural language interface to it. Domain Abstractions and DSLs A useful way to frame this is through Domain Driven Design http://martinfowler.com/bliki/DomainDrivenDesign.html . Its core insight is building a shared conceptual model of the domain in code, and then using that model - which DDD calls a Ubiquitous Language https://martinfowler.com/bliki/UbiquitousLanguage.html - both to evolve the codebase and to give the team a vocabulary to think and communicate in. Often, it is highly effective to build a domain specific language on top of that model: a constrained syntax for expressing the domain's concepts and operations. Seen this way, most development is the process of building a domain model and using it to evolve the system. The LLM plays two distinct roles depending on whether the domain model already exists. In this article, I will focus on how Domain-Specific Languages https://martinfowler.com/dsl.html DSLs work with LLMs. Why DSLs work so well with LLMs It is a common experience that DSLs work well with LLMs. PlantUML, Mermaid, and Graphviz are domain specific languages for visual modeling; SQL is a DSL for querying databases; Kubernetes YAML is a DSL for describing cloud infrastructure. These are not general-purpose programming languages — they are deliberately constrained, designed to express a narrow set of concepts in one domain. And it is no surprise that LLMs are remarkably good at generating Mermaid diagrams, SQL queries, or Kubernetes manifests from a plain English description. My observation is that DSLs make LLMs more reliable because they respond so well to a few in-context examples. A general-purpose language like Java offers lots of valid ways to express the same intent. A DSL strips the variation away. Giving the model a few examples is enough to reliably generate the correct syntax. It's worth noting that frontline models are already heavily exposed to PlantUML or Java fluent interfaces during training, so they aren't starting from scratch. It will be curious to see how smaller, more constrained models perform when tasked with a truly novel DSL. For an agent — an LLM running in an autonomous generate-and-check loop rather than a single shot generation — there is one more benefit. A DSL almost always ships with a deterministic validator: a parser, a JSON schema, a type checker, or a compiler. The agent can generate a candidate, run it past the validator, and repair it from the error, all without a human in the loop. Crucially, the errors are phrased at the level of the domain — “you cannot select an action before choosing a client” — rather than as a stack trace buried deep in generated code. DSL's toolset itself acts as an excellent harness. We will see it concretely in the Tickloom examples below, where the DSL's grammar is enforced by the host language's compiler and the resulting runs are checked automatically. It is important to note that this is not a one-size-fits-all solution. The advantage holds while the DSL stays small and constrained enough that a few in-context examples can convey its usage. There is also a real upfront cost in designing and maintaining the language and its semantic model. The payoff is therefore concentrated in well-factored, genuinely constrained DSLs backed by a validator. Example: Using LLMs to generate diagram rich powerpoint presentations LLMs make it really easy to build custom tools https://www.martinfowler.com/articles/exploring-gen-ai/16-building-custom-tooling-with-llms.html . While teaching distributed systems, I frequently need to create presentations which mostly have diagrams explaining distributed operations in a cluster. UML Sequence diagrams have been great for that but showing a full sequence diagram while explaining the flow of messages through the cluster is not very useful. I needed a tool to show a sequence diagram step by step in a powerpoint presentation. With the help of LLMs I was able to build a tool which processes a YAML describing the presentation structure with references to the PlantUML diagrams and generates a powerpoint presentation. The PlantUML diagrams are marked with steps, and the tool generates a separate slide for each step. This made it really easy to create diagram-rich presentations. This prompt generates the following PlantUML code with step markers: @startuml actor Alice box "Cluster" lightblue participant athens participant byzantium participant cyrene end box ' step Alice - athens: "title", "After Dawn" ' step athens - athens: save note right of athens state: title: After Dawn end note ' step athens - red x byzantium: "title", "After Dawn" ' step athens - cyrene: "title", "After Dawn" note right of cyrene state: title: After Dawn end note ' step athens - athens: isQuorumReached ' step athens -- Alice: Success @enduml I used it to create a series of slides in a powerpoint presentation. For doing that, I developed a small YAML specification to describe the presentation structure and the diagrams to be used in each slide. This allowed me to use LLMs to create presentations describing complex distributed systems concepts, without having to manually create animations on the slides. An example prompt to generate a slide YAML spec is as simple as following. This generates a slide spec YAML like following: - slide: title: "Quorum Write Example" diagram: "quorum-write" It's important to note that even if the prompt is saying create a slide YAML, it's not any random YAML spec. Because the tool to generate the powerpoint presentation and the YAML specification understood by the tool is used as a context in the prompt, the LLM is able to generate the correct YAML spec which can be directly used by the tool to generate the powerpoint presentation. The full YAML spec can be viewed at this Github repo https://github.com/unmeshjoshi/MADSPlantUMLSteps/blob/main/src/presentation/deterministic-simulation-testing.yaml Notice that the LLM played two different parts in this single example. First it was a co-designer — helping shape the step-marked PlantUML extension and the slide YAML on top of existing PlantUML tooling. Then, once that small DSL existed, it became the natural-language interface that turns an English request into a valid spec. We will come back to this division of labour at the end of the article. Building the Semantic Model The example we covered in the previous section was straightforward. The YAML was used as a carrier syntax, and I process its parsed syntax tree directly, effectively using the syntax tree itself as my Semantic Model https://martinfowler.com/dslCatalog/semanticModel.html though this couples the syntax to the execution semantics . But in more complex domains, like distributed systems, we need more complex semantic models to represent the concepts in the domain and the design decisions we have made in the codebase. Let's look at an example based on a small framework I built to quickly build and test distributed systems. Example: Tickloom — a semantic model for distributed systems Implementing distributed systems such as quorum-based key-value stores or consensus protocols like Raft and Paxos is a daunting task. Even if implementation is guided incrementally through prompts, specifications, or carefully constructed .md skill files, the asynchronous runtimes still expose an overwhelming space of possible implementation decisions. Threading models, networking patterns, storage coordination, retry behavior, and timing semantics all remain entangled within the generated code. The problem is not merely code generation complexity, but verification complexity. The resulting state space created by all possible interleavings across thread scheduling, network delays, process pauses, and clock skew becomes so large that systematically reviewing and validating correctness across all interacting behaviors is nearly impossible. This is the reason we see that Jepsen https://jepsen.io tests find bugs even in the most battle-tested distributed systems. This is exactly where a semantic model is beneficial. Tickloom https://github.com/unmeshjoshi/tickloom is a small framework I built to construct and test distributed algorithms. Its abstractions are not a generic runtime; they are a set of design decisions about how a distributed process behaves. Every node runs in a single-threaded tick loop : each call to tick advances a logical clock by one and processes pending work in a fixed, deterministic order network, then message bus, then process, then storage . Time is measured in ticks, not milliseconds. Messages are plain Java records. Coordination across replicas is expressed through a Replica base class that already knows about peers, broadcasts, and quorums. Threading, timing, network delivery are no longer open questions to be re-decided in every prompt. What remains for the algorithm author is the actual protocol logic. A quorum replica, for instance, is just a set of message handlers expressed in the framework's vocabulary. Because the framework supplies the vocabulary — Replica , quorumRequest , countResponseIf , MessageType , Handler — a prompt can stay at the level of the protocol rather than the plumbing: This high level description produces code like following: @Override protected Map