singularity-forge/docs/dev/context-and-hooks/07-the-system-prompt-anatomy.md

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.