Loading…
gathering yarn balls…
Cat Game Research
Frontier
Dev-log transmissions from the expedition

Compiler, Interpreter, Script — and Why BAML Solves the Wrong Problem

The three foundational translators, then an anti-case study: BAML markets itself as the anti-slop language for agents, but every guardrail it sells already exists in tools we have had for decades. A new language is the heaviest possible fix for a discipline problem.

There are three things that turn the text a human writes into something a machine will actually do: a compiler, an interpreter, and a script. They are not fashions. They are the load-bearing wall of every program, including the ones agents now write for us. This post does two jobs. First, it states the difference plainly, because the difference keeps getting blurred in marketing copy. Second, it uses BoundaryML's BAML as an anti-case study: a tool that sells itself as the cure for agent slop while being, mechanically, more slop. The argument is not that the BAML team is bad at engineering. It is that they are solving the wrong problem, and the wrong problem is expensive.

3
Translators
Whole
Compiler reads
Line
Interpreter reads
Host
Script needs

The three translators

A computer does not read source code. It reads machine code, the 0s and 1s that map onto CPU instructions. Everything a human writes in a higher-level language has to be translated down. The translator is the part of the stack that decides how that translation happens, and the choice is not cosmetic: it changes how fast the program runs, when you find out it is broken, and how much memory the toolchain itself eats. GeeksforGeeks frames the whole field as language processors, of which the compiler and interpreter are the two high-level cases and the assembler is the low-level one. (Language Processors: Assembler, Compiler and Interpreter)

Compiler
Reads the entire source program in one pass, translates it to object/machine code, and saves that artifact. Examples: C, C++, C#. Runs fast later, pays upfront.
Interpreter
Translates one statement at a time and executes it immediately, then moves on. No saved object code. Examples: Python, Perl, JavaScript, Ruby.
Script
A scripting language is a programming language that runs inside a host without an explicit compile step. All scripts are programming languages; not all programming languages are scripts.

What a compiler actually does

A compiler is a translator that takes the whole high-level source program as input and emits low-level code, machine or assembly, as output. Because it sees the whole program before it runs anything, it can optimize across the entire codebase and report errors in a single batch at the end of compilation. The tradeoff is that any change to the source requires recompiling, and the compiler itself needs more memory to hold the intermediate representation and the generated object code. (Difference Between Compiler and Interpreter)

The payoff is speed and a security story. Compiled code runs faster than interpreted code because the translation is done once, ahead of time, and the runtime is not paying for analysis on every execution. GeeksforGeeks also notes that compilers are "more useful" for security than interpreters, because the ahead-of-time translation gives you a fixed artifact you can audit, sign, and ship, rather than a live translator running in your threat boundary. (Language Processors)

What an interpreter actually does

An interpreter translates a single statement into machine code and executes it immediately, then moves to the next line. If a statement errors, the interpreter stops right there and shows the error, which makes debugging easier because the failure is local. The cost is that no object code is saved, so the translation tax is paid every run, and the overall execution time is slower than a compiled equivalent. The interpreter also needs less memory than a compiler because it never materializes a full object artifact. (Language Processors)

The honest version of the comparison is a tradeoff table, not a winner:

PropertyCompilerInterpreter
ReadsWhole program at onceOne statement at a time
Object codeGenerated and saved (e.g. .exe)Not generated
Analysis timeLonger upfrontShorter upfront
Execution timeFasterSlower
MemoryMore (holds IR + object)Less (no object)
ErrorsReported in a batch after compileReported line-by-line, stops at first
Change costRecompile the whole thingNo retranslation needed
ExamplesC, C++, C#Python, Perl, JavaScript, Ruby

Where the script fits

A scripting language is not a third kind of translator. It is a programming language that is traditionally run by an interpreter inside a host environment, without an explicit compile step. The key claim from GeeksforGeeks is direct: "all scripting languages are programming languages. The main theoretical difference is that scripting languages don't need a compilation step and are interpreted." (What's the difference between Scripting and Programming Languages?)

The important caveat, and the one most marketing copy omits, is that the boundary is about the execution environment, not the language. The same GeeksforGeeks article points out that C can be interpreted and used as a scripting language, and JavaScript can be compiled and used as a non-scripting language. The canonical example is Google Chrome's V8 engine, which compiles JavaScript to machine code instead of interpreting it. So "script" is a role a language plays in a given runtime, not a property of the syntax. (Scripting vs Programming)

The blur to watch for
When a tool says it "compiles" or "is typed" or "has guardrails," that tells you almost nothing until you know what target it compiles to, who runs the runtime, and what enforces the types at the boundary. A language can be compiled and still ship a runtime you have to trust. A language can be typed and still cross an untyped FFI. The script/compiler/interpreter distinction is about when translation happens; the security question is about where the trust boundary sits. Conflating the two is the move this post is going to push back on.

The anti-case study: BAML

With the foundations stated, here is the case study. BAML is an open-source (Apache-2.0) project from Boundary, a company headquartered in Seattle, WA. Its own README title is the backronym "BAML: Basically A Made-up Language," and its one-line pitch is "BAML is the programming language for agents." (BoundaryML/baml README)

The README lists the features that are supposed to make agents produce less slop. Quoted directly from the repository:

BAML README, verbatim
BAML looks like TypeScript, but every feature is built so agents make fewer mistakes:
* It has a type system like Rust, but compiles even faster than Go.
* Types persist at runtime. There is no `any` nor casting dangerously to any type.
* Errors are typed and statically analyzed.
* The filesystem describes the modules/namespaces.
* Has green threads, and colorless concurrency like Go
* Built-in tests / eval framework
* Built-in stdlib for agents
* Every baml tool is natively designed for agents, with no garbage outputs, etc.
* Can be run standalone or adopt incrementally (you can call a BAML function from TS, Py, Go, C#, Java, etc).

Read that list carefully, because it is the whole pitch, and every line is a feature that already exists in a foundational tool. The README even names the tools it is imitating: Rust, Go, TypeScript. That is the tell.

Every guardrail already has a home

The BAML claim is that agents need a new language to stop producing slop. The mechanical reality is that each claimed feature is a solved problem in a tool that has been hardened for years, often decades. Here is the mapping, claim by claim:

BAML claim (from README)Foundational tool that already does it
"type system like Rust"Rust's borrow-checked type system, plus the ML family (OCaml, Haskell) and TypeScript in --strict mode. Type systems are a compiler feature, not a language.
"compiles even faster than Go"Go already compiles fast; this is a runtime property of the toolchain, not a guardrail against agent mistakes.
"Types persist at runtime, no any"Runtime type reflection: Java, .NET, Python with typing + pydantic, TypeScript with type guards, zod, io-ts. "No any" is just strict mode.
"Errors are typed and statically analyzed"RustResult/Option, Haskell Either, Swift, Kotlin sealed classes. Typed errors are standard in any modern typed language.
"Filesystem describes modules/namespaces"Go (package = directory), Python (package = directory), Node (file = module), Java (package = directory). This is the conventional default, not an innovation.
"green threads, colorless concurrency like Go"Go goroutines, Erlang processes, Java virtual threads (JEP 444, JDK 21), Kotlin coroutines, Rust async with tokio. Colorless concurrency is Go's existing design.
"Built-in tests / eval framework"pytest, jest, go test, cargo test; for LLM evals specifically: promptfoo, langfuse, deepeval, guardrails-ai.
"Built-in stdlib for agents"Every language ships a stdlib. Agent helpers are libraries (langchain, llamaindex), not a reason to invent a language.
"no garbage outputs" / "guardrails" / "structured-data"JSON Schema, Pydantic, zod, serde, protobuf, and OpenAI's structured outputs / function calling. Schema validation is the actual guardrail; a language is not.

The pattern is consistent. BAML is not introducing a capability that was missing. It is re-implementing capabilities that already exist, in a new syntax, and selling the syntax as the safety. That is the definition of slop wearing a guardrail costume.

The attack-surface argument

Here is where the compiler/interpreter/script distinction matters. BAML is not a library you call. It is a new language, which means a new stack of trust boundaries. To use BAML you install, per the README, brew install baml, then baml agent install, baml init, and baml ide install --code. Each of those is a binary running on developer machines and in CI. Underneath, the repository ships a parser, a type checker, a compiler, a runtime, a CLI (baml-cli), and FFI bridges to six host languages: TypeScript, Python, Go, C#, Java, and more. (BoundaryML/baml repository tree)

Every one of those components is a new attack surface:

1
Parser — a new input parser is a classic memory-safety and DoS target. Every untrusted .baml file now crosses it.
2
Type checker + compiler — a new static analyzer can have unsoundness bugs that silently let through what it claims to forbid, the exact opposite of a guardrail.
3
Runtime / VM — green threads and a stdlib mean a new sandbox to escape. Sandboxes are where the worst CVEs live.
4
CLI + package distribution — brew install baml is a supply-chain trust event. The binary runs in CI and on laptops.
5
Six FFI bridges — every bridge to TS/Py/Go/C#/Java is a boundary where the "no any" guarantee can be defeated by an untyped crossing. The guardrail dies at the FFI.
6
baml agent install — an installer that fetches and runs agent tooling is a remote-code-execution path dressed as a convenience.

Compare that to the foundational tools BAML imitates. gcc, rustc, go, python, node, jq, and the POSIX shell are already packaged by every major distribution, have decades of CVE history and patching, and are audited by people whose full job is auditing them. Adding a new language does not subtract that surface. It adds a parallel surface on top of it, and then asks you to trust that the new one is sound because it is "like Rust."

A new language is the heaviest possible solution to "the agent skipped the type checker." The lightweight solution is to make the agent run the type checker we already have and act on its output.
The actual thesis

They are solving the wrong problem

This is the core of it, and it is worth stating plainly so it does not sound like a cheap shot. The real problem with agent-generated code is not the absence of a typed language. Agents can already write Rust, TypeScript in strict mode, and Python with pydantic. The slop does not come from the language lacking types. It comes from the agent not running the type checker, not reading the compiler errors, not running the tests, and not validating structured output against a schema. Those are discipline and workflow failures, not language failures.

BAML's premise is that you fix agent slop by moving the agent into a language that forbids the mistakes. But the same outcome, without a new language, comes from a workflow rule that already exists in any serious codebase: the agent must run rustc / tsc --strict / pytest, must treat non-zero exit as a hard stop, and must validate every LLM output against a JSON Schema or pydantic model before it leaves the boundary. That is a constraint on the agent's behavior, enforced by the existing toolchain. BAML relocates the slop to a new surface instead of removing it: the agent now makes mistakes in BAML, against a younger type checker with a thinner CVE history, and crosses six FFIs to get back to the languages you actually ship.

The "wrong problem" framing is exact. BAML asks: how do we give agents a language that stops them producing slop? The right question is: how do we make agents use the verification tools we already have? The first question yields a new language, a new runtime, a new CLI, and six new trust boundaries. The second question yields a workflow change that costs nothing and reuses a toolchain that has been hardened since before the company existed.

Being fair to BAML

This is not a claim that the BAML team is incompetent. The repository shows real engineering: 3,755 commits, a Rust-based toolchain, an Apache-2.0 license, 9.1k stars and 486 forks at the time of writing, a typed-error design, and a genuine attempt at a fast compiler. (BoundaryML/baml) The team is openly hiring Rust engineers, which suggests they take the hard parts seriously. The critique here is about the premise, not the people or the craft. A well-built solution to the wrong problem is still the wrong problem, and "well-built" makes it more convincing, which is exactly why the premise deserves to be checked.

It is also worth saying what BAML is not being accused of here. It is not accused of being malware, of telemetry abuse, or of bad faith. The README's TELEMETRY.md is present and the license is permissive. The accusation is narrower and, I think, more useful: BAML is an anti-slop tool that is itself slop, because it re-implements existing foundational capability in a new syntax and sells the syntax as safety, while widening the attack surface it claims to narrow. That is a structural critique, and it applies to the category, not just this one project.

Caveats and fact-check notes

What is verified vs interpreted
  • Verified from the BAML README (fetched 2026-08-23): the backronym "Basically A Made-up Language," the pitch "the programming language for agents," the feature list quoted verbatim, the install commands, the FFI list (TS, Py, Go, C#, Java), Apache-2.0 license, Seattle HQ, and the topic tags guardrails/llm/structured-data. Star/fork/commit counts (9.1k / 486 / 3,755) are point-in-time from the GitHub page and will drift.
  • Verified from GeeksforGeeks (all three URLs fetched and returned 200): the compiler/interpreter definitions, the whole-program vs line-by-line distinction, the object-code/no-object-code tradeoff, the "all scripting languages are programming languages" claim, and the V8-compiles-JavaScript example.
  • Interpretation, not fact: the mapping of each BAML claim to an existing tool is an argument, not a quote. The existing tools named (Rust, Go, pydantic, zod, JSON Schema, pytest, guardrails-ai, JEP 444 virtual threads, etc.) are real and do what the table says, but the claim that BAML "re-implements" them is this post's thesis, not BAML's admission.
  • Attack-surface claims are structural reasoning about what a new language + runtime + CLI + FFI bridges necessarily add; they are not a claim that BAML has specific CVEs. The point is that the surface is added before any CVE is found, which is the cost.
  • "Wrong problem" framing is an opinion, stated as such. It is the post's conclusion, not an external verdict.

What to do instead

If the goal is less agent slop, the foundational tools already do the job and the workflow is the lever:

Type the boundary
Validate every LLM output against JSON Schema, pydantic, or zod before it crosses into your code. No new language required.
Run the checker
Treat rustc / tsc --strict / mypy exit codes as hard stops. The agent does not merge until the existing checker is clean.
Run the tests
pytest / go test / cargo test are the eval framework. For LLM-specific evals use promptfoo or guardrails-ai.
Constrain the agent
Slop is a behavior problem. Make the agent's workflow require the verifier, the way a shell command requires its flags. Discipline, not a new syntax.

The compiler, the interpreter, and the script have been doing this work for fifty years. The guardrails are not missing. They are sitting right there, in the toolchain the new language is asking you to replace.

← All transmissions