Why Pi misses nested AGENTS.md files in a monorepo
Pi 1.5.2 can run a command in packages/web while its system prompt still contains only the repository-level AGENTS.md. The default loader looks for instructions from the session’s starting directory upward. Running cd packages/web in a Bash tool call changes the shell’s directory for that command. Pi’s context file documentation
A session started in packages/web can load both the package and repository files. That difference explains how a command can run in the right package while missing its instruction to run browser tests.
Which instructions appear in the system prompt
The probe below uses Pi’s SDK to inspect the system prompt it assembles, then runs the built-in Bash tool directly. It needs Node.js 22.19 or newer, npm, Git, and a Bash-compatible terminal on macOS or Linux. npm downloads Pi 1.5.2 during setup. The probe runs locally without a model request or API key. Pi 1.5.2 release
probe_dir=$(mktemp -d)
cd "$probe_dir"
npm install --prefix . --no-audit --no-fund --ignore-scripts @earendil-works/[email protected]
mkdir -p repo/packages/web config
git -C repo init -q
printf '%s\n' 'Repo rule: keep release notes current.' > repo/AGENTS.md
printf '%s\n' 'Web rule: run the browser tests for checkout changes.' > repo/packages/web/AGENTS.md
Save the script as probe.mjs in the directory created by the setup commands.
import { resolve } from "node:path";
import { createAgentSession, SessionManager } from "@earendil-works/pi-coding-agent";
const agentDir = resolve("config");
const repo = resolve("repo");
async function start(cwd) {
const { session } = await createAgentSession({
cwd,
agentDir,
sessionManager: SessionManager.inMemory(cwd),
});
return session;
}
function report(label, session) {
console.log(label, {
repo: session.systemPrompt.includes("Repo rule:"),
web: session.systemPrompt.includes("Web rule:"),
});
}
const rootSession = await start(repo);
report("Started at repo root", rootSession);
const bash = rootSession.agent.state.tools.find((tool) => tool.name === "bash");
const result = await bash.execute("probe", { command: "cd packages/web && pwd" });
console.log("Shell entered packages/web:", result.content[0].text.trim().endsWith("/packages/web"));
report("After cd inside the shell", rootSession);
rootSession.dispose();
const webSession = await start(resolve(repo, "packages/web"));
report("Started inside packages/web", webSession);
webSession.dispose();
Run node probe.mjs. On Node.js 24.13.0, the probe printed the following.
Started at repo root { repo: true, web: false }
Shell entered packages/web: true
After cd inside the shell { repo: true, web: false }
Started inside packages/web { repo: true, web: true }
Why the shell and prompt differ
createAgentSession passes the starting directory to DefaultResourceLoader. The loader checks the user configuration directory, then walks the session directory and its parents. In the probe, the user configuration directory is empty. From repo, the walk can find repo/AGENTS.md. From repo/packages/web, it also finds repo/packages/web/AGENTS.md and orders the repository rule before the package rule. The session passes both files to the code that renders the project instructions in the system prompt. Session creation · Context file discovery · Prompt assembly
Pi’s Bash tool launches a separate shell process to execute the command. In the probe, pwd prints the package directory because it runs in the same shell as cd. The instruction loader still has the original session directory. Bash process launch
For work confined to one package, launch Pi from that package directory. For work spanning packages, stay at the root and name the instruction files Pi should read before editing. For this checkout change, that means asking it to read packages/web/AGENTS.md before touching the package and checking the browser-test result in the session. The probe verifies what the default loader puts in the prompt. Whether the model follows a loaded instruction requires a separate check.