NUVOLIC (a blog)

Why AI needs a new programming language

A programming language, but not for building software.

Mon, Jun 01 - Written by: Arnau Gómez

Engineers at Vercel built Zero, a systems programming language designed to help AI build more reliable low-level software: compilers, HTTP proxies, CLIs…

While I was happy to hear the news, it shocked me to see how different Vercel’s idea of “a programming language for AI” is to mine!

I also think we desperately need a programming language for AI. “We” = the AI engineering community. The developers who build and deploy chatbots, AI assistants, data processing workflows, and all kinds of AI-enabled applications. AI in the wild.

A programming language, but not for building software

Let´s call this new programming language “Nuvo”.

Nuvo is a language that serves as the interface between AI agents and their environment.

Every tool the AI can use is available as a code API. Instead of calling one tool or MCP at a time, AI writes small scripts in Nuvo. These scripts can combine multiple tools to execute complex tasks efficiently.

The runtime is secure by default. If a tool requires user approval, execution can be paused and resumed at any time. This is because durable execution is a built-in feature: the program’s state can be easily stored as a JSON object.

Similarly, access to the network and other APIs is denied by default, and the permissions are configurable. The language’s tooling can auto-generate a prompt that tells AI about the available tools. There is also a built-in escape hatch in case the script produces long-running computations or infinite loops.

The runtime does not require an operating system: it is a TypeScript library that can execute Nuvo programs inside the TypeScript runtime. A runtime inside the runtime 🤪.

The idea is very early, I’m even thinking of names. If you have name ideas, message me on Twitter (@nuvolico).

Why tool calling is not enough

Coding assistants are the most successful use case of AI. But LLMs are similarly capable of automating boring tasks in other sectors of knowledge work: education, legal, accounting, etc.

So far, automations in these other sectors are done with 2 techniques: tool calling and MCPs. In tool calling, the AI is presented with a list of possible actions called “tools”. When the AI generates its response it can include an instruction to perform a certain action, like fetch the homework assignment of a student in a learning management system. This instruction is referred to as a “tool call”.

User: Create a report with every task that is not completed yet.

Assistant: I'll fetch the tasks and build the report.
<tool_call name="readTasks" />

MCPs can be thought of as collections of tools that allow AI to connect to an external system. For example, the MCP of a task manager app exposes a set of tools that allow AI to retrieve and manage your tasks.

If you combine a large language model with a list of tools, you create an AI agent capable of interacting with its environment. It is widely believed that tool calling is the way to create AI agents capable of complex tasks. In practice, there are many problems:

Tools are inefficient

Let’s tell our agent to create a report that combines all tasks that are not completed yet.

With tool calling, the AI model has to take many steps. First, it generates a tool call for the readTasks tool.

<tool_call name="readTasks" >
 <content></content>
</tool_call>

Then, it reads all the tasks. This information is loaded into the context window.

<tool_result name="readTasks" >
1. Review onboarding copy
2. Send invoice to Acme
3. Prepare launch checklist
4. Follow up with vendor
... 18 more items
</tool_result>

Then, it calls the createReport tool. As we mention, every time the AI calls a tool it has to generate its content. So, to create the list, it has to generate the content of the items again.

<tool_call name="createReport">
<content>
Open tasks

1. Review onboarding copy
2. Send invoice to Acme
3. Prepare launch checklist
4. Follow up with vendor
... 18 more items
</content>
</tool_call>

A human would not do it this way. This would be equivalent to copying the text of each task by hand, or typing them word for word.

AI models do not complain about copying by hand, but they suffer in other ways. First, LLM usage is billed per token, so reading and generating long sequences of content is expensive. Moreover, LLMs are probabilistic machines so they may not copy the content exactly. A study by Microsoft showed that even the most capable models have a likelihood of corrupting the documents they work with.

By contrast, if a human developer could write code to automate this task, it would look like this:

const tasks = await readTasks();
const openTasks = tasks.filter((task) => task.status !== "done");

await createReport({
  title: "Open tasks",
  items: openTasks,
});

Cleaner and shorter than tool calling.

Tools are not flexible enough

My example above is contrived: you could enhance the readItems tool with a createdAtAfter parameter to filter by creation date.

const tasks = await readTasks({ createdAtAfter: "2026-01-01" });

However, in real-world deployments, users employ agents as a way to search and manage tasks of any kind. This opens many questions: should users be able to filter by all properties? Should each property have a filter for values greater than, equal to, and smaller than a certain value? How are filters combined? Tool configuration gets more and more complex.

Too many tools confuse the model.

Studies show that a model’s performance decreases with the number of tools. An excessive number of available tools makes it more likely for the model to use the wrong tool. This contrasts with the growing of capabilities that are expected from AI assistants nowadays.

The solution is code?

It´s not a new idea: companies like Cloudflare and Anthropic have arrived to the same conclusion. Models are competent at writing code, so let´s allow them to write short programs that perform the tasks they’d otherwise do with tool calls.

Crucially, the goal of this code is not to build software. It is not application code, built to be enjoyed by users. It is throwaway code. It´s a script that runs once, for one user, to perform a single task. It’s code that no human will likely ever read.

But when looking at the existing scripting languages, most of them lack the features that would significantly help AI:

  • Simplicity and token-efficiency: some programming languages are more verbose than others. The same program can take twice as many tokens to generate in one language than in another, which makes AI slower and costier.
  • Secure runtime: programming languages were built to give power to the developer, so they have a large number of primitives to interface with all parts of the operating system. Here, we want to restrict the AI’s actions to what’s allowed by the user and the organization, while preserving the flexibility that programming languages bring.
  • Granular review and approval: no programming language was designed with user approval built-in, and it shows. For example, coding assistants like Claude Code struggle to create a permissions system around bash, and the system often breaks when there are compound commands.
  • Durable execution: because AI needs to pause and wait for approval, the program’s state needs to be captured, stored, and resumed at any time.
  • Type safety: catches errors ahead of time, giving AI feedback to improve.
  • Rich context: the tooling should easily auto-generate prompts that teach the AI how to write scripts in that language, and what actions are allowed
  • Portable environment: instead of requiring complex infrastructure, scripts should be able to run inside the runtime of the server process itself. This would adapt to the growing demand for simple AI agents that run in serverless environments.
  • Instrumentation: built-in primitives for logging and tracing the actions realized by the AI by executing the script.

Should we just use bash?

There is a strong case for using bash, instead of inventing a new language. AI models are trained on large amounts of bash, so they’re very capable at it.

However, bash feels like an intermediate state towards better tooling. Consistently, bash has been the go-to tool for OS administration. It is the gate to powerful commands that can edit thousands of documents in one go, but also nuke the file system and reveal sensitive information.

Granular permissions and security are not a built-in feature of the runtime: instead, they have to be built on top. Bash assumes access to the file system and the OS, which requires virtualization. However, there are interesting projects like just-bash that allow developers to run bash commands in a secure, simulated environment that’s built on top of the TypeScript runtime itself.

Overall, I’m not sure what solution will prevail: either a new language, designed for AI, or bash. Both options come with challenges. But there are very good reasons to try. I wish at least one of these two solutions succeeds.

Why we need this

We owe our users an excellent experience: fast, affordable AI services that make their lives meaningfully better. As AI systems become more autonomous, we should expect a rigorous permission system that protects sensitive data.

We are accountable for our environment, too: AI data centers consume a large number of resources and cause an impact to society and the economy. By building more efficient systems, we minimize AI’s impact while preserving its benefits.

Finally, the AI engineering community should not blindly accept the solutions that the labs give us (tools and MCPs). Instead, we shoud strive to innovate, and develop better solutions that move the industry forward.

In the Middle Age, priests copied the Bible by hand. The invention of the press automated this process and caused a revolution in our culture. Nowadays, we’ve invented powerful AI models, but we’re telling them to copy content by hand again. So we need a programming language for AI. Not to build software, but to change AI engineering forever.