Intro0:00
Hey everybody, 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, we are implemented from scratch.
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.
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, uh, and be empirical and deliver a reliable product that does this effectively.
Deterministic Testing1:04
So here's what we're going to be talking about. The rest of the talk is going to be looking at code and just talking about our experience. Um, so testing, evaluation, what's the difference? Um, first I just want to start by saying, like, without an empirical approach to the software that we've been building since 2018, 2021, depends on how long you measure it, Zed would crash every 8 seconds.
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 50 different iterations of this with every random interleaving of anything that could possibly be concurrent.
Um, so that if the 375,000th—and this is only 50, 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. 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. Even with sort of non-deterministic things, we've been able to really lock things down and never have flaky tests on CI.
Stochastic Shift2:24
But as soon as an LLM enters the picture, that's all out the window,right? Because we could, like, you know, assuming that we're using, you know, uh, frontier models, but 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 going to get a completely different output.
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 Swebinch, probably looks pretty familiar.
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. Like, we come from this very different perspective of automated testing.
And soright away, you know, we had this traditional data-driven eval, and then backing that, you know, in the same program we else, 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 things.
Programmatic Evals3:31
Um, weright away got into, like, making the 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 quite a bit more granular.
Because the problem is when that big eval fails, it's like, what do we do? 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 of the grep tool looked like. So you can see in this case we're saying we want to add, uh, a window argument to this tool run trait method in this particular file name,right?
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? Where I'm going to go ahead and set up the project, uh, perform this search for fn run, and then, uh, we use TreeSitter, which is, um, Max's parsing, uh, parser generator framework, to actually expand out the match to the syntactic boundaries.
Um, and so that was able to, you know, drive a substantial improvement in the behavior of the agent. Soright there, like, that was an interesting discovery of just being empirical and driving out what was ultimately an algorithmic problem with a stochastic test.
But then we 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. Like, you get key, value, key, value, and it all comes back at once.
Streaming Edits5:05
So 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 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 going to be doing. So I'll just dive in and show you, um, some of the examples.
So here's, like, 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 want to run this eval 200 times.
And in this particular case, didn't start this way, but eventually we got this way. We want 100% of these 200 examples to pass, or this test should, like, literally fail the build. And 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 case I'm using, uh, eval as judge. But there are some cases you saw earlier where we do things more programmatically.
Robust Parsing6:30
This case we're just, like, verifying if this thing worked correctly. 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?
Like, and so again, these are things that we can—they're non-deterministic, but the nondeterministic is sort of bounded. So in this case we're going to 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, uh, you know, can handle arbitrary chunking 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 approximate match really saved a lot of the tool calling failures for us.
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 comparing the new text it's emitting with the old text and then making a dynamic decision of if I don't see some text, is that that was 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. 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.
LLM Quirks8:17
So one thing that we noticedright 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 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. But again, it would still do it, you know, 1% or 2% 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.
And this is just, like, again, I'm in this case simulating the output of the LLM. Um,
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. 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. We were able to move that by saying always close all tags properly to, like, 95%. But we still have that last, like, 5%.
What do we do with that? 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, 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 to-do macro here. And we want to say replace this to-do with return 42.
And we would see the model doing things like this,right? Where the indentation level was completely flattened out,right? 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. So we figure out basically we sort of renormalize 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 that the LLM emitted and compute this delta.
And then again, driving it back to a deterministic test of, um, here we build a buffer with lorem ipsum dolar sit with this very interesting indentation pattern. And then you can see here we want to replace ipsum dolar sit with ipsum dolar sit amet where it's outdented,right?
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 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. We would notice especially Gemini doing all kinds of crazy stuff, like doing HTML escape codes, um, doing this sort of backslash escaping.
Yeah, with newlines and stuff we would see, like, you know, uh, crazy escaping that it would do, uh, double escaping the newlines. 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. Um, so yeah, I mean, from my perspective, like, the lessons we learned from this entire experience is that, like, rigorous testing is fundamental to building reliable software, period.
Lessons12:11
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. 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. 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.
A lot of those problems are not, I don't know, they're not advanced machine learning problems or anything. It's just stupid things that the model will try to do that we need to account for. Um, and so this, 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 driving that even further to an actual good old-fashioned test. 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 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 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. 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.
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. 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. I appreciate your attention.





