AI Engineer World's Fair 2026

fighting slop with slop

Read the talk

Fighting Slop with Slop

How do you build a reliable programming language without code reviews? Vaibhav Gupta’s answer combines architectural constraints, agent-driven testing, and tooling that makes behavior easier to inspect.

From a talk by Vaibhav Gupta

Before you start: Familiarity with typed functions, exceptions, command-line tools, and CI/CD will help you follow the demonstrations.

A programming language without code reviews

How do you ship a programming language when your team does no code reviews, requires engineers to work on things in parallel, and lets everyone use AI however they want? Those are Vaibhav Gupta’s opening engineering practices—not a hypothetical proposal.

Slide listing “No code reviews,” “You must work on things in parallel,” and “Use AI however you want.”Open full source frame
Three engineering practices: no code reviews, parallel work, and unrestricted AI use.

The stakes are unusually high. His team began building a language about three years earlier, and a language must behave consistently across programs and releases. A bad design decision cannot simply be reversed a month later without consequences for its users. The implementation can change rapidly; the contract it exposes needs to endure.

Gupta introduces his fictional mentor, Slop Tzu, with the instruction to become the slop in order to defeat it. Underneath the joke is a deliberately broad definition: slop is any code you do not read. That makes unread implementation an engineering condition to manage, not something a ban on AI can eliminate. Today’s codebase, he warns, contains the least slop it will ever have. The practical question is what replaces exhaustive reading as that codebase grows.

0:230:35
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

0:23 · section reference included

Standardize the invariants, not the agent

Engineers want different tools: Claude, Codex, or whatever they have just found on Hacker News. Instead of making those choices uniform, Gupta’s team puts its durable architectural rules in architecture.md, rather than tying the shared contract to a model-specific file such as CLAUDE.md. The common ground is what the software must preserve, not how each engineer prompts an agent.

The file must stay small and contain only things expected to remain true for months or years. For this project, that means the compiler’s layers. Work deeper inside the compiler also carries a consultation requirement: the agent must involve at least one other person. That intentionally introduces friction where changes have wider consequences. Freedom in tool choice rests on stability in architectural obligations.

1:552:05
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

1:55 · section reference included

A design document needs readers

Design decisions receive a different standard from implementation: “Code can be slop, writing cannot.” Simply stating that rule did not produce careful, handwritten documents. The team built a dedicated design-document tool, replacing the relevant parts of Notion and GitHub with versioning, comments, and a place to discuss proposals.

A document system alone did not create readership, so the team added a Slack integration that announced every update. Gupta recalls a design document posted at 2 AM attracting three readers immediately. The channel became a popular place to follow consequential design work. Underneath, Markdown files and simple CLI scripts made the same workflow available to agents.

Making publication easy created a new failure mode. Gupta reports producing ten design documents a day and overwhelming his team. The final rule therefore addressed consumption: shipping a design document required people actually to read it. The slide makes that concrete by describing meetings booked just for reading. The constraint is no longer merely whether a document exists; it is whether the decision has received human attention.

White slide stating “we book a meeting just to read” and “If it isn't worth reading, it isn't worth writing.”Open full source frame
“We book a meeting just to read.”
2:362:46
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

2:36 · section reference included

Make architectural boundaries executable

A small architecture file describes the intended structure. The next tool shows whether the code still has that structure: a dependency graph containing internal packages, semantic boundaries, and selected external dependencies. Engineers can watch how the graph changes as implementation grows.

The graph becomes more useful when paired with CLI checks that enforce its invariants. If Claude creates a package or introduces a dependency that leaks across a boundary, CI/CD can flag the violation, while Git history identifies the change that introduced it. Gupta reports that the team’s architecture had remained unchanged for three or four months. The important mechanism is the division of labor: visualization helps people understand the structure, and executable checks prevent forbidden changes from passing unnoticed.

4:004:11
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

4:00 · section reference included

Use agents to exercise the language

Stable package boundaries do not establish language correctness. Generics, closures, memory allocation, and foreign-function interfaces each introduce behavior that can fail inside an otherwise tidy architecture. Gupta points to bugs persisting in Python decades into its life to emphasize the difficulty: would you trust a language implementation simply because its team had written good design documents?

The team’s response is a continuous loop in which agents create BAML programs from scratch and other agents inspect the attempts:

  1. Have an agent build a program using the language.
  2. Preserve its full Claude transcript, including tool calls and outcomes.
  3. Inspect the transcript for incorrect behavior and unnecessary work, such as an interaction requiring three tool calls where one should suffice.
  4. Have humans triage proposed issues, separating real problems from hallucinations and poor judgments.
  5. Let agents implement fixes for the selected problems.

This tests more than whether a program eventually runs. It also exposes where the language and its tooling make an agent struggle.

The same loop can support A/B testing of language features and skills. Instead of deciding by intuition which interface an agent will use well, compare tool-call counts, errors, and correct outcomes on the tasks being exercised. Gupta presents this as the next step beyond issue detection: use observed agent behavior to choose among designs.

4:384:47
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

4:38 · section reference included

Spend automation on the engineering process

These internal tools are examples, not a prescribed stack. Their broader purpose is to make engineering decisions depend on evidence without requiring humans to handwrite every part of the measurement system. Generated code can build the machinery that checks other generated code.

Gupta frames an eight-person, less-than-two-year language-building effort as something enabled by spending billions of tokens rather than relying on enormous amounts of manual labor. He does not identify a precise milestone connecting that account to the earlier three-year project history, or provide a measured productivity baseline. His practical claim is that teams can build enough supporting machinery to ship without reading every implementation line.

That still leaves a deeper problem. Better checks can win individual battles against unreliable code, but they operate on top of a language and runtime whose own behavior may be surprising. Gupta’s next question is whether those foundations were designed for the way code is now being produced.

6:176:30
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

6:17 · section reference included

What would a language designed for agents change?

TypeScript provides the starting point. Its design goals and non-goals explicitly balance correctness with productivity when explaining why a sound or provably correct type system is not a goal. Gupta reads that productivity objective as human productivity. If humans were no longer writing the code, which compromises would still be worth making?

His first demonstration concerns sorting. Without a comparator, JavaScript’s array sort compares defined values after converting them to strings; TypeScript inherits that runtime behavior. A small example illustrates the specified rule:

typescript

const values: number[] = [2, 10, 3];

const defaultOrder = [...values].sort();
const numericOrder = [...values].sort((a, b) => a - b);

console.log(defaultOrder); // [10, 2, 3]
console.log(numericOrder); // [2, 3, 10]

The values are numbers in both cases. The difference is the comparison operation, so a numeric type annotation does not supply the ordering a programmer may expect.

Gupta follows this with an on-screen contrast between the TypeScript he likes and the TypeScript his agent likes. His objection is that generated programs inherit the choices their language makes available. He then traces a progression from JavaScript through CoffeeScript and TypeScript to Effect. Effect is a TypeScript library, rather than another language in that sequence; the common theme is adding stronger facilities above an existing runtime. BAML is his attempt to reconsider the foundation itself.

7:227:37
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

7:22 · section reference included

Choose which code to inspect

The first BAML demonstration changes the view of a program. Instead of starting with source text, Gupta opens a visualization of its structure. Diagram elements link to exact source lines, and expanding or zooming into a region reveals more detail. The diagram is a navigation surface over the implementation.

While one pipeline runs, he opens another. Its semantic boundaries make the setup work and an agent loop recognizable without reading their implementations. He expands selected regions, then stops when the remaining detail is not useful. Selective inspection replaces an all-or-nothing choice between reading everything and understanding nothing. The engineer can leave a region collapsed while retaining a route to its source when a question makes that detail relevant.

9:269:31
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

9:26 · section reference included

Read execution as well as structure

Returning to the running pipeline adds the temporal view: an execution trace showing where the program spends its time. Structure explains which components exist and how they connect. A trace explains what happened during a particular run. The displayed flame chart places function rows and timing bars beside the source.

Gupta claims that designing tracing into the foundation can make comprehensive tracing have effectively zero performance cost. He contrasts that with tracing everything in Python or TypeScript, which he describes as untenably slow. The demonstration supplies no workload, hardware, tracing-disabled baseline, or overhead measurement with which to quantify that comparison.

Editor showing an image-generation loop beside a BAML Playground flame chart with function rows, timing values, and orange bars.Open full source frame
BAML source beside an execution flame chart.

The trace is also intended to be navigable by Claude, with per-file information that helps an agent locate bugs, errors, and inefficiencies. That creates a shared inspection surface: a human can investigate a slow region, and an agent can use the same execution evidence to propose an optimization. The objective is understanding the system well enough to act on it, rather than treating the number of source lines read as a proxy for understanding.

10:3710:52
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

10:37 · section reference included

Describe a symbol instead of assembling search results

Human tooling took decades to develop into modern IDEs. Gupta jokes that VS Code finally let him work comfortably without learning how to escape Vim. Agent tooling deserves a similarly deliberate interface, beginning with the everyday task of locating and understanding a function.

A ripgrep search for calculate returns matching text across the codebase. That is useful discovery, but an agent must still assemble the definition, documentation, and relevant uses. The demonstrated alternative asks for a description of calculate and returns those pieces together.

InterfaceWhat it returnsFollow-up work
Text search for calculateMatching source linesLocate and read relevant definitions and uses
Symbol descriptionDocstrings, source, and usage locationsInspect the assembled context

The benefit is the shape of the response: information that would otherwise require several retrieval steps can arrive in one tool call. The same inspection approach extends to external libraries, reducing the need to leave the codebase for a separate web search.

Gupta treats implementation as more authoritative than prose documentation: descriptions, architecture files, and READMEs can drift away from what the program does. His qualification is that unusual architectures can complicate even that relationship. Returning actual source alongside explanatory material gives the agent something concrete against which to check a description.

11:3911:54
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

11:39 · section reference included

Make functions directly runnable

After inspecting a function, the next way to understand it is to run it. Gupta demonstrates add becoming a CLI command with A and B parameters. The function’s interface supplies the command’s inputs, so trying it does not first require finding an application entry point or writing a separate driver.

He then bundles multiply into a standalone CLI binary. This moves the same idea from local experimentation to distributing a directly callable tool. The binary contains the function; running it still executes that function, but does not require the caller to reconstruct the original project workflow.

Gupta presents type safety, determinism, and predictable interfaces as the benefits, with build targets spanning Windows, macOS, Linux, and Wasm. In this model, agents spend less effort discovering how to invoke code or adapting its packaging to each platform. The toolchain takes responsibility for those recurring tasks, allowing engineers to move from a function to a usable tool more directly.

13:3013:41
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

13:30 · section reference included

Let the compiler track error obligations

Tooling can improve navigation and execution, but error handling reaches into language semantics. Gupta describes an agent failure pattern familiar from JavaScript: nested try/catch blocks accumulate until the implementation gives up with a console.log saying that something went wrong. The question is whether the language can make the unhandled cases visible before execution.

The arithmetic demonstration now moves from add, multiply, and subtract to divide. Division can fail on a zero denominator. In the demonstrated BAML tooling, divide knows that it can throw a division-by-zero error without an explicit error annotation. When calculate calls it, that possible error propagates into the caller’s inferred error information.

Inferred errors turn handling into a checkable obligation. A caller can handle an error or allow it to propagate, and the compiler can check whether handling is exhaustive. When Gupta imposes an API constraint that a function must never throw, the tooling reports two unhandled errors. The constraint fails because the implementation does not yet meet it.

He then adds a catch and returns the sentinel value 0. The relevant change is in the set of errors that can escape:

FunctionBefore the handlerAfter the handler
divideCan throw division-by-zeroStill can throw division-by-zero
calculatePropagates division-by-zeroStill propagates division-by-zero
parseCan expose division-by-zeroHandles it and returns 0

Handling the exception in parse does not make division itself safe. It changes what callers of parse must account for. Nor does this establish that zero is the right application result, or that the other previously reported error has been resolved. The demonstrated guarantee is that division-by-zero no longer escapes through that path.

This is the kind of delegated trust programmers already place in compilers. Most users do not inspect the compiler implementation before relying on a type error. Gupta wants generated code to sit inside similarly rigid constraints, so confidence comes from properties the system checks rather than from faith in the model that produced the code.

14:5315:04
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

14:53 · section reference included

Cross the language boundary without a rewrite

A new language also introduces new problems. Asking everyone to rewrite all existing software would make the proposed cure difficult to adopt, so Gupta says the team began thinking about language bridges roughly two years earlier. The goal is to use BAML inside an existing application, not only as a standalone environment.

He names Python, TypeScript, Rust, Go, Ruby, and Java as host languages in that vision, then demonstrates Python calling BAML’s calculate function through a typed interface. The demonstration is of the compiler and bridge shown in the recording, not a release-pinned promise for every host language; the current BAML overview still marks Ruby as forthcoming.

Python receives both synchronous and asynchronous ways to call calculate. BAML itself has no function coloring—the distinction that forces separate synchronous and asynchronous calling conventions through a call chain—but the bridge exposes the forms a host-language programmer needs.

The next example passes a Python lambda into a timeout helper with a deadline expressed in milliseconds. Gupta extends the bridge’s ambition to generics and closures as well as simple values. The timeout example does not establish whether reaching the deadline cancels the underlying Python work or only stops waiting for it. Its central role here is to show callable behavior crossing the boundary while retaining a typed interface. Gupta wants that type system to remain the source of truth even when agents compose code across languages.

17:2717:38
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

17:27 · section reference included

Replace the work that code review used to do

The opening freedoms return with their conditions attached. Working in parallel, choosing different AI tools, and dispensing with traditional code reviews require careful attention to the systems underneath the team. The review process cannot simply disappear while its responsibilities—design scrutiny, architectural consistency, correctness, and understanding—go unattended.

Gupta reports that an engineer built a partial C compiler entirely in BAML the previous day. For him, this illustrates the possibility of building software the team had not initially believed it could produce. Reducing exhaustive source reading can free engineers to fill gaps in the surrounding process. He compares that transition to adopting CI/CD: the initial investment slows a team down before the resulting automation lets it move faster. His three-month example describes that transition cost, not a measured payback period for these tools.

Cartoon soldier labeled SLOP shields a sleeping purple figure labeled BAML; an upper-right README excerpt is headed “baml-c-compiler.”Open full source frame
The slop-shield illustration alongside a baml-c-compiler README excerpt.

The process has to evolve as generated code accumulates. Gupta returns to his warning that the current codebase contains the least slop it will ever have, then explains the motivation behind the provocation: falling in love with software about fifteen years earlier changed how he saw the world, and he does not want unreliable software to become its accepted future. His immediate request is practical—build internal tools that make systems and engineering processes more robust.

The more ambitious task is to reconsider the foundations those tools rely on. Gupta closes by proposing a new Git, a new database, and a new programming language. The challenge is to find which assumptions still serve engineers and which were made for a world where humans wrote and read nearly every line.

19:2219:34
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

19:22 · section reference included

Resources

From the talk

  • Source code and installation instructions for BAML, including its type system, runtime, and agent tooling.

  • A recursive regular-expression search tool with ignore-file filtering, usage examples, and installation instructions.

  • Introduction to the TypeScript library for typed errors, concurrency, resource management, and observability.

Updates since the talk

Transcript download
Read the transcript and explore slides

Loading transcript and slides…