This is the first in a series of blog posts about my attempt to learn how coding agents work from the very beginnings. I will be using pi.dev by @badlogicgames as my blueprint. Goal is to really start learning/understanding this from the basics and pi being OSS really gives a good starting point as reference for sth "good".
The plan:
- clone the pi monorepo from https://github.com/badlogic/pi-mono
- start a conversation with some clanker about what this all is, what it does ... etc.
- forge these conversations into a series of blog posts on my website
And then build a minimal version of pi in Ruby as this is the programming language I best know and understand.
It's empty now, but that is where it's going to be:
So this is part one of the series and it all starts by understanding how pi actually works, what it does and how it’s put together. A lot of the writing on this will be LLM generated as you might guess all this is going to be a rather LLM heavy operation.
As I wanna share everything, you’re going to find my LLM conversation history in every post, at the end of the page
The “big idea” of pi in plain language
The pi monorepo is a set of building blocks for a tool called pi: something you can talk to in your terminal (or in Slack / a web UI) that can use tools (read files, run commands, edit code) while it chats with you.
Think of it like:
- A brain connector (talks to many AI providers)
- A manager (decides when to call tools and how to continue)
- A shell/body (terminal UI, sessions, settings, plugins)
What is the core?
There are two “cores”, one inside the other:
Core #1: “Talk to AI in a consistent way” (packages/ai)
No matter whether the AI is OpenAI, Anthropic, Google, etc., pi tries to make the interaction look the same: same message format, same event stream (“text arrived”, “tool call requested”, “done”, “error”), same cost/token reporting.
Core #2: “Agent loop” (packages/agent)
This is the logic that turns a chat model into an agent: → Ask the AI something
→ If the AI says “I need to use a tool”, run the tool
→ Feed the result back to the AI
- Repeat until the AI is finished
Then packages/coding-agent wraps that core with all the “product” stuff (terminal UI, saved sessions, extensions, commands like /model, etc.).
What happens when you use pi?
Imagine you type: “Rename this function everywhere and fix the tests.”
Behind the scenes, pi runs a loop like this:
- You send a message → pi stores it as part of the conversation history (so the AI can remember what happened).
- pi asks the AI for the next step It sends the AI:
- The AI starts streaming an answer → pi prints the response as it comes in (so you don’t wait for the whole thing). → Sometimes the AI just writes text. → Sometimes it says: “Call tool X with these arguments.”
- If the AI requests a tool, pi runs it
Example: AI requests
readonsrc/foo.ts→ pi runs the tool locally, gets the result (file contents), and records it. - pi sends the tool result back to the AI Now the AI has new information (“here is the file content”). The AI continues: maybe it asks to edit, maybe run tests, maybe read more.
- Repeat until the AI stops requesting tools When the AI finally says “done”, pi ends that turn. That’s the core behavior: chat → tool calls → tool results → chat → … until done.
→ the conversation so far
→ the “system prompt” (house rules / instructions)
→ a list of available tools (“you can read files, edit files, run bash…”)
How is it done (conceptually, not code)
Everything is “messages + events”
pi treats nearly everything as a stream of events:
- “a user message started/ended”
- “assistant text chunk arrived”
- “assistant is calling tool X”
- “tool execution started/updated/ended”
- “turn ended”
This event stream is what drives:
- the terminal UI updates
- session saving
- extensions/hooks
There’s a strict separation of responsibilities in pi
LLM connector: “How do I talk to provider X and stream results?”
Agent loop: “Given tools + messages, how do I keep going until finished?”
Session/product wrapper: “How do I save history, load skills, offer /commands, show UI, handle retries, compact long chats?”
Now intellectually understanding what pi does in general it’s time to start getting the hands dirty. Learning, or optimal learning at least needs, according to Andrew Huberman and Dr. Michael Kilgard three complementary ingredients for learning and useful rewiring of the brain:
Learning
- focused, effortful practice (friction)
- sleep/rest (for consolidation)
- reflection/self-testing (to strengthen retention).
Training should be specific and repeated with feedback; mental rehearsal helps most when it rehearses real actions already experienced, and spaced repetition across many trials is crucial.
from the “Huberman Lab Podcast on How to Rewire your Brain & Learn Faster with Dr. Michael Kilgard.
How to Rewire Your Brain & Learn Faster | Dr. Michael Kilgard
Huberman Lab · Episode
open.spotify.com
So this is what I am going to do, and what could better than actually (re-)building the thing for focused, effortful practice and self-testing?
In the next blog post it’s about starting to build RubiPi 2 | Learning by doing