Skip to content

Course · Pi · The core

The tiny prompt, and why

Pi’s extension API can print the literal system prompt behind any given turn (the observability lesson shows the two calls; you write your first extension in a later chapter). Here’s the claim worth checking before you check it yourself: printed in full - identity line, tool list, guidelines, doc pointers, project context, skills metadata, and the working directory - Pi’s default prompt runs under roughly 1,000 tokens. That figure is its author’s: system prompt and tool definitions together, as of that November 2025 post, so treat it as a dated claim to check rather than a spec. That’s dramatically smaller than the thousands of tokens of standing instructions you’ve likely seen from other agents before you’ve typed a single word to them.

Go print it on stash (the course’s running example, the small FastAPI feed reader the module opener describes; any repo whose context files sit where Pi doesn’t read them shows the same result) in a moment and you’ll see whether that number holds up on a real project, not just in its author’s post. Pi’s isn’t thin because someone forgot to write the rest of it. It’s thin on purpose, and the purpose is worth understanding before you start editing it yourself in a later chapter.

The prompt isn’t a static block of text sitting in a file somewhere - it’s assembled fresh, every turn, by a small template builder. In the order it gets built:

  1. One identity line - something to the effect of “You are an expert coding assistant operating inside pi, a coding agent harness.” No persona essay, no list of virtues.
  2. The available tools - whichever ones are actually active for this session. If a tool isn’t enabled, it simply doesn’t appear; there’s no boilerplate describing capabilities you don’t have.
  3. Guidelines, conditionally included based on what’s active. This is the part worth sitting with, because it’s not one fixed paragraph - it changes shape depending on your tool set. With read and edit both active, it adds a line like “use read to examine files before editing.” If bash is your only way to explore the filesystem, it tells the model to lean on bash for ls/rg/find-style work; if dedicated grep/find/ls tools are present instead, the guidance flips to prefer those over bash for exploration.
  4. A pointer to Pi’s own docs - file paths the model can read itself if it needs more detail, rather than that detail being inlined into every single turn whether it’s relevant or not.
  5. Project context, injected inside <project_context> tags - this is where AGENTS.md-style context files land, which matters enough to get its own module next chapter.
  6. Skills, as metadata only - name, description, and file path for each one, never the full skill body. The model reads a skill’s actual instructions only when a task matches it. This is the same lazy-loading instinct as read’s truncation from two lessons ago, applied to prompt construction itself: don’t spend tokens on something until it’s actually needed.
  7. Runtime metadata - the current working directory, so paths in tool calls and bash commands resolve against the directory you launched Pi from rather than somewhere the model has to guess.

write gets its own note steering it toward new files and full rewrites, reinforcing what you already saw edit enforce structurally earlier in this module. That’s the whole shape. No fifty-line list of edge-case reminders, no repeated safety disclaimers, no verbose description of a tool that isn’t even turned on. Each piece earns its place, and the optional ones appear only when relevant - nothing is inlined “just in case.”

Context files layer in automatically, and you can watch it happen

Section titled “Context files layer in automatically, and you can watch it happen”

Before any of that project context lands in <project_context> tags, Pi walks the directory tree collecting AGENTS.md-style files - global first (~/.pi/agent/AGENTS.md), then every parent directory on the way down, then the current one. Everything matching is concatenated in that order, so a closer file lands later in the prompt than a farther one.

Across levels, the loader has no dedup and no override mechanic - a file two directories down doesn’t replace the global one, it follows it. The one override is local: an AGENTS.override.md replaces AGENTS.md or CLAUDE.md in the same directory only, and it doesn’t suppress files from any other level (Pi’s configuration docs). If you write conflicting instructions at two levels, both are in the string, in walk order, and which one the model obeys is up to the model reading them, not a rule deciding for it.

You can disable the whole mechanism with --no-context-files if you ever need Pi reasoning with zero project context at all - useful for isolating whether a weird behavior is coming from your rules file or from the model itself.

Closer files are read after farther ones - ordering you can verify in the printed string, not precedence you have to take on faith.

stash makes the boundaries concrete: its conventions live in a root SYSTEM.md the repo arrived with, and it doesn’t match any name the loader walks, so <project_context> comes up empty on this repo today. Moving those rules to where Pi actually reads them is next chapter’s opening move, not something to fully unpack here. What matters now is just this: it’s not magic, it’s a walk up the filesystem, and you already know how to go check exactly what landed in the prompt because of it.

The prompt is meant to be replaced, not just read

Section titled “The prompt is meant to be replaced, not just read”

Every piece of this is overridable, and Pi gives you several levels of override rather than one blunt switch:

  • .pi/SYSTEM.md (project) or ~/.pi/agent/SYSTEM.md (global) replaces the default template wholesale (these levels and flags are documented in Pi’s usage docs, the page to check for current names) - context files and appended sections still get layered on top, but the base identity/tools/guidelines text is gone, swapped for whatever you write instead.
  • APPEND_SYSTEM.md at either scope adds to the default without replacing it - the gentler option when you just want to bolt on a house rule or two.
  • CLI flags - --system-prompt <text> and --append-system-prompt <text> do the same two things for a single invocation, no file required.
  • A before_agent_start extension hook can rewrite the prompt for that turn only - the base template resets right after, and if more than one extension hooks this event, each one sees the previous extension’s already-modified version before adding its own - layering you can watch in the same printed string, by printing the prompt with and without the file and diffing the two. You’ll build one of these in the extensions chapter.

And when a custom tool registers its own promptSnippet and promptGuidelines, those get spliced into the assembled prompt the same way the built-in tools’ guidance does - and disappear automatically the moment that tool is disabled. Every override mechanism composes: a global SYSTEM.md, a project APPEND_SYSTEM.md, and a per-turn hook can all be active on the same turn at once, each layering on top of the last. The whole thing is rebuilt from scratch every time, never left stale from a config that changed three sessions ago.

The argument behind all of this, in its author’s own words: frontier models have been “RL-trained up the wazoo” and already understand what a coding agent is and how to drive read/write/edit/bash without three paragraphs of hand-holding per tool. A bloated system prompt, on that view, is “unnecessary overhead” - tokens spent every single turn restating things the model already knows, that could instead go toward your actual project context, or just not be spent at all.

The guiding line for what makes it into the template is blunt: if it isn’t needed, it doesn’t get built.

That’s a bet, not a law of nature - it assumes you’re pointing Pi at a genuinely capable model, and it trades away some of the guardrail-by-verbose-instruction style you’ll find in more opinionated agents. But it’s a bet you can now personally audit rather than take on faith. The printing method is the observability lesson’s; go run ctx.getSystemPrompt() on stash right now and count what comes back.

Here’s what to look for: with <project_context> empty, the total should land close to the under-1,000-token claim from the top of this lesson (this page didn’t capture a count, so your number is the evidence). If it does, that’s not because stash happens to be a small project, but because every tool that isn’t enabled, every skill you haven’t triggered, every doc file you haven’t asked to read, genuinely isn’t in the string you just printed. Then the claim holds on a real project, not just in Pi’s own numbers.

You can now see everything: the exact tools, the loop (which imposes no step ceiling), the live context, and the tiny prompt underneath all of it. Run the four questions from the observability lesson over everything this module showed you - what layer, what it costs, what it buys, who decided - and every answer to the last one comes back the same: the tool schemas, the missing step ceiling, the prompt template are all fixed costs, all chosen by Pi before you touched anything.

That sameness is the finding. Nothing in this module was your choice yet, and seeing isn’t the same as controlling: you can read the window now, down to the literal string sent this turn, and it fills anyway. The rest of this course is about putting your own decisions into that window, one layer at a time. Next: turn what you know about stash into context Pi carries automatically.