← Pi Agents

The Project .pi Directory and Trust

Which project resources Pi loads only after trust, and which context files load regardless of the trust decision.

You clone a repository. It contains .pi/extensions/migrate-schema.ts, and migrate-schema.ts calls execSync with a string built from a prompt. Pi asks whether you trust the folder. You are being asked a real question: does “yes” merely acknowledge the repository, or does it allow that extension to load and run with your process permissions?

security.md answers it precisely, and the answer has a wrinkle worth knowing before you answer.

What triggers the prompt

Pi requires a project-trust decision when it finds any of these from the current working directory:

  • .pi/settings.json
  • .pi/mcp.json
  • .pi/extensions, .pi/skills, .pi/prompts, or .pi/themes
  • .pi/SYSTEM.md or .pi/APPEND_SYSTEM.md
  • project .agents/skills in the current directory or an ancestor directory

A bare .pi directory does not require trust. Trust is requested because something would load.

What trust admits

Granting trust allows Pi to load project settings, MCP servers from .pi/mcp.json, the extensions, skills, prompt templates, themes and system-prompt files under .pi, missing packages configured through project settings, and project-local and project-package extensions.

These are the project resources covered by the trust decision. Trust authorises their loading; it does not grant a separate set of operating-system permissions or restrict the permissions Pi already has.

What trust does not do

security.md is unusually direct on this point: project trust does not limit what tool calls can access or affect. After Pi starts, enabled tools still use the operating-system permissions of the Pi process. Instructions and other content in the folder can also influence the model.

A separate class of input can load regardless of the trust decision:

Context files such as AGENTS.override.md, AGENTS.md, and CLAUDE.md load regardless of project trust unless you disable context loading. Treat instructions in a folder as untrusted input even when you decline project trust.

This is the distinction that catches people. Trust gates protected project resources, including executable extensions and non-executable settings, prompts and skills. Context files carry instructions, and they can arrive regardless of that decision. configuration.md confirms it from the other direction: context-file discovery does not require project trust.

Wrong: “declining trust means the folder cannot influence this run.” Correct: declining trust stops the listed project resources from loading. AGENTS.md and CLAUDE.md still reach the model, and so does the text of the files themselves once a tool reads one.

    flowchart TD
  W["Working directory"] --> D["Discover project resources"]
  D --> K{"Which kind?"}
  K -->|"context file"| CF["Loads under the context rules"]
  CF --> CFN["No trust decision asked"]
  K -->|"protected resource"| T{"Trust decision"}
  T -->|"granted"| L["Loaded"]
  T -->|"declined"| X["Skipped"]
  

The one exception

Project trust is not a complete startup boundary. Pi reads the project sessionDir setting while selecting or creating a session, before it resolves trust. Declining trust prevents the remaining project settings and protected resources from loading, but it cannot undo that initial session-directory lookup.

Practically: a project can point your sessions at an arbitrary directory before you answer anything. That is worth knowing when you cd into a directory you do not control.

How the decision is made

A command-line --approve or --no-approve override applies first. Otherwise:

  1. User-level and command-line extensions can handle the project_trust event. The first handler returning { trusted: "yes" } or { trusted: "no" } owns the decision; a bare string is not a valid result.
  2. If no extension decides, Pi looks for a saved decision for the current directory or one of its parents. The closest decision applies.
  3. If no saved decision applies, Pi follows defaultProjectTrust, whose default is "ask".

Saved decisions use canonical directory paths and live in:

~/.pi/agent/trust.json

/trust saves a decision for future processes. A parent-directory decision propagating to children is convenient and is worth thinking about before you rely on it.

The hole automation cannot see through

Print, JSON, and RPC modes cannot show the built-in trust prompt. With no override, extension, or saved decision:

  • defaultProjectTrust: "always" loads protected project resources.
  • "ask" or "never" skips them.

So an automated run cannot answer a question it cannot ask. Use --approve or --no-approve when you want an explicit one-time decision in a script:

pi --no-approve --print "Summarize README.md"
pi --approve --print "Run the project's own test suite"

What a safe project actually contains

A .pi directory is not inherently malicious, but its contents have different risks. An extension can execute code inside Pi; a skill can direct the model to execute a bundled script; and settings or prompts can change the way the agent behaves. Review what would load before granting trust.

A review-friendly project looks like this:

.pi/
├── settings.json          # tool selection, compaction budget; no extensions
├── APPEND_SYSTEM.md       # conventions worth stating every run
├── prompts/               # reviewed text
└── skills/                # reviewed text and reviewed scripts

The order of review matters. Extensions execute inside the Pi process with your permissions; a skill can instruct the model to run scripts; and prompts and settings can shape later actions. Reviewing the extension code first is sensible, but none of these resources should be treated as harmless merely because it is text.

Trust is not your sandbox

security.md gives three ways of running Pi and what each protects:

How Pi runs What remains protected
Directly, as your operating-system user Anything that user cannot access
Entirely inside a container, VM, or sandbox Host files and processes you do not expose
Outside the isolated environment, tools inside Host resources from actions taken through those tools

Whichever you choose, provide only the files and services the task requires. Restrict network access when commands do not need it. Trust is a review gate; isolation is the boundary.

Disable context files when you need a hard edge

If you want to run Pi against an unfamiliar repository without automatically discovered folder instructions, -nc/--no-context-files disables context-file discovery, including AGENTS.md and CLAUDE.md. It does not prevent the model from reading instructions contained in ordinary files through an enabled tool, so it is not a general prompt-injection barrier.

All of this is a loading gate. Nothing decided here constrains what a tool does once Pi is running, so the next question is not what the folder was allowed to contribute but what environment the first command actually lands in.

What was run

examples/ch08-trust/ exercises the parts of the trust decision that live in Pi’s exported code, on real directories:

  • a bare .pi directory needs no decision, and each of the documented triggers does: settings.json, mcp.json, extensions, skills, prompts, themes, SYSTEM.md, APPEND_SYSTEM.md, and a project .agents/skills in an ancestor directory;
  • context files are not a trigger: a repository with only AGENTS.md and CLAUDE.md asks nothing;
  • saved decisions resolve by the closest path: a parent decision covers its children, a decision on the child wins and leaves the parent’s alone, they persist in trust.json, and setting null clears one;
  • through the real resource loader, an untrusted project’s extension and prompt template did not load while its AGENTS.md still did, and the same project loaded all three when trusted.

The decision order itself needs the shipped binary, because it lives in the command-line entry point. examples/ch08-trust/decision.test.ts runs the real pi as a child process in print mode, with a project extension that writes a flag file when it loads, and a scripted model. In print mode there is no UI to ask with, so what is observed is what each source decides:

  • with no flag, no extension, no saved decision and no setting, the project extension does not load: the default is ask, and print mode cannot ask;
  • --approve loads it and --no-approve does not;
  • defaultProjectTrust: "always" in the agent directory loads it, and "ask" and "never" do not;
  • a saved decision for the directory applies without any flag, and beats defaultProjectTrust;
  • a command-line flag beats a saved decision, in both directions;
  • a project_trust handler in a command-line extension decides before saved decisions and the default: yes beat a saved no, no beat a saved yes, and yes beat defaultProjectTrust: "never";
  • an extension that answers undecided hands the decision on, so the saved decision then applied;
  • a flag outranks an extension.

That is the order the chapter gives, observed: flag, then extension, then saved decision, then defaultProjectTrust.

The exact project_trust handler result is { trusted: "yes" | "no" | "undecided", remember?: boolean }, as shown by the shipped project-trust.ts example and its declaration. A bare string is silently ignored, leaving the saved decision or default to decide. The first version of this book’s test made that mistake and exposed the failure. The remember field is declared, but its behaviour was not tested here.