I have recently tried the AI harness [Pi](https://pi.dev/) . I'm coming from it
from a harness that I first picked up when all this stuff was sort of newer
called [Goose](https://Goose-docs.ai/). I can see what all the hype is about,
but I actually had a bit of a "better is worse" experience with it. I think I'll be sticking with Goose for now. I have come to the conclusion that it is because of the friction Goose introduces into the system makes it better.
In the rest of this blog post, I describe the differences in my experience running Pi versus Goose using Open Router's deepseek-v4-flash model. Goose configuration that I used may be found here. Pi global configuration may be found here. Goose project-level wrapper may be found here. Pi project-level configuration may be found here.
I type a short three-to-four sentence prompt into Goose and it just works. Sometimes I would need to provide guidance, but once I get it going, I can often step away from my desk and come back. I often heard anecdotes about how AI could "slip the leash," like cheat on unit tests, implement the wrong thing, do exactly what they were told not to do, that sort of thing. I really didn't understand these complaints. I would just write stuff, give the AI the occasional dont-do-that-do-this, and it would do okay. However, I tried the same short-style prompting on Pi, and it immediately started slipping the leash.
I tested using Pi on a project I'm porting from Clojure to Common Lisp. I wrote a skill that told it how I like Common Lisp to be written. In there I said to write a bunch of beads. I initially set up Pi to write beads via the bead MCP server. Then I told the AI to use the beads MCP in the skill I wrote and later it was also in the AGENTS.md file. I started Pi up, and told it to read AGENTS.md and I also gave it an initial prompt to use the skill and start porting code.
When I started Pi up, it also immediately ignored what I told it to do in the skill. It didn't write any beads and just started writing code almost immediately. I had to yell at it two or three times before it figured out "oh yeah, I should stop and write beads." I stopped Pi and did some research to determine the underlying cause of this flagrant ignorance on the part of the AI.
The Pi MCP plugin lazily loads the MCP servers into the context by default. Mario Zechner wrote an interesting blog post about how MCP might not be great because it introduces tokens into the context window about the MCP tools whether you use them or not. The MCP adapter in Pi follows this philosophy. It only loads the definitions of the MCP tools into the context. Because of this, my mention of beads was probably the first occurrence of beads that the AI saw. Not knowing what to do about it, it probably just blew past it.
I started it up again with a bead plugin instead of using the bead MCP server. However, I couldn't take this approach with the Common Lisp cl-mcp MCP server. No one had written an equivalent plugin. Pi kept deviating from my telling it to use the MCP server, and this was a big problem. Running Common Lisp code from the CLI often drops into the Lisp interactive debugger. Common Lisp doesn't like being vibe coded, and the interactive debugger is one of those reasons, but there are many. Long-running REPL development, REPL re, one-off Lisp evaluations within the REPL environment, Lisp-form-level editing (rather than line editing), and parenthesis balancing are all problems with which AIs often struggle. The cl-mcp server fixes several of these problems, and more besides.
Goose handles the Lisp debugger really poorly when it runs Lisp code through the CLI. In Goose, the debugger totally hangs the Goose session and Goose must be restarted. When I restart it, I tell Goose "You used Lisp CLI tools again, don't do that, use the MCP server." I use Goose's Top Of Mind feature to remind Goose to use the MCP server instead. When Top Of Mind enabled, I have more or less stopped having this problem with Goose.
Pi handles the debugger and even parenthesis balancing more gracefully than Goose, but this actually caused problems within the session. Pi seems to put the Lisp process in its own shell and handles the interactive prompt gracefully by killing it when it gets to the debugger prompt. Like with Goose, though, I still wanted to enforce the use of cl-mcp. Pi didn't have a top-of-mind-style context injection system available in its package system, when I was running this test, so I made a top of mind plugin for Pi and used that to remind Pi of the MCP server. Even with my putting it into the context every 10 tools calls or so though, Pi kept messing up and stopped using the MCP server. It kept getting big enough rewards from the ad-hoc interactions it had with Common Lisp through the CLI that it stopped using the proper tool for the job, specifically designed to help it get better results.
With the same model and API backend, Pi generates text much, much faster than Goose. It also doesn't allow the user to stop the generation by default. Pi didn't stop when I wanted to talk to it, just kept barrelling toward ruin. The speed would make me feel white-knuckled, like "what is it going to screw up this time." I ended up installing the pi-stop plugin. This helped immensely because I could now stop Pi. However, it still generated text so fast that I couldn't process what it was generating. Often, by the time I had figured out it needed steering, it generated a lot of garbage that sat there poisoning its context, not to mention the bad code or incorrect beads that it wrote. Eventually, after any given Pi session, I would have to switch back to Goose because the AI got confused about what to do next. This speed was not only a problem for context poisoning, but since it generated a lot of churn that then needed cleaned up in a very short amount of time, it costed a lot more money on OpenRouter while yielding poorer results. It simply goes too fast and too easily makes mistakes without providing enough time for steering to take place.
Goose comes with top-of-mind, full-stop steering, maximum turns before stop, and (though I don't use it) fine-grained permissions system allowing you to fine tune what tools are exposed to the AI and which are not. Finally, it has a maximum context token setting (I had to install yet-another-plugin in Pi for this, pi-context-cap. All of this comes out of the box with Goose. I had to hunt around for these features with Pi, and in one case, I had to write my own extension. Worse, the plugins often had only hundreds of downloads, making me think they were one-off garage projects (like the top-of-mind plugin I just wrote for Pi was!) that weren't really maintained by their creators. It made me feel like I was playing whack-a-mole. "Oops! Pi doesn't have x, guess I have to go shopping." I don't go shopping around for different seat belts for my car. I just hope the manufacturer had them built-in. Plug-ins are awesome, and a vibrant package ecosystem is awesome, but my time in the trenches with Jenkins has taught me that plugins shouldn't be used everywhere.
Goose's heavy use of MCP makes it work better. Goose has
[hooks](https://Goose-docs.ai/docs/guides/context-engineering/hooks/) and
[plugins](https://Goose-docs.ai/docs/guides/context-engineering/plugins/), but
most extensions are simply implemented via MCP. It loads the definitions into the context window up front, as far as I can tell. It uses the sequential thinking MCP. It uses the to-do MCP. It uses the beads MCP. It often does this without me explicitly prompting it to do so because I already sort of did by those things. Because all of its MCP tool definitions are loaded into it all at once, the AI thinks it needs to use them. That load was an implied suggestion that these MCP tools are to be used. In Pi, there are a bunch of plugins I have like the task plugin, but it doesn't use it unless I tell it do so so. I figured out eventually how to eagerly load the MCP servers in Pi eagerly into the context, but at first this was a problem. Goose's philosophy is to limit the MCP servers to just the ones you use. Having had this problem of please-use-my-mcp-no-seriously with cl-mcp, I agree with the sentiment. If you do want the AI to use the tool, you SHOULD have MCP definitions, skills, and other sundry tools you want the thing to use loaded up front.
Code is generated at a much more manageable pace in Goose. Goose is written in Go. Go has excellent asynchronous tools, but the author doesn't seem to have used them. Everything feels more or less single threaded. This is objectively worse, but it's also objectively better. It's easy to stop the AI from doing something wrong. It slows the AI down a little bit, but speed is not the AI's problem. My cognitive load trying to keep up with the AI is the problem. Just like with cars and roads, my AI tools need speed limits to allow for the reaction time of the driver.
With Goose, I set the session up, then spend about 20 minutes watching it. I will often have to give it one-sentence steering messages, but this drops in frequency as the session goes on. It is very often at first, but after a while, it just gets "into a groove" and starts cranking out good, high quality code. After that, I check on it every so often, sometimes stopping it and writing one-sentence steering messages, and then go back to what I was doing. If I forget about the session and come back, it will often have hit its configured maximum token limit or configured maximum number of turns, but it will still have written good stuff.
It all just makes me feel like Goose was written by people who really cared about bringing the AI under control without fuss. Goose just works. It feels boring. Boring is good.
Now we can see the worse-is-better effect in my experiences. More wasted context is better, since it tells the AI how to think. Slower is better, since it allows me time to think about what the AI is generating. The Lisp debugger breaking Goose but not Pi is better, since it forced me to force the AI to use the MCP server.
It makes me think about Golang versus JavaScript since those are the two languages in which Goose and Pi were written. Typed languages famously made the trade-off of slower development, but the friction they introduced help write more precise code. This is awful if you're writing a front end, because the front end needs to change faster than that, but lower down, things need to change less and be more correct since it's hard to inspect the effects of your code when you're outside the browser. I feel these positive effects of a slower, heavier AI harness when I use Goose.
I use NeoVim as an editor, and it felt like I was switching from Vim to VS Code when I went from Goose to Pi. I found what I thought I would find: a better plug-in system, a rich extension ecosystem, a more polished UX, just like VS Code. However, I also had worse results in coding. The one thing that Goose needed to be better at, it was better at, just like with Vim. There are still some really awesome things that I like about Pi, just like there are some really awesome things about VS Code. However, Vim has always been a better fit for my brain.
I think I'm going to stick with Goose for a little while longer. Form is nice, but in this business, function is non-negotiable.