6 min read

Use LLMs to build ideal tools (instead of products)

or "DSLs for fun and profit"

TL;DR: Here I propose an alternative way to use LLM agents in the Software Development Lifecycle: have them build your developers' ideal tool(s) rather than the end product. I propose this could lead to significant productivity boosts, lower operating cost, and higher reliability. Although I focus on coding here, this idea applies equally well to other creative and problem solving tasks — from engineering, to art, to design. Why wouldn't an artist try using an LLM to generate their ideal Photoshop filter or tool?

Anecdotally, pure “vibe coders” appear to operate somewhere around 1x productivity — their apparent rapid progress is often offset by technical debt. (In fact, some less experienced developers may see productivity drop below 1x!) But developers who know about architecture and design and can optimally prompt LLMs may see much bigger productivity boosts. The challenge is that human language lacks the precision that these more advanced developers require.

LLM agents excel at automating the mundane or repetitive aspects of coding. Ideally, this should free engineers to focus on the more creative and challenging aspects of software development like design and architecture. And in practice, many advanced developers are already seeing this shift: the better LLM agents frequently pause to ask developers to choose among architectural alternatives or clarify requirements before generating code. (This points to a future where there is a deeper transformation in the software development lifecycle—one I’ll explore in another post.) Here, I’ll merely outline a concrete step toward that future: leveraging LLMs to replace ambiguous human language with precision-engineered domain-specific languages.

From Imprecise Prompts to Precision Languages

Today, developers interact with LLMs primarily in two ways:

  1. Product Generation from Natural Language (Figure 1, panel A): Developers describe end products in human language and let the LLM produce the code. At its extreme, often involving many iterations, this is known as “vibe coding.” It can yield impressive results for straightforward or "run-of-the-mill" products, but breaks down when innovation, scale, or precision are required. The gap between intent and implementation leaves room for architectural drift and miscommunication.
  2. Augmented Assembly (Figure 1, panel B): More advanced developers mix natural language with explicit architectural constraints, directing the LLM to assemble well-defined components. This method reliably elevates strong developers to something like 10x productivity.

I propose the time is ripe to introduce a third paradigm: leveraging domain-specific languages (DSLs) as a means to segment and boost the LLM-driven development pipeline. Here, developers use an LLM to generate a purpose-built DSL tailored to the business problem. The DSL reifies constraints and domain knowledge, which accelerates development and improves clarity.

What exactly is a DSL? You’ve likely encountered some form of domain-specific language in your day-to-day experiences: recipes, ordering fancy coffees at Starbucks, SQL, or Terraform all use a constrained, precise syntax to reduce ambiguity and accelerate implementation. Think of them like a shorthand custom-made for the task. Regardless of the target domain(s) —UIs, APIs, machine learning pipelines, databases—the idealized goal of a DSL is the same: empower developers to express business logic in the most direct, fit-for-purpose code possible.

It is worth noting that the developers who drive the design of the DSLs may be different from those who actually use them.

Bridging the Tooling Gap

DSLs are inherently more constrained than general-purpose languages, which makes them more precise, concise, and expressive within their domain. They encode institutional knowledge and eliminate entire categories of bugs. So why isn't everyone already using them? Historically, building a robust DSL required custom parsers, compiler tooling, or deep compiler theory. These barriers limited adoption to computer scientists and large engineering teams. Another impediment to adoption is trust and testability: most DSLs are not designed to implement testing within the language itself. So developers end up using general purpose languages, even though they might be not particularly well suited to the task at hand.

But what if you could eliminate those barriers? This paradigm, which I've codenamed "Diesel," is tooling in the form of prompts and processes to automatically generate, validate, and deploy DSLs that are rigorously testable by design. The result is precise, domain-optimized languages that developers can comfortably and efficiently target.

Demonstration 1: Accounting DSL

To illustrate this approach, I've generated a simple example DSL. For this demonstration, we chose a target of Scala 2 due to its robust support for internal DSLs, powerful reflection capabilities (enabling dynamic UI generation), and seamless access to the Java ecosystem. While the core principles apply to Racket, LLVM frontends, or other targets, Scala provides an immediate, practical proof of concept. (But see here for this example ported to Racket.)

Rather than reproducing the implementation here, I encourage you to explore the fully working example in the README:

GitHub - LighthillCorp/demo-accounting-dsl
Contribute to LighthillCorp/demo-accounting-dsl development by creating an account on GitHub.

The repository demonstrates how the Diesel-generated domain-specific accounting language works, complete with type-safe validation, automated internal test generation, and a clean, readable syntax that maps directly to financial operations. Through extending this language, a developer can be empowered to define business rules in minutes instead of days, while being assured of correctness through built-in testability and deterministic (non-agentic) implementation.

Some example code written in the DSL:

CREATE   ACCOUNT "checking"
CREATE   ACCOUNT "savings"  AS SAVINGS
DEPOSIT  AMOUNT 1000.00 INTO ACCOUNT "checking" 
DEPOSIT  AMOUNT  250.00 INTO "savings"
WITHDRAW AMOUNT  200.00 FROM "checking"
TRANSFER AMOUNT  300.00 FROM "checking" TO "savings"

SET INTEREST OF ACCOUNT "savings" TO 0.05         // 5% per year

PROJECT BALANCE OF ACCOUNT "savings" AFTER 10 YEARS;

In subsequent blog posts I'll demonstrate other possible DSL designs. The figure in this article shows an even higher level possibility:

Display new window
Request stock symbol from user
Train new ML regression model x
On completion of x 
  Email excel summary to user
  Send SMS reminder to user

A Critical Role for Self-Testing in DSL Adoption

Trust in a domain-specific language is built through verification. When a DSL can test itself, it transforms from a theoretical abstraction into a reliable, production-ready tool that domain experts can confidently delegate to.

Traditionally, DSL validation has been outsourced to the underlying implementation language. While the host language defines the DSL’s semantics, its test suite almost always lives in a completely different syntax and paradigm. This forces domain experts to translate their business logic into foreign code just to verify correctness. Experts either become polyglots to validate their own tools—or they operate in the dark, hoping the implementation matches their intent.

Self-testing eliminates this friction entirely. By embedding validation directly into the DSL’s execution model, the domain expert can verify operations, constraints, and edge cases using the language they already understand. The DSL checks itself against its own rules, ensuring that every transformation, calculation, or business policy executes exactly as specified. 

This approach delivers some concrete advantages:

  • Preserves domain fidelity: Tests are expressed in business terminology, not implementation artifacts
  • Eliminates the polyglot barrier: Experts validate tooling behavior without learning a secondary language
  • Accelerates iteration: Feedback loops close instantly within the domain context
  • Guarantees correctness by construction: The DSL’s semantics and its validation layer remain perfectly synchronized

When a DSL is self-testing, it stops being a black box and becomes a trusted collaborator. Domain experts spend less time debugging tooling misinterpretations and more time refining their business rules. The cognitive load drops, confidence scales, and the productivity gains promised by DSL-driven development actually materialize.

Diesel ensures this capability is foundational to the architecture. Every generated DSL ships with first-class, self-contained testability by design, so domain experts can verify, iterate, and deploy with absolute confidence.

Conclusion

Engineers strive to express complex ideas with precision, yet prompting LLMs with natural language is inherently ambiguous. Consider a simple prompt like “get the average temperature for all days in July.” Is it an average for each day, or an average across all days? Does that include today? Which timezone? How should missing data be handled?

DSLs improve over LLMs by resolving these ambiguities and prioritizing:

  • Determinism – predictable, repeatable outcomes
  • Testability – built-in validation and verification paths
  • Readability – terminology that typically terse and mirrors the domain
  • Targeted expressiveness – powerful but within clear boundaries
  • Controlled scope – limiting unnecessary complexity

Developer productivity ultimately depends on one metric: the quality of "impedance matching" between human intent and the resulting code. Every layer of ambiguity, translation, or manual boilerplate drains efficiency. I think we can shrink that gap by automating the creation of precise, testable DSLs that align perfectly with domain requirements. The result is a fundamentally tighter loop between design, implementation, and verification — and an elimination of dependence on external, expensive, stochastic LLM agent tooling.