ππ Agent
Chapter 12 / 21

System Prompt, Skills & Context Files

The system prompt is an assembly pipeline

AGENTS.md walks up from cwd; skills follow agentskills.io
No simulation scenario available for this chapter

Deep Dive

~7 min read·7048 chars

The system prompt is an assembly pipeline; AGENTS.md walks up from cwd

Learning Objectives

After this chapter you will be able to:

  • Explain the five-stage pipeline of buildSystemPrompt and its priorities
  • Describe the AGENTS.md walk-up mechanism and its design rationale
  • Explain how Skills follow the agentskills.io standard and inject into the prompt
  • Understand the security implications of .pi/SYSTEM.md wholesale identity replacement

Key Source Files

FileLinesResponsibility
packages/coding-agent/src/core/system-prompt.ts162buildSystemPrompt: five-stage pipeline
packages/coding-agent/src/core/skills.ts487Skills loading, formatting, trigger parsing
packages/coding-agent/src/core/prompt-templates.ts~200Reusable prompt fragments
packages/coding-agent/src/core/resource-loader.ts1043AGENTS.md / context file loading

Design Motivation: Why Can't the System Prompt Be Hardcoded?

Problem: Different projects need different Agent behaviors — React projects follow component conventions, Rust projects watch lifetimes, enterprise projects have internal API contracts. A hardcoded system prompt means forking pi for every project.

Analogy: Like Nginx config inheritance — global config → virtual host → location block, each layer overriding the previous. pi's system prompt is also multi-source assembly with layered overrides.

Design goals:

  • Default behavior works out of the box (zero config)
  • Project-level conventions inject naturally (AGENTS.md)
  • Power users can fully replace (.pi/SYSTEM.md)
  • Skills dynamically extend capabilities (agentskills.io)

The Five-Stage Pipeline

buildSystemPrompt(cwd, tools, skills):

  ① Identity layer (wholesale replaceable)
     Default: pi's built-in DEFAULT_SYSTEM_PROMPT
     Replace: .pi/SYSTEM.md or ~/.pi/agent/SYSTEM.md
     ├── exists → completely replace default identity
     └── absent → use default

  ② Tool instructions
     formatTools(tools) → name, params, usage for each tool
     Tells the model "what hands you have"

  ③ Project context
     <project_context>
       AGENTS.md / CLAUDE.md content
     </project_context>
     Project conventions injected — model "reads" project norms

  ④ Skills formatting
     formatSkillsForPrompt(skills) → trigger conditions and descriptions
     Tells the model "what skills you know"

  ⑤ Append layer
     APPEND_SYSTEM.md content appended at the end
     Doesn't change identity, only adds constraints — lightest customization

Priority: ① is "who I am," ⑤ is "also remember." ① is replaceable, ⑤ is append-only — two granularities of customization.

AGENTS.md Walk-Up

// resource-loader.ts (simplified)
function loadProjectContextFiles(cwd: string): string[] {
  const results: string[] = []
  let dir = cwd

  while (dir !== "/" && dir !== "") {
    for (const name of ["AGENTS.md", "CLAUDE.md"]) {
      const path = join(dir, name)
      if (existsSync(path)) results.push(readFileSync(path, "utf-8"))
    }
    dir = dirname(dir)
  }
  return results  // ordered near to far
}

Example:

cwd = /repo/packages/ai/src
  → /repo/packages/ai/src/AGENTS.md?  no
  → /repo/packages/ai/AGENTS.md?      no
  → /repo/packages/AGENTS.md?         yes → read
  → /repo/AGENTS.md?                  yes → read
  → / (stop)

Why walk up?

  • In monorepos, root AGENTS.md defines global conventions (code style, CI norms)
  • Sub-packages can have their own AGENTS.md adding local conventions
  • Developers launching pi at any depth inherit project conventions

Analogy: Like .gitignore hierarchical inheritance — root rules apply to all subdirectories; subdirectories can add more.

Skills: The agentskills.io Standard

// skills.ts (conceptual)
interface Skill {
  id: string              // "add-llm-provider"
  name: string            // "Add LLM Provider"
  description: string     // trigger condition description
  content: string         // SKILL.md body
  triggers: string[]      // trigger keywords
}

function formatSkillsForPrompt(skills: Skill[]): string {
  return skills.map(s =>
    `[skill:${s.id}] ${s.name}: ${s.description}`
  ).join("\n")
}

Workflow:

  1. Skills loaded from ~/.pi/skills/ and .pi/skills/
  2. Formatted into system prompt — model "sees" its available skills
  3. When model reply contains pi.skill_id marker, parseSkillBlock parses and executes
  4. Follows agentskills.io open standard — portable across Agents

.pi/SYSTEM.md: Security Implications of Wholesale Replacement

If .pi/SYSTEM.md exists:
  Default identity is completely replaced
  ├── Benefit: enterprises can fully customize Agent persona
  └── Risk: malicious projects can inject "ignore all safety constraints"

Therefore:
  .pi/SYSTEM.md only takes effect in trusted projects (→ Chapter 10 trust gate)
  Untrusted project → ignore .pi/SYSTEM.md → use default safe identity

This is yet another reason for the trust gate (→ Chapter 10): Not only extensions can be malicious — system prompt replacement can too.

Engineering Insights

  1. system-prompt.ts is only 162 lines: The pipeline logic itself is simple (concatenate 5 sections). Complexity lives in resource-loader.ts (1043 lines) — handling file formats, encoding, size limits, circular references.

  2. skills.ts 487 lines: Mostly SKILL.md frontmatter parsing (YAML), trigger matching, permission validation. The agentskills.io standard defines a strict schema.

  3. Prompt length budget: System prompt can't grow unbounded — it occupies context window. resource-loader has maxBytes limits; exceeding truncates. AGENTS.md too long = squeezing conversation space.

Cross-Chapter Links

DirectionChapterRelationship
PrerequisiteCh01 Agent LoopsystemPrompt is AgentState's first field
PrerequisiteCh10 CLI ModesTrust gate determines if .pi/SYSTEM.md takes effect
Follow-upCh13 ExtensionsExtensions can append system prompt fragments
Follow-upCh03 CompactionSystem prompt is never compressed
Follow-upCh18 Settings & TrustComplete trust mechanism design

Check Your Understanding

  1. If AGENTS.md only searched cwd (not walking up), what problems would arise in monorepo scenarios?
  2. Why is .pi/SYSTEM.md "wholesale replace" while APPEND_SYSTEM.md is "append"? What scenarios suit each granularity?
  3. If the system prompt had no length limit, what problems would result?

Takeaways

  • System prompt is a five-stage assembly pipeline: identity → tools → project context → Skills → append
  • AGENTS.md walks up from cwd — monorepo conventions inherit naturally
  • Skills follow agentskills.io open standard, portable across Agents
  • .pi/SYSTEM.md wholesale replacement requires trust gate protection

This chapter is based on the main-branch source of earendil-works/pi.