The Loop You Are Writing For
Before you write an extension, know the loop it attaches to: how a submitted message becomes a model request, how a tool call becomes a result, and when the run is over.
You are going to write an extension. Before that, you need to know what it is extending.
This is the step most people skip. They install Pi, run a task, get something nearly right, and start writing code against a mental model built from observation. The model is usually close enough to work and wrong in a few named places, and those places are where every interesting bug lives.
So this chapter is one turn of Pi, described exactly, with the places you can attach to it named.
One turn
Pi’s documentation calls this the agent loop. It is worth being precise, because “the loop” is used loosely and the precise version answers most extension questions on its own.
A submitted message is added to the active branch. Pi builds a model request from four things: the system prompt, the active branch, the available tools, and the model settings. That request goes out through the selected provider.
The provider streams back an assistant response, which can contain text and tool calls. Pi records the response, executes each tool call, and records the results. That completes one turn.
Then the question:
Do the tool results, or queued messages, require another model request?
|
+-- yes -> start another turn
|
+-- no -> the run ends
That is the whole loop. Everything else in this book is a decision about where in it to attach.
Four inputs, one request
The model request is assembled from four inputs. This decomposition is worth holding in your head, because each one is separately addressable, and almost every extension you will write manipulates exactly one of them.
| Input | What it is | How you change it |
|---|---|---|
| System prompt | Pi’s base instructions plus discovered context files | SYSTEM.md, APPEND_SYSTEM.md, AGENTS.md, or an event handler |
| Active branch | The conversation history that leads to the current entry | The conversation itself, or context / context_with_system |
| Available tools | What the model may call | pi.registerTool(), pi.setActiveTools() |
| Model settings | Which model, and how hard it thinks | /model, thinking level, settings.json |
An extension that inserts something into the transcript is working on the second input. An extension that registers a tool is working on the third. They feel different from the outside and behave nothing alike from the inside, which is the subject of chapter 18.
What is the active branch
Pi stores sessions as trees. Each entry has an ID and refers to its parent. The path from the root to the current entry is the active branch, and that path is what supplies the history for the next request.
Two consequences follow immediately, and both of them will save you later.
The branch is not the session. A session file can contain several branches. Going back and editing creates another one. Everything on the other branches still exists in the file. When something inspects “the conversation,” it has to decide whether it means the active branch or the whole tree — and these are different questions with different right answers.
Compaction does not delete. When Pi compacts, it inserts a summary entry that replaces older messages in subsequent requests. The original entries stay in the tree. This is why a compacted session can still be inspected in full, and why a compaction bug is usually a bug about which entries you counted rather than a bug about missing entries.
Chapter 24 covers the tree properly and chapter 23 covers compaction. For now the only thing to hold is: history is a tree, and the next request reads one path through it.
The turn is not the run
One more distinction, and it is the one that catches most extension authors.
A turn is one model request plus the tool execution that follows it. A run is the whole sequence — turn, tool results, another turn, tool results, until nothing more is needed.
Wrong: “
agent_endfired, so the run is over.”Correct: “
agent_endclosed one low-level agent run. Automatic retry, overflow recovery, compaction, steering or follow-up work can still follow.”
Two boundaries sit between them, and Pi’s documentation treats them with unusual care:
agent_before_settle— the final actionable boundary. A handler here can append entries and request one continuation.agent_settled— final and notification-only. Nothing can continue after it.
Both matter, and they matter most in Part III, where automatic retries, recovery,
compaction and queued work can all continue after agent_end. Chapter 16 is the
full treatment.
Queued messages
Two kinds of message can arrive while a run is in progress, and they land in different places.
Steering messages enter after the current assistant turn. They do not interrupt the tool calls already in flight; they land once that turn closes.
Follow-up messages enter after the agent has finished its pending work — after the run has settled.
And aborting stops the current run and returns queued messages to the editor, rather than discarding them.
This is a small design with a large consequence: there is no mechanism for interrupting a turn mid-flight. If you need work to stop now, you abort the run, and anything queued comes back to a human. Chapter 25 covers the mechanics; what matters here is that you know the queue exists and where it drains.
Where the context comes from
Before the request goes out, Pi assembles the prompt. This part surprises people, so it is worth stating plainly.
The system prompt is built from Pi’s base instructions and discovered context files. The request also carries tool definitions and skill descriptions.
Full skill instructions are not included. They are loaded on demand, when a task matches a skill’s description. That is what keeps a directory of skills from costing you context before you have used any of them.
Extensions can add instructions or transform context. Prompt templates expand in the editor before anything becomes a user message. Selected files, images, pasted text and shell output can all become message content.
So when the model “knows” something, it is because one of these put it there:
base instructions
context files AGENTS.md and friends, discovered by directory
APPEND_SYSTEM.md additive instructions
SYSTEM.md replaces the base prompt
skill descriptions always present
skill instructions loaded on demand, when matched
tool definitions always present, for active tools
extension additions whatever you add, by event
editor input prompt templates, @-selected files, pasted content
That list is the context. There is nothing else. When you are debugging why the model did or did not know something, this is the list you work down.
The loop, as a drawing
flowchart TD
IN["INPUT<br/>message added to the active branch"] --> REQ["REQUEST<br/>system prompt, active branch,<br/>tools, model settings"]
REQ --> PROV["PROVIDER"]
PROV --> RESP["RESPONSE<br/>text and tool calls"]
RESP --> EXEC["EXECUTION<br/>run each tool call, record each result"]
EXEC --> MORE{"more needed?"}
MORE -->|yes| REQ
MORE -->|no| DONE["RUN ENDS"]
Every integration point in this book is a box on that diagram, and each one names where an extension attaches:
flowchart TD
X["EXTENSION"] --> T["registerTool"] --> E["EXECUTION"]
X --> C["context handler"] --> R["REQUEST"]
X --> V["pi.on, any event"] --> G["the edges between boxes"]
X --> P["registerProvider"] --> PR["PROVIDER"]
A prompt template changes what arrives at INPUT.
Why this chapter is first
An extension author who does not know how context is assembled writes an extension that fights the system, and — this is the expensive part — cannot tell whether the bug is theirs or Pi’s.
The symptoms all look the same from the outside. The model ignores your instructions. Your tool never appears. Your injected content vanishes after a compaction. Your handler fires at the wrong moment. In every case the question you need answered is the same, and it is answered by the diagram above: which box did I attach to, and is that box the one I meant?
Next
You can now name the loop and its seams, which is enough to debug against it but not yet enough to have watched one run. The next chapter starts the process, runs a real task through it, and marks the decisions in that first hour that you will keep making for the rest of the book. If you already have Pi running, skip to chapter 11 and come back here when something behaves strangely.
Chapter 2: installation, a first session, and a first task.