315 lines
12 KiB
Markdown
315 lines
12 KiB
Markdown
# 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 `read` tool 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 `promptGuidelines` on tools
|
|
|
|
### How Tool Descriptions Appear
|
|
|
|
Each active tool gets a line in "Available tools":
|
|
|
|
```
|
|
- toolname: [one-line description]
|
|
```
|
|
|
|
The description is determined by priority:
|
|
1. `promptSnippet` from the tool registration (if provided)
|
|
2. Built-in description from `toolDescriptions` map (for read, bash, edit, write, grep, find, ls)
|
|
3. The tool's `name` as 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:
|
|
|
|
```markdown
|
|
# 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:
|
|
|
|
```xml
|
|
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 `## filepath` header
|
|
|
|
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.
|