AI Engineer World's Fair 2025
CI in the Era of AI: From Unit Tests to Stochastic Evals
About this talk
Zed co-founder Nathan Sobo explains how the Rust-native, GPU-rendered editor extends rigorous deterministic CI into stochastic evaluations for agentic coding. He describes replayable concurrency simulations, SWE-bench-like end-to-end agent evaluations, focused regression tests, Tree-sitter-assisted search, and defenses against streaming-diff errors, malformed XML, indentation mismatches, and model-specific escaping.
Chapters
- 0:00Nathan Sobo introduces Zed's Rust-native, GPU-rendered editor
- 1:13Deterministic CI, simulated concurrency, and stochastic LLM evaluations
- 3:11Headless agent evaluations, targeted failures, and Tree-sitter search
- 6:04Evaluation thresholds, streaming diffs, and malformed XML
- 9:40Indentation normalization and model-specific escaping failures
- 12:54Empirical agent engineering, open source, and Claude 4
Talk transcript
- 0:00
[upbeat music] Hey, everybody.
- 0:15
Thanks for coming to my talk. Uh, I'm Nathan Sobo. I'm the co-founder of Zed. We are an AI-enabled code editor, and what sets us apart is we are not a fork of VS Code.
- 0:27
We are implemented from scratch. [laughs] [clapping] Uh, implemented from scratch in Rust. We literally engineered the entire system like a video game around about 1,200 lines of shader program that run on the GPU, and the rest of the system is organized to deliver frames at 120 frames per second really quickly.
- 0:47
And recently, I recorded a video because time is short, and I-- this is obviously sped up massively, but we launched agentic editing in Zed, and I wanted to talk about the approach that we took to test this, um, and be empirical ab-- and deliver a reliable product that does this effectively.
- 1:04
So here's what we're gonna be talking about. The rest of the talk is gonna be looking at code and just talking about our experience. Um, so
- 1:13
testing, evaluation, what's the difference? Um, first, I just wanna start by saying, like, without an empirical approach to the software that we've been building since twenty-eighteen, twenty twenty-one, depends on how long you measure it, Zed would crash every eight seconds.
- 1:29
Like, uh, we have probably tens of thousands of tests at this point. And here's an example of the extreme to which we take empirical software development. We literally are starting a server, creating two clients, and we have a simulated scheduler that gets passed in here, and we run fifty different iterations of this with every random interleaving of
- 1:48
anything that could possibly be concurrent. Um, so that if the three hundred and seventy-fifth thousandth, and this is only fifty, but, uh, iteration of this particular interleaving of network packets, um, goes wrong, we can replay that over and over again, freeze it, and have full control.
- 2:06
So we're very hardcore about testing and being empirical at Zed. But up until very recently, until shipping some of these AI-related features, we've been able to be fully deterministic.
- 2:17
Even with sort of non-deterministic things, we've been able to really lock things down and never have flaky tests on CI. But as soon as an LLM enters the picture, that's all out the window, right?
- 2:28
Because we could like, you know, assuming that [chuckles] we're using, you know, uh, frontier models, but s- even if we were to use our own model and to, like, control the sampling off the logits, you change one token in the input, and you're gonna get a completely different output.
- 2:43
So it's just a fundamentally different problem where we have to embrace stochastic behavior. And so as we built this system, our first eval that we really hammered on was something that if you've seen something like SWE-bench probably looks pretty familiar.
- 2:57
It's very data-driven, and it seems like in the machine learning world, like this is what an eval is, input, output. But in the programmatic software world, an eval is more like a test that passes or fails.
- 3:11
Like, we come from this very different perspective of automated testing. And so right away, you know, we had this traditional data-driven eval, and then backing that, you know, in the same program we als-- uh, that runs all these evals basically compiles a headless copy of Zed, checks out a repo, runs the agent, tries to make it do
- 3:30
things. Um, we right away got into, like, making the e- eval more programmatic. So you can see here the conversation is literally a function. And then our ability to sort of come in here and write code that performs assertions about what the agent did, even, you know, getting, uh, quite a bit more granular.
- 3:49
'Cause the problem is when that big eval fails, it's like, what do we do? [chuckles] There's a million ways that that thing could go wrong. And so when we wrote this eval, we were able to drive out, uh, one really simple failure mode, which is when we run the grep tool, this is what our original, like, dumb implementation
- 4:06
of the grep tool looked like. So you can see in this case, we're saying we wanna add, uh, a window argument to this tool run trait method in this particular file name, right?
- 4:17
But if this is what the model sees, then we're in trouble, right? And so what we ended up, you know, driving from this stochastic test here is a more deterministic test, right?
- 4:31
Where I'm gonna go ahead and set up the project, uh, perform this search for FN run, and then, uh, we use Tree-sitter, which is, um, Max's parsing, uh, parser generator framework, to actually expand out the match to the syntactic boundaries.
- 4:48
Um, and so that was able to, you know, drive a sub-- a substantial improvement in the behavior of the agent. So right there, like, that was an interesting discovery of just being empirical and driving out what was ultimately an algorithmic problem with a stochastic test.
- 5:04
But then we g- moved on to the editing, and when we first implemented editing, it was just done with tool calls. But the problem with tool calls, if anybody's worked with them, is they don't really stream very well.
- 5:14
Like, you get key, value, key, value, and it all comes back at once. So we, what we ended up doing is deciding instead we perform a small tool call that just describes the edits, then we loop back around for the moment to just the same model because it has everything already loaded in its cache and ask it
- 5:33
to emit these old text, new text blocks. Um, but there's some challenges there. Like, now we have to parse that data is coming back and all kinds of weird stuff that the model's gonna be doing.
- 5:45
So I'll just dive in and show you, um, some of the examples. So here's like--
- 5:51
uh, there are a bunch of different, uh, tests. Like, these are part of our main regular old test suite, and we just added this eval function, and we say, "I wanna run this eval two hundred times."
- 6:02
And in this particular case-
- 6:04
Didn't start this way, [chuckles] but eventually we got this way. We want 100% of these 200 examples to pass, or this test should, like, literally fail to build. So we kind of set a watermark that way, and then here you can see I'm just, like, loading in a conversation with the LLM, um, and then ultimately, uh, in this
- 6:24
case, I'm using, uh, eval as judge. But there are some cases you saw earlier where we do things more programmatically. This case, we're just, like, verifying this thing worked correctly.
- 6:34
Um, and so we go from a test like this, a stochastic test, into the particular problems, um, that drive this. So I mean... But it's funny, a lot of the things that went wrong here were really just, like, basic things, right?
- 6:48
Like, and so again, these are things that we can-- they're non-deterministic, but the non-determinism is b- non-determinism is sort of bounded. So in this case, we're gonna run 100 iterations of this very simple parsing test, where we're randomly chunking up the input into arbitrary boundaries and just making sure that the parser for this data, um, you know,
- 7:12
can handle arbitrary chunking o- of the text. Similarly here, fuzzy matching algorithm, that's a critical piece of, you know, if the model's fuzzy or, uh, generates something slightly wrong, being able to do this dynamic programming that gets us an, a, a, an approximate match really saved a lot of the tool calling failures for us.
- 7:34
But again, something that could be deterministically tested. And then finally, there's this idea of a streaming diff that as the model is emitting new text, we need to be care- comparing the new text it's emitting with the old text and then making a dynamic decision i- of if I don't see some text, is that in-- that was
- 7:53
present in the old text, is that because it was deleted or it just hasn't been streamed out yet? So that's another, you know, completely, uh, deterministic thing that still was critical to making this system work correctly.
- 8:06
So yet another, like, deterministic test. Um, but then we get into some of the fun stuff with, uh, uh, the behavior of the model itself. So one thing that we noticed right away was, um, the model when it was trying to insert at the top or the end of the document in some cases would just have an
- 8:27
empty old text tag, which like if you're doing old text, new text matching is not very useful. And so we found that we were able to just add this, you know, simple thing to the prompt and improve the eval.
- 8:39
But again, [chuckles] it would still do it, you know, one or two percent of the time. And so just being able to test that, uh, you know, we can handle that case robustly, um, you know, ended up being really helpful.
- 8:52
And this is just like, again, I'm in this case simulating the output of the LLM. Um,
- 8:59
uh, another case that we encountered a lot was that it would mismatch the XML tags, and so we would have old text, and then it would end like this with new text.
- 9:10
Um, and so again, we were able to get a certain distance with the prompt. We initially were sort of 40% of the time we were getting this mismatch and blowing up.
- 9:20
We were able to move that by saying, "Always close all tags properly," to like 95%, but we still have that last like five percent. What do we do with that?
- 9:30
And so again, it's just like driving it into a deterministic test where we have old text, new text, you know, and we need to just assert that even though we got...
- 9:40
You know, we just need to be robust and accept crazy stuff from the LLM, like, uh, in its output. Um, another interesting case was indentation. So here's an eval that, uh, that we wrote, which was this idea of having an outer function, and then inside here there's an inner function, and we have this todo macro here.
- 10:02
And we wanna say, "Replace this todo with return 42," and we would see the model doing things like this, right? Where the indentation level was completely flattened out, right?
- 10:13
It says, "Replace fn_inner," but fn_inner has like all this leading indentation on the front of it. Um, but otherwise, like it's perfectly fine. And so in this case, what we did is this strategy of like detecting this indent delta.
- 10:29
So we figure out basically sort of re-normalize the indent, if that makes sense. So if the text in the buffer is indented by a certain amount but we otherwise match, then we also detect the text in the, that the LLM emitted and compute this delta, and then again, driving it back to a deterministic test of, um, here
- 10:51
we build, uh, a buffer with lorem ipsum dolor sit with this very interesting indentation pattern. And then you can see here, we wanna replace ipsum dolor sit with ipsum dolor sit amet where it's out dented, right?
- 11:07
And you can see that when we actually do our, uh, indentation normalization, we're able to handle that correctly. Um, another tricky thing that we ran into was this weird escaping behavior where in these certain cases where we have weird constructs, like this is a Rust, um, sort of a raw string basically that can include, uh, things like
- 11:30
quotes inside of it due to this interesting escaping syntax. And doing something as simple as saying, just like, "Tweak this," we would notice this is the behavior we want.
- 11:40
We would notice, especially Gemini doing all kinds of crazy stuff like doing HTML escape codes, um, doing this sort of backslash escaping. Yeah, e- with new lines and stuff, we would see like, you know, uh, crazy escaping that it would do, [chuckles] uh, double escaping the new lines.
- 11:59
Um, and so in this case, like we-- that was just a pure prompt fix and didn't really... We didn't try yet, at least, to kind of detect- any of this escaping any further, although obviously, like, that's an opportunity, yeah, to keep going even further.
- 12:15
Um, so yeah, I mean from, from my perspective, like, the lessons we learned from this entire experience is that, like,
- 12:24
rigorous testing is fundamental to building reliable software, period. Uh, we have this new fancy parlance of, like, an eval, which comes, I think, out of the machine learning field of kind of input/output and having lots of examples of that.
- 12:38
But I think a lot of the techniques of just traditional, good old-fashioned software engineering are still really applicable. But we have to embrace this more statistical approach, where we're running it 100 times, 200 times, and asserting a threshold of pass versus fail.
- 12:54
Um, but I think, I mean, this is a real-world example of just the stuff that we saw trying to implement streaming edits or an agent that can go search and edit.
- 13:04
A lot of those problems are not, I don't know, they're not advanced [laughs] machine learning problems or anything. It's just stupid things that the model will try to do that we need to account for.
- 13:15
Um, and so this, I, I think this motion of sort of starting with the zoomed-out eval, then zooming into a sort of stochastic unit test that's still random and interacting with the model but is more focused in on a particular aspect of the experience, and then finally, like driving that even further to an actual good old-fashioned test,
- 13:35
that's been the process that we've discovered. But we so far haven't needed to use, like, special external tools, um, or eval frameworks or anything of like that. It's just in our test suite so far, and kind of using the same underlying infrastructure that's used to do any other kind of software testing.
- 13:54
And honestly, using just a lot of those same skills. Um, so for us, it was like just be empirical, just like we've always been, but it's this new X factor of the LLM doing all these crazy things.
- 14:09
Um, yeah, so that's, that's a little story of how we ... And this is all open source, so you can check it out. Uh, Zed is under the GPL license.
- 14:18
I'd love help improving it all, actually. Um, maybe some of you are like, "What are you doing? You could do better in the following five ways." I'd love, uh, contributions, if that's interesting to folks.
- 14:28
And, um, yeah, it's working. I'm, uh, with the Claude 4 models, I'm finally able to write Rust agentically really efficiently, and I'm just loving it. So, um, that's how we got there.
- 14:42
I appreciate your attention. [audience applauds] [upbeat music]