Agent design

Give Your AI Agent a Domain-Specific Language

If you're using AI for something complicated and seeing inconsistent success, a DSL might help you unlock the capability you seek.

AI-powered financial modeling finally works now. One of the key ingredients that made that possible for us is we gave the LLMs we use a domain-specific language (DSL) in which to operate. If you're using AI for something complicated that needs to be done repeatedly and are seeing limited or inconsistent success, a DSL might help you unlock the capability you seek.

Why a DSL?

LLMs currently have two major constraints:

  • They have amnesia: Unless you train your own model on your particular domain, it must re-ingest any information you want to give it related to your domain every time you talk to it.
  • They are context-constrained: They can only manage so much information at one time. The more information they have in working memory, the worse they perform. "Information" includes tokens spent on thinking.

As such, "context engineering" has become a vital practice to extract performance out of LLMs. The less complexity the agent needs to deal with, the better we can expect it to perform, both in terms of accuracy and speed.

It therefore follows that we should remove as much "incidental complexity" (the extra difficulty, overhead, or friction that does not come from the actual problem being solved) from an LLM's purview as we can. Creating a DSL for your agent to use is one strategy for accomplishing this.

Case Study: Financial Modeling

Financial modeling is a hot area for AI tools. Financial models are typically built in Excel, so naturally the first step to bringing AI to bear on the problem has been to sprinkle some AI onto Excel in the form of plug-ins.

We think this is a losing approach because it forces the LLM to deal with excessive levels of unnecessary complexity by forcing it to express financial model logic in Excel's cell-by-cell data format, among other reasons.

Instead, we made a DSL for specifying financial model logic so our agent can operate at a higher level, and not worry about Excel mechanics at all. To walk through a specific example, below is an extremely common financial modeling pattern called a "hardcode carry-forward":

A hardcode carry-forward row in Excel with hardcodes in 3Q26 and 3Q27

The idea is that you enter a hardcoded assumption in any period, and that value is flatlined (carried forward) into subsequent periods until a new hardcoded assumption is entered, which itself is carried forward, until we reach the end of the forecast range. In the above example, we have hardcodes entered in 3Q26 and 3Q27, and full year total columns that sum up the quarters of each year.

Consider what an LLM operating in Excel needs to do to write such a line, compared with what an LLM has to do using Modeloptic:

Writing the row via an Excel plug-in 9 steps
  1. Identify the sheet on which to write
  2. Identify the row number in which this row should be written
  3. Identify the column in which to write the row's label, and write it
  4. Identify which columns correspond to which periods, and remember the mapping between them (eg remember that 1Q26 is column C, 2Q26 is column D, etc). Correctly identify that columns G and M are year totals and column H is a blank spacer column
  5. Identify this as a hardcode carry-forward pattern, and begin mapping that knowledge to this particular situation
  6. Decide that a hardcoded 25 belongs in E4, a hardcoded 50 belongs in K4, find in its knowledge that hardcoded inputs should carry blue font, and write the values to the two cells
  7. Decide that 1Q26 and 2Q26 should both have hardcoded zeroes that should also be blue font, identify that those are cells C4 and D4, and write them
  8. Decide that the remaining quarterly columns should reference the prior quarter, construct the proper formula addresses for each, remember that when crossing a year boundary the cell to reference is not immediately adjacent but is instead 3 cells over, and write them all (F4 writes "=E4", I4 writes "=F4", etc)
  9. Recall that columns G and M are year totals, figure out that this means they should each sum the 4 preceding cells in the row, recall and decide that the "sum" function should be used, construct the two formulas (G4 writes "=SUM(C4:F4)", etc), and write them
Writing the row in Modeloptic 3 steps
  1. Identify this as a hardcode carry-forward pattern
  2. Recall in its system prompt that it has a tool available that will write this pattern, and read the tool description to figure out how to use it
  3. Call the tool with the proper arguments, which is roughly:
    operation="add logic row" forecast-option="hardcode carry-forward" label="# of New Subscribers" values="3Q26:25, 3Q27:50"

In Excel, that's a ton to manage to write a single simple row. In our DSL, it's one function call, and the rest is handled by code outside of the LLM's purview. By shortening the distance between the "hardcode carry-forward" concept and the actual execution of representing it in the data, we are able to get much better performance out of the LLM. Instead of wasting finite context on cell addresses and low level Excel mechanics, the agent can operate much more in the realm of concepts.

Making a DSL for Your Domain

The main reason to give your agent a DSL is to make its job as easy as possible. The easier its job is, the more likely it do it correctly, and the faster it will perform.

A DSL can make your agent's job easier by giving it less to think about, and by removing work from its purview altogether. Execute the deterministic parts of the task with software; use the LLM only when judgment is needed. Don't bog down your agent with execution details; let it focus on the higher level concepts.

To make one thing clear, we're not just talking about giving your agent more tools that it can call (although tools are probably the best way to allow your agent to interact with your DSL). We're talking about finding the right abstractions to make the distance between thought and result as short as possible. In that sense, this exercise is not all that different conceptually from doing good software engineering or product development.

Here are a few simple ideas:

  • Say you want your agent to send customer invoices via email under certain circumstances. Don't make the agent interact with SMTP (the raw email sending protocol), give it a send_invoice tool
  • Say you want your agent to read salespeople's inboxes and update the CRM with anything missing. Don't make the agent interact with a SQL database directly, give it an update_status tool
  • Say your agent writes tailored customer contracts. Don't just have it output free form text every time, give it an insert_section tool that lets it call up specific pre-written provisions

Tactically, you can expose your DSL to your agent by:

  1. Defining tools it can call
  2. Documenting the arguments to those tools in some combination of the tool descriptions, the system prompt, and skills

Which is optimal will depend on your use case, but the less frequently a tool will be used, the stronger the case for documenting it in a skill.

Beyond reducing complexity, there's another big advantage that a properly written DSL can help enable: A structured change log. If you have your agent interact with your system via a defined list of operations that your code then executes, you can provide your users with a full list of every action the agent prescribed on their behalf. Contrast that with an agent that does its own haphazard editing of data directly.

Every domain is going to have its own nuances; I hope this post has helped spur some ideas for addressing your own.

Don't miss any updates
Join us to receive the latest news, articles, and updates from Modeloptic.