12 KiB
The System Prompt Anatomy
How pi's system prompt is built, what goes into it, when it's rebuilt, and every lever you have to shape it.
The Final Prompt Structure
When buildSystemPrompt() runs, it assembles sections in this exact order:
┌──────────────────────────────────────────────────┐
│ 1. Base prompt (default or SYSTEM.md override) │
│ ├── Identity statement │
│ ├── Available tools list │
│ ├── Custom tools note │
│ ├── Guidelines │
│ └── Pi documentation pointers │
│ │
│ 2. Append system prompt (APPEND_SYSTEM.md) │
│ │
│ 3. Project context files │
│ ├── ~/.sf/agent/AGENTS.md (global) │
│ ├── Ancestor AGENTS.md / CLAUDE.md files │
│ └── cwd AGENTS.md / CLAUDE.md │
│ │
│ 4. Skills listing │
│ └── <available_skills> XML block │
│ │
│ 5. Date/time and working directory │
└──────────────────────────────────────────────────┘
After buildSystemPrompt(), extensions can further modify via before_agent_start.
Section 1: The Base Prompt
Default Base Prompt (no SYSTEM.md)
When no SYSTEM.md exists, pi uses its built-in base:
You are a purpose-driven software compiler. You take bounded intent, produce a falsifiable purpose contract (the PDD/TDD gate), then write failing tests before implementation. You help users by reading files, executing commands, editing code, and writing new files.
Available tools:
- read: Read file contents
- bash: Execute bash commands (ls, grep, find, etc.)
- edit: Make surgical edits to files (find exact text and replace)
- write: Create or overwrite files
- my_custom_tool: [promptSnippet or description]
In addition to the tools above, you may have access to other custom tools
depending on the project.
Guidelines:
- Use bash for file operations like ls, rg, find
- Use read to examine files before editing. You must use this tool instead of cat or sed.
- Use edit for precise changes (old text must match exactly)
- Use write only for new files or complete rewrites
- [extension tool promptGuidelines inserted here]
- Be concise in your responses
- Show file paths clearly when working with files
Pi documentation (read only when the user asks about pi itself...):
- Main documentation: [path]
- Additional docs: [path]
- Examples: [path]
SYSTEM.md Override (full replacement)
If .sf/SYSTEM.md (project) or ~/.sf/agent/SYSTEM.md (global) exists, its contents completely replace the default base prompt above. The tools list, guidelines, pi docs pointers — all gone. You own the entire base.
Project takes precedence over global. Only one SYSTEM.md is used (first found wins).
What still gets appended even with a custom SYSTEM.md:
- APPEND_SYSTEM.md content
- Project context files (AGENTS.md / CLAUDE.md)
- Skills listing (if the
readtool is active) - Date/time and cwd
What you lose:
- The entire default prompt structure
- Built-in tool descriptions and guidelines
- Pi documentation pointers
- Dynamic guidelines from
promptGuidelineson tools
How Tool Descriptions Appear
Each active tool gets a line in "Available tools":
- toolname: [one-line description]
The description is determined by priority:
promptSnippetfrom the tool registration (if provided)- Built-in description from
toolDescriptionsmap (for read, bash, edit, write, grep, find, ls) - The tool's
nameas fallback
promptSnippet is normalized: newlines collapsed to spaces, trimmed to a single line.
How Guidelines Are Built
Guidelines are assembled dynamically based on which tools are active:
| Condition | Guideline |
|---|---|
| bash active, no grep/find/ls | "Use bash for file operations like ls, rg, find" |
| bash active + grep/find/ls | "Prefer grep/find/ls tools over bash for file exploration" |
| read + edit active | "Use read to examine files before editing" |
| edit active | "Use edit for precise changes (old text must match exactly)" |
| write active | "Use write only for new files or complete rewrites" |
| edit or write active | "When summarizing your actions, output plain text directly" |
| Always | "Be concise in your responses" |
| Always | "Show file paths clearly when working with files" |
Extension tool guidelines from promptGuidelines are appended after the built-in guidelines. They're deduplicated (same string appears only once even if multiple tools register it).
Section 2: Append System Prompt
If .sf/APPEND_SYSTEM.md (project) or ~/.sf/agent/APPEND_SYSTEM.md (global) exists, its contents are appended after the base prompt.
This is the safe way to add project-wide instructions without replacing the default prompt. It works with both the default base and a custom SYSTEM.md.
Section 3: Project Context Files
Pi walks the filesystem collecting context files:
1. ~/.sf/agent/AGENTS.md (global)
2. Walk from cwd upward to root:
- Each directory: check for AGENTS.md, then CLAUDE.md (first found wins per directory)
- Files are collected root-down (ancestors first, cwd last)
All found files are concatenated under a "# Project Context" header:
# Project Context
Project-specific instructions and guidelines:
## /Users/you/.sf/agent/AGENTS.md
[global AGENTS.md content]
## /Users/you/projects/myapp/AGENTS.md
[project AGENTS.md content]
AGENTS.md vs CLAUDE.md: Both are treated identically. Per directory, AGENTS.md is checked first. If it exists, CLAUDE.md in the same directory is skipped.
Section 4: Skills Listing
If the read tool is active and skills are loaded, an XML block is appended:
The following skills provide specialized instructions for specific tasks.
Use the read tool to load a skill's file when the task matches its description.
When a skill file references a relative path, resolve it against the skill directory.
<available_skills>
<skill>
<name>commit-outstanding</name>
<description>Commit all uncommitted files in logical groups</description>
<location>/Users/you/.agents/skills/commit-outstanding/SKILL.md</location>
</skill>
</available_skills>
Skills with disable-model-invocation: true in their frontmatter are excluded from this listing.
Key design: Only names, descriptions, and file paths go into the system prompt. The full skill content is NOT loaded. The agent uses the read tool to load specific skills on demand. This keeps the system prompt small even with many skills.
Section 5: Date/Time and CWD
Always appended last:
Current date and time: Saturday, March 7, 2026 at 08:55:05 AM CST
Current working directory: /Users/you/projects/myapp
When the System Prompt Is Rebuilt
The base system prompt (_baseSystemPrompt) is rebuilt in these situations:
| Trigger | What happens |
|---|---|
Startup (_buildRuntime) |
Full rebuild with initial tool set |
setActiveToolsByName() |
Rebuild with new tool set (guidelines and snippets change) |
reload() (/reload) |
Full rebuild — reloads SYSTEM.md, APPEND_SYSTEM.md, context files, skills, extensions |
extendResourcesFromExtensions() |
Rebuild after resources_discover adds new skills/prompts/themes |
_refreshToolRegistry() |
Rebuild when extension tools change dynamically |
Per-Prompt Modifications
On each user prompt, the before_agent_start hook can modify the system prompt. This modification is not persisted — the base prompt is restored if no extension modifies it on the next prompt:
User prompt 1:
before_agent_start → extensions modify system prompt → LLM sees modified version
User prompt 2:
before_agent_start → no extensions modify → LLM sees base system prompt (reset)
This means before_agent_start modifications are truly per-prompt. You cannot make a permanent system prompt change through this hook alone (the change must be re-applied every time).
Every Lever for Shaping the System Prompt
From static configuration to dynamic extension hooks, ordered from broadest to most targeted:
Static (file-based, loaded at startup)
| Mechanism | Scope | Effect |
|---|---|---|
SYSTEM.md |
Replace base prompt entirely | Nuclear option — you own everything |
APPEND_SYSTEM.md |
Append to base prompt | Safe additive instructions |
AGENTS.md / CLAUDE.md |
Project context section | Per-project conventions and rules |
Skill SKILL.md files |
Skills listing | On-demand capability descriptions |
Dynamic (extension-based, runtime)
| Mechanism | Scope | Timing | Effect |
|---|---|---|---|
before_agent_start → systemPrompt |
Full prompt | Per user prompt | Modify/append/replace system prompt |
promptSnippet on tools |
Tool description line | When tool set changes | Custom one-liner in "Available tools" |
promptGuidelines on tools |
Guidelines section | When tool set changes | Add behavioral bullets |
pi.setActiveTools() |
Tool list + guidelines | Immediate, next prompt | Add/remove tools (rebuilds prompt) |
resources_discover event |
Skills listing | Startup + reload | Inject additional skills from extensions |
Per-Turn (message-based, not system prompt)
These don't modify the system prompt but add to what the LLM sees:
| Mechanism | Timing | Effect |
|---|---|---|
before_agent_start → message |
Per user prompt | Inject custom message (becomes user role) |
context event |
Per LLM turn | Filter/inject/transform message array |
pi.sendMessage() |
Anytime | Inject custom message into conversation |
Practical Tradeoffs
SYSTEM.md vs before_agent_start
| SYSTEM.md | before_agent_start | |
|---|---|---|
| Persistence | Permanent until file changes | Per-prompt, must re-apply |
| Dynamism | Static file content | Can compute based on state |
| Tool awareness | Loses built-in tool guidelines | Preserves base prompt, appends |
| Composability | Only one SYSTEM.md (project or global) | Multiple extensions can chain |
Recommendation: Use SYSTEM.md only when you genuinely need to replace the entire prompt (e.g., custom agent personality, non-coding use case). Use before_agent_start for everything else.
APPEND_SYSTEM.md vs AGENTS.md
Both append content, but they appear in different sections:
- APPEND_SYSTEM.md appears immediately after the base prompt, before "# Project Context"
- AGENTS.md appears inside "# Project Context" with a
## filepathheader
Functionally equivalent for the LLM. Use APPEND_SYSTEM.md for instructions that feel like system-level directives. Use AGENTS.md for project-specific conventions and context.
promptGuidelines vs before_agent_start
| promptGuidelines | before_agent_start | |
|---|---|---|
| Scope | Only when the tool is active | Always (or conditionally in your code) |
| Positioning | Inside "Guidelines" section | Appended to end (or wherever you put it) |
| Tool coupling | Automatically appears/disappears with tool | Independent of tool state |
Recommendation: Use promptGuidelines for instructions directly related to tool usage. Use before_agent_start for behavioral modifications independent of tool state.
The Full Context Surface Area
Everything the LLM sees on a given turn:
System prompt (built from all sources above + before_agent_start mods)
+
Message array (after context event filtering + convertToLlm):
- Compaction summaries (user role)
- Branch summaries (user role)
- Historical user/assistant/toolResult messages
- Bash execution results (user role, unless !! excluded)
- Custom messages from extensions (user role)
- Current prompt + before_agent_start injected messages
+
Tool definitions:
- name, description, parameter JSON schema
- Only for active tools (pi.getActiveTools())
Understanding this complete surface area — and which levers control which parts — is the key to effective context engineering in pi.