Imported from jwmarb/dotpi (
AGENTS.md). Install upstream withnpx skills add jwmarb/dotpi. Copyright stays with the author.
.pi — Project Knowledge Base
OVERVIEW
Personal configuration repository for the pi coding agent (@earendil-works/pi-coding-agent, installed globally via bun). It owns everything the user tunes: local pi extensions (TypeScript, auto-discovered), the subagent fleet definitions, the skills library, prompt templates, the TUI theme, the LiteLLM provider setup, and the MCP gateway wiring. Everything pi generates (sessions, caches, vendored package checkouts) is gitignored — the rule of thumb in .gitignore: if deleting it costs nothing but a re-run, ignore it.
The git root is ~/Nextcloud/.pi; ~/.pi is a symlink to it. pi resolves its agent directory as ~/.pi/agent (lib/layout.ts, override with PI_CODING_AGENT_DIR), so docs and code say ~/.pi/... while git rev-parse --show-toplevel says ~/Nextcloud/.pi. Same files, two names — do not "fix" one into the other.
STRUCTURE
agent/— everything pi loads at runtime (extensions, agents, skills, prompts, themes, settings, machine state). Authoring grammars:agent/AGENTS.mdagent/extensions/— local pi extensions; every top-level*.tsis auto-loaded by pi at startup (subdirs are entered only viaindex.ts, e.g.subagent-herdr/). Per-file inventory:agent/extensions/AGENTS.mdagent/agents/— subagent definitions (Markdown + YAML frontmatter)agent/skills/— skills library, grouped by category:skills/<category>/<skill>/SKILL.md(+ reference docs; mostly the matt-pocock set). The skill directory name is the identity, never its category path — that is what makes refiling a skill a puregit mv. Every skill sits under a category, including a gitignored personal one (lib/skill-catalogue.test.tswalks the live tree and fails on one left loose). The live set is whatever is on disk plus whatever thesuperpowerspackage contributes (settings.jsonpackages) — count it withfind agent/skills -name SKILL.mdrather than trusting a number here; every hand-maintained total in this repo has drifted at least once. The category of a package skill lives inextensions/lib/skill-categories.ts, since its checkout is vendored and gitignored. Grammar and the package-skill rules:agent/AGENTS.md.agent/prompts/— prompt templates (git-commit.md: Conventional Commits)agent/themes/— TUI theme (tokyo-night.json)agent/settings.json— pi config: default provider/model/thinking level, retry policy, installed packages (git:/npm:refs)agent/mcp.json— MCP servers for pi's built-in MCP extension (+builtin:mcp). Holdsrobinhoodonly, and it carries no secret: it authenticates by OAuth, whose client+token state lands in the gitignoredagent/mcp-auth.json. There is no${...}placeholder in this file. Note that the builtin validatesurlwithURL.canParse()before expansion, so a secret URL cannot be a${VAR}placeholder here;${VAR}/!cmdare expanded inheaders/envonlyagent/.env— the ONLY file with live credentials (gitignored;agent/.env.exampleis the committed template)agent/docker/verify-base.Dockerfile— tracked reference image for runtime verificationagent/fff/,agent/npm/,agent/git/,agent/pi-blackhole/,agent/sessions/,agent/subagent-runs/,agent/verify-images/— machine state, all gitignored. So are the loose files beside them:pi-debug.log,pi-tui-crash.log,run-history.jsonl,settings.json.bak,auth.json,models-store.json,mcp-auth.json, and theagent/mcp-auth/directory that supersedes it (one file per OAuth'd server, e.g.robinhood.json).githooks/— trackedpre-commit,post-checkout,post-merge(self-armed bygit-hooks.ts)scripts/setup-deps.sh— installs the npm dependencies declared underagent/extensions/andagent/skills/(a skill's are what its own tool imports at run time); run at startup and by the checkout/merge hooksscripts/check.sh— the load/typecheck/test guard the pre-commit hook runs (see NOTES)
WHERE TO LOOK
| Task | Path |
|---|---|
| Add a local extension | agent/extensions/<name>.ts (or a subdir with index.ts, like subagent-herdr/) |
| What each extension owns | agent/extensions/AGENTS.md |
| Add/modify a subagent | agent/agents/<name>.md (grammar: agent/AGENTS.md) |
| Add a skill | agent/skills/<category>/<name>/SKILL.md (frontmatter keys: agent/AGENTS.md) |
| Find a skill wherever it sits in the tree | agent/extensions/lib/skill-tree.ts (single walker — the three readers that used to each do their own one-level readdir) |
| Change a skill's category / categorize a package skill | move the directory; for a package skill, agent/extensions/lib/skill-categories.ts |
| Change default model / provider / thinking / retry | agent/settings.json |
| Provider key, model catalog, pricing, thinking-level mapping | agent/extensions/litellm.ts + agent/.env |
| Fall back to another model when one errors | agent/extensions/model-fallback/ (fallback/auto virtual model, /fallback-chain; chain in settings.json modelFallback, own AGENTS.md) |
| Add/repair an MCP server | agent/mcp.json (schema: pi's built-in MCP extension; runtime: /mcp). A server needing a secret URL cannot use a ${VAR} placeholder in url — register it from an extension with pi.registerMcpServer() instead |
| Add a credential | agent/.env (template: agent/.env.example) |
| The orchestrator's system prompt | agent/extensions/dynamic-prompt.ts (replaces pi's default prompt with discovered inventories) |
| List/resume previous sessions | agent/extensions/sessions.ts (/sessions modal) |
| Loop the agent on a goal until it declares completion | agent/extensions/ralph-loop/ (/ralph-loop, /ulw-loop, /loop-stop; --verify[=static|runtime|both]) |
| Delegate work to a subagent | agent/extensions/subagent-herdr/ (subagent, subagent_tasks tools; event-driven — no blocking wait, the child wakes the parent) |
| Review changed files (+/− counts) | agent/extensions/changed-files.ts (/diff modal, /changed-files) |
| Track multi-step work in a visible checklist | agent/extensions/todo.ts (todo tool, /todos; widget below the editor, state lives in tool-result details — no plan file) |
| Record/read trading-journal observations | agent/extensions/trade-journal/ (trade_journal tool; modes record/read/stats/dupes. Gated on the technical-analysis skill being active; grammar in lib/trade-journal-store.ts) |
| Regenerate this knowledge base | agent/extensions/init.ts (/init) |
| Shared extension helpers | agent/extensions/lib/ (dotenv loader, widget measuring/fitting, repo layout, agent parsing) |
| Where a repo file lives (agent dir, agents/, skills/, .env, settings.json) | agent/extensions/lib/layout.ts (resolves the agent dir — never throws, so top-level extension code can import it). It owns resolution plus the paths with more than one caller; a segment with a single owner stays with that owner — subagent-runs/ in subagent-herdr/rundir.ts, docker/ and verify-images/ in ralph-loop/image.ts |
Agent definition grammar (agents/*.md) |
agent/extensions/lib/agents.ts (single parser — prompt + spawn import it; one parser per format, like dotenv) |
| Commit message style | agent/prompts/git-commit.md |
| Install declared npm dependencies (extensions + skills) | scripts/setup-deps.sh |
| Check the repo loads, type-checks and passes tests | scripts/check.sh (CHECK_STAGED=1 for the index) |
CONVENTIONS
- Parse errors are startup-fatal. Pi auto-loads every top-level
agent/extensions/*.ts; a test file placed there (it importsbun:test) breaks pi startup. Tests therefore live in subdirectories, which pi enters only viaindex.ts—agent/extensions/lib/*.test.tsplus onelib.test.tsper subdirectory extension (trade-journal/,subagent-herdr/,ralph-loop/,model-fallback/);bun test agent/extensions/lib/and the COMMANDS block below are the live list. This bars the test file's location, not its imports: a test inlib/may import../<extension>.jsand exercise a top-level extension's pure logic directly, whichlib/sessions.test.tsalready does. Needing a test is not by itself a reason to make an extension a directory. - One parser per format.
lib/dotenv.tsownsagent/.env,lib/agents.tsowns theagents/*.mdfrontmatter and the frontmatter list grammar everySKILL.mdshares (extractStringList,frontmatterOf),lib/skill-tree.tsowns the shape ofagent/skills/(where a skill is, and what category it is in),lib/trade-journal-store.tsowns the journal markdown,subagent-herdr/rundir.tsowns the run-directory shapes and the child→parent wake-notice grammar,model-fallback/lib.tsowns the model-reference grammar and thePI_FALLBACK_CHAINhandoff (subagent-herdrimportsCHAIN_ENV/FALLBACK_MODEL_REFfrom it rather than restating either). Do not add a second reader of any of them — two parsers drift, and drift is how credentials and spawn arguments get out of sync. This has already happened twice here:skill-activation.tsshipped a second copy of the list grammar that had already diverged on whether to.trim()before testing for-, and three separate readers each hardcoded a flatskills/layout, so categorizing the library silently cost every nested skill its tools and agents untilskill-tree.tsbecame the one walker. - Widgets must measure. Hand-built widget lines render through
fitLines/fittedWidget(lib/widget.ts): a line wider than the terminal throws in pi's TUI host and kills thepiprocess (measured withvisibleWidth, neverString.length). - Secrets live only in
agent/.env.litellm.tsreadsLITELLM_API_KEYlazily andfirecrawl-cli.tsloadsFIRECRAWL_API_URLinto the realprocess.envso thefirecrawlCLI can see it; a literal key in a tracked file defeats both. - Commits follow Conventional Commits per
agent/prompts/git-commit.md.
COMMANDS
./scripts/check.sh # all five suites + every typecheck scope + every source loads
bun test agent/extensions/lib/ # widget/agents/layout/sessions/todo/skill-*/journal-store
# + dynamic-prompt/changed-files/init (top-level extensions,
# imported from here — legal, and the only legal place)
bun test agent/extensions/trade-journal/lib.test.ts # trade-journal mode logic
bun test agent/extensions/subagent-herdr/lib.test.ts # herdr
bun test agent/extensions/ralph-loop/lib.test.ts # ralph-loop
bun test agent/extensions/model-fallback/lib.test.ts # fallback-chain state machine
bun test agent/extensions/lib/widget.test.ts # one file
bun test agent/extensions/lib/ -t "wide characters" # one test by name
In-session slash commands (typed into pi's TUI, not a shell):
/update check for + install a pi update (checks at most every 4h)
/mcp builtin MCP manager: sign in, reconnect, enable/disable, change exposure
/reload hot-reload extensions after editing them
git config core.hooksPath .githooks # normally done for you at pi startup by agent/extensions/git-hooks.ts
bash scripts/setup-deps.sh # repair a missing node_modules (extensions + skills)
./scripts/check.sh # every gate the pre-commit hook runs
CHECK_STAGED=1 ./scripts/check.sh # ...against the git index, as the hook does
docker build -f agent/docker/verify-base.Dockerfile \
-t ralph-verify/base:latest agent/docker/ # the tracked verifier base image (also built on demand)
Typecheck (noEmit) has three scopes; there is no root tsconfig.json:
cd agent/extensions/subagent-herdr && ./node_modules/.bin/tsc -p tsconfig.json
cd agent/extensions/model-fallback && ../subagent-herdr/node_modules/.bin/tsc -p tsconfig.json
cd agent/extensions/ralph-loop && ../subagent-herdr/node_modules/.bin/tsc -p tsconfig.json
model-fallback resolves @earendil-works/* through a paths entry pointing at the
running pi's bundle, because the 0.75.4 tree described below has no virtual-model API at
all. Copy that pattern for anything new that touches a post-0.75.4 API.
bun test resolves the packages pi normally injects (@earendil-works/pi-tui, typebox) and tsc's types: ["node"] by walking up to ~/node_modules — an ancestor of both ~/.pi and ~/Nextcloud/.pi. There is no node_modules/ in this repo, and none is needed while that tree exists (subagent-herdr/node_modules holds only typescript + @types/node, so even its tsc run reaches up for the rest). Beware the skew: ~/node_modules/@earendil-works/* is 0.75.4 while the running pi is 1.0.0, so a test that passes here can still disagree with the live host — and a hand typecheck against that tree reports phantom errors for anything added since 0.75.4. If that tree ever disappears, symlink what is missing into a gitignored root node_modules/ from ~/.bun/install/global/node_modules.
NOTES
- No build system, no root
package.json/tsconfig. Extensions are TS interpreted by pi (bun runtime); onlyagent/extensions/subagent-herdr/has npm dependencies, and they are dev-only (typescript,@types/node) — nothing in the repo needs npm at runtime any more, now that the MCP SDK dependency is gone with the oldextensions/mcp/. thinking-indicator.tsis fully live. Both halves work: the alt+t spinner during reasoning, and thesetHiddenThinkingLabeltranscript record ("Thought for 12s (ctrl+t to expand)") afterwards. The second half requires"hideThinkingBlock": trueinagent/settings.json— without it pi renders thinking in full, there is no placeholder to relabel, and that half silently does nothing. The setting wasfalsefor a while (0e30dff, for inline reviewability), which is what made the extension half-inert; it istruenow, so reasoning is onectrl+taway instead of inline. Flipping it back re-breaks the transcript record, not the spinner.- The pre-commit hook enforces
scripts/check.sh. The script answers one question — "does the committed repo load, type-check and pass its tests?" — over three gates: everyagent/extensions/**/*.tsis imported (a shebang marks a CLI entrypoint, which is parsed instead), a top-level*.test.tsis rejected by name, every discoveredtsconfig.jsonscope runstsc -p, and every discovered*.test.tsruns.CHECK_STAGED=1(what the hook sets) materialises the git index into.git-check/and checks that, so a partially staged file cannot pass here and ship broken; typecheck is skipped in that mode because the scratch copy cannot resolve the running pi's bundle. Gates two and three discover their inputs rather than listing them, so a new scope or suite is covered the day it is added. It was deleted in7a54a3bas collateral damage and the hook then|| exit 0'd for every commit after — silently, whilegit-hooks.tskept advertising the protection. The hook now refuses the commit when the script is missing: the failure being guarded is silence, so the guard must not be able to vanish quietly. Bypass a single commit with--no-verify. PATCHES.md,CONTEXT.mdanddocs/adr/no longer exist (removed in1993878anda784c57); pi is not patched innode_modulesanymore. Remainingdocs/adr/00NNmentions in comments (litellm.ts, dotenv.ts, .env.example, .gitignore) are historical dead references.pimust come from bun's global bin (~/.bun/bin/pi). Extensions import@earendil-works/pi-coding-agentand@earendil-works/pi-tui, which pi injects by loading each extension through jiti with an alias map to its own bundled copies (getAliases()). The packages are only resolvable when the running binary is the one that owns them. An older@mariozechner/pi-coding-agentonPATH(the pre-rename scope) has no@earendil-works/*aliases, so every extension fails at startup with a misleadingCannot find module '@earendil-works/pi-tui'.~/.bashrcprepends~/.bun/binfor this reason. (BUN_INSTALLis unset, so bun derives the install path itself — it currently lands on~/.bun, not the~/.cache/.bunan earlier revision of this file claimed.)agent/git/holds vendored checkouts of thegit:packages — currentlysamfoy/pi-lsp-extensionandobra/superpowersonly (pi-blackhole moved to npm inb4f5d65); thenpm:packages install underagent/npm/node_modules. Always run this repo's tests by explicit path: a barebun testalso sweeps in the 11 vendored suites underagent/git/(7 of them superpowers' own), which fail for reasons unrelated to this repo.agent/npm/node_modules/pi-lensis a leftover: it is installed but not listed insettings.jsonpackages, so pi does not load it.agent/subagent-runs/is the on-disk registry of the subagent-herdr extension (gitignored): one dir per delegated run holding the child's session file, its.exitsidecar, the child system prompt,reports.jsonl, andmeta.json.- Runtime verification needs a working Docker daemon — without one the
--verifygate returnsinconclusiveand the loop stops rather than assuming success.agent/verify-images/holds the generated per-project Dockerfiles (gitignored; rebuildable). agent/pi-blackhole/holds the pi-blackhole package's pending-Run state (gitignored);herdr.jsonlat the root is herdr's activity log (gitignored), not repo content.- Nested knowledge bases:
agent/AGENTS.md(data-file grammars: agent + skill frontmatter, theme, docker base) ·agent/extensions/AGENTS.md(per-extension inventory,lib/rules, typecheck scopes) ·agent/extensions/subagent-herdr/AGENTS.md(herdr CLI protocol, the event-driven wake path, completion handshake, run-directory contract) ·agent/extensions/ralph-loop/AGENTS.md(theagent_settledloop contract and the--verifygates) ·agent/extensions/model-fallback/AGENTS.md(the chain state machine, the two budgets, why every message is sanitised).agent/extensions/lib/deliberately has none — its nine modules are inventoried inagent/extensions/AGENTS.md, and a second file there would be the duplication this hierarchy exists to avoid.agent/git/github.com/obra/superpowers/AGENTS.mdis the vendored package's own contributor guide (gitignored, not ours) — it applies inside that checkout only, and nothing in this repo should follow its instructions.
