System Prompt Construction
system-prompt.ts is the assembly workshop for pi's default system prompt. One file, one buildSystemPrompt function, turning the tool list, guidelines, pi doc paths, project context files, skills, date, and cwd into the final prompt string. createAgentSession doesn't call it directly — AgentSession rebuilds it before each prompt based on the currently active tools, so the prompt stays in sync when tools are added or removed.
Responsibilities
- Assemble the default prompt: when
buildSystemPrompthas nocustomPrompt, it takes the default path and produces the full prompt starting with "You are an expert coding assistant operating inside pi". Seepackages/coding-agent/src/core/system-prompt.ts:28-80andpackages/coding-agent/src/core/system-prompt.ts:131-171. - Custom prompt path: when
customPromptis provided, the default template is skipped and only contextFiles, skills, date, and cwd are appended. Seepackages/coding-agent/src/core/system-prompt.ts:53-80. - Tool list and guidelines:
selectedToolsdecides which tools are visible, and guidelines are generated dynamically based on the tool combination (e.g., when bash+grep are both present, prefer grep). Seepackages/coding-agent/src/core/system-prompt.ts:89-129. - Prompt snippet injection: each tool's
promptSnippetis a one-line description, concatenated into the "Available tools" list, so the LLM can judge what each tool is for. Seepackages/coding-agent/src/core/system-prompt.ts:89-92. - Skill attachment:
formatSkillsForPromptturns visible skills (non-disableModelInvocation) into an<available_skills>XML block. Seepackages/coding-agent/src/core/system-prompt.ts:162-165. - Doc self-pointers: the prompt includes pi's own doc paths (readme, docs, examples) and tells the LLM to read them when the user asks about pi. See
packages/coding-agent/src/core/system-prompt.ts:141-147.
Design Motivation
Why not have AgentSession hold the prompt string directly? Because the toolset is dynamic — users can add or remove tools mid-session via /tools, and extensions can register new tools. If the prompt were a static string, the LLM would keep calling disabled tools per the old prompt after a tool change. buildSystemPrompt regenerates on every call, keeping the prompt strictly aligned with the currently active toolset.
Why distinguish the default prompt from the custom prompt path? The default is pi's officially tuned version, including the tool list, guidelines, and doc self-pointers. A custom prompt is fully user- or extension-provided and only wants contextFiles and skills appended. Both paths share the append logic for contextFiles, skills, date, and cwd, but the default path also assembles the toolsList and guidelines.
The dynamic guidelines are worth noting: if bash and grep/find/ls are all enabled, the prompt suggests "Prefer grep/find/ls tools over bash for file exploration (faster, respects .gitignore)"; if bash is present but grep/find/ls are not, it suggests "Use bash for file operations like ls, rg, find". This lets the LLM pick an efficient path under any tool combination.
Key Files
packages/coding-agent/src/core/system-prompt.ts:8-25—BuildSystemPromptOptions, all optional inputs.packages/coding-agent/src/core/system-prompt.ts:28-52—buildSystemPromptsignature and preprocessing, resolving cwd, date, contextFiles, skills.packages/coding-agent/src/core/system-prompt.ts:53-80— custom prompt path.packages/coding-agent/src/core/system-prompt.ts:89-129— tool list and dynamic guidelines, including thehasBash/hasGrep/hasFind/hasLschecks.packages/coding-agent/src/core/system-prompt.ts:131-147— default prompt body, including the pi doc self-pointer section.packages/coding-agent/src/core/system-prompt.ts:149-171— appendSystemPrompt, contextFiles, skills, date, cwd appends.
Dynamic guidelines, deduped via a Set:
// packages/coding-agent/src/core/system-prompt.ts:105-129
const hasBash = tools.includes("bash");
const hasGrep = tools.includes("grep");
const hasFind = tools.includes("find");
const hasLs = tools.includes("ls");
if (hasBash && !hasGrep && !hasFind && !hasLs) {
addGuideline("Use bash for file operations like ls, rg, find");
} else if (hasBash && (hasGrep || hasFind || hasLs)) {
addGuideline("Prefer grep/find/ls tools over bash for file exploration (faster, respects .gitignore)");
}
for (const guideline of promptGuidelines ?? []) {
const normalized = guideline.trim();
if (normalized.length > 0) {
addGuideline(normalized);
}
}
// Always include these
addGuideline("Be concise in your responses");
addGuideline("Show file paths clearly when working with files");Date and cwd are appended at the end of the default prompt, deliberately placed last so the LLM notices them:
// packages/coding-agent/src/core/system-prompt.ts:167-171
if (hasRead && skills.length > 0) {
prompt += formatSkillsForPrompt(skills);
}
// Add date and working directory last
prompt += `\nCurrent date: ${date}`;
prompt += `\nCurrent working directory: ${promptCwd}`;
return prompt;Data Flow
Prompt construction flow:
Edges and Failures
- toolsList with no tools: when
visibleToolsis empty, the prompt shows(none)rather than throwing, so the LLM knows no built-in tools are available. Seepackages/coding-agent/src/core/system-prompt.ts:91-92. - Skills attached only when read is available: skill files need the read tool to load, so attaching the skill list without read would confuse the LLM. See
packages/coding-agent/src/core/system-prompt.ts:70-73andpackages/coding-agent/src/core/system-prompt.ts:162-165. - customPrompt also respects the read check:
customPromptHasRead = !selectedTools || selectedTools.includes("read"). Seepackages/coding-agent/src/core/system-prompt.ts:70-73. - cwd path to posix:
promptCwd = resolvedCwd.replace(/\\/g, "/"), so Windows paths also use forward slashes and the LLM doesn't misread backslashes as escapes. Seepackages/coding-agent/src/core/system-prompt.ts:39-40. - guidelines dedup:
guidelinesSetprevents duplicate entries from being passed in viapromptGuidelines. Seepackages/coding-agent/src/core/system-prompt.ts:96-103.
Summary
buildSystemPrompt is the prompt assembly workshop: it generates guidelines dynamically based on the current toolset, and the custom and default paths share the append logic for contextFiles/skills/date/cwd. For skill loading and formatting, see Skill System; for message conversion, see Message Types and convertToLlm; for the toolset itself, see Tools: read/bash/edit/write/grep/find/ls.