pi-namespace
Namespace tools and skills in pi — group tools by extension with configurable prefix rewriting
Package details
Install pi-namespace from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:pi-namespace- Package
pi-namespace- Version
0.1.1- Published
- Jun 15, 2026
- Downloads
- not available
- Author
- monotykamary
- License
- MIT
- Types
- extension
- Size
- 33.8 KB
- Dependencies
- 0 dependencies · 0 peers
Pi manifest JSON
{
"extensions": [
"./namespace.ts"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
🏷️ pi-namespace
Namespace tools and skills in pi
Group tools by extension with configurable prefix rewriting — flat tool names become deploy:push, fs:read, msg:send.
The Problem
Pi registers all tools in a flat namespace. When multiple extensions register tools, colliding names silently shadow each other — the first registration wins. There's no grouping to tell the LLM that push, status, and rollback all belong to the deploy extension.
Available tools
- read ← built-in? extension? override?
- push ← what does this do? where from?
- status ← deploy status? git status? system status?
- search ← brave? vector? code?
- deploy ← the tool? or a namespace collision?
Skills are similarly flat — every skill appears in the system prompt without provenance or grouping.
The Solution
pi-namespace patches ExtensionRunner.prototype.getAllRegisteredTools — the single chokepoint where pi's AgentSession pulls tool definitions before they're frozen into the LLM schema, system prompt, and TUI.
| Before | After |
|---|---|
push |
deploy:push |
status |
deploy:status |
search |
msg:search |
read |
fs:read (with builtinNamespace) |
Namespaced names flow through to everything: LLM tool schemas, system prompt snippets, setActiveTools(), --tools, --exclude-tools, and the TUI.
Available tools
- fs:read ← built-in (namespaced)
- fs:bash ← built-in (namespaced)
- fs:edit ← built-in (namespaced)
- fs:write ← built-in (namespaced)
- deploy:push ← from pi-deploy
- deploy:status ← from pi-deploy
- deploy:rollback ← from pi-deploy
- msg:search ← from pi-messenger
- msg:send ← from pi-messenger
Install
With pi install (recommended):
pi install https://github.com/monotykamary/pi-namespace
Manual — copy to global extensions:
cp namespace.ts ~/.pi/agent/extensions/
Project-local:
mkdir -p .pi/extensions
cp namespace.ts .pi/extensions/
Quick test:
pi -e ./namespace.ts
Reload with /reload after any install method.
Configuration
Create ~/.pi/agent/namespace.json (global) or .pi/namespace.json (project):
{
"namespaces": {
"deploy-extension": "deploy",
"pi-messenger": "msg",
"pi-code-previews": "preview"
},
"builtinNamespace": "fs",
"autoNamespace": false
}
| Option | Type | Default | Description |
|---|---|---|---|
namespaces |
Record<string, string> |
{} |
Map of extension sourceInfo.path (or substring) → namespace prefix |
builtinNamespace |
string |
— | Optional prefix for built-in tools (read, bash, edit, write, grep, find, ls) |
autoNamespace |
boolean |
false |
Auto-derive namespace from extension directory/package name |
Namespace matching
The key in namespaces is matched against each tool's sourceInfo.path:
- Exact match — key equals the full path
- Substring match — key is contained within the path
"deploy-extension" matches:
✓ /home/user/.pi/agent/extensions/deploy-extension/index.ts
✓ /home/user/.npm-global/lib/node_modules/@foo/deploy-extension/dist/index.js
✗ /home/user/.pi/agent/extensions/my-deploy/index.ts
Auto-namespace
When autoNamespace is true, tools from extensions without an explicit mapping get a namespace derived from:
| Source format | Example | Derived namespace |
|---|---|---|
File path with pi- segment |
/home/user/.pi/agent/extensions/pi-messenger/index.ts |
messenger |
npm: package |
npm:@foo/pi-deploy |
deploy |
git: package |
git:github.com/user/pi-tools |
tools |
No pi- prefix, falls back |
/home/user/.pi/agent/extensions/myext/index.ts |
myext |
Commands
| Command | Description |
|---|---|
/namespace |
Show namespaced tool count and config summary |
/namespace list |
Same as above |
/namespace config |
Show the full namespace config JSON |
/namespace map |
Show per-tool namespace mapping with source paths |
Example output
/namespace map
read → fs:read (<builtin:read>)
bash → fs:bash (<builtin:bash>)
edit → fs:edit (<builtin:edit>)
write → fs:write (<builtin:write>)
push → deploy:push (/home/user/.pi/agent/extensions/deploy-extension/index.ts)
status → deploy:status (/home/user/.pi/agent/extensions/deploy-extension/index.ts)
search (no namespace, local)
How It Works
┌─────────────────────────────────────────────────────────────┐
│ session_start │
│ │
│ Patch ExtensionRunner.prototype.getAllRegisteredTools │
│ (once per process — all future calls go through the patch) │
│ │
│ Build reverse namespace map: namespaced → original │
│ (e.g. "deploy:push" → "push") │
└────────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ AgentSession._refreshToolRegistry() │
│ │
│ Calls getAllRegisteredTools() ──→ PATCH INTERCEPTS ──→ │
│ │
│ 1. resolveNamespace(): find matching prefix from config │
│ 2. applyNamespace(): rewrite name "push" → "deploy:push" │
│ 3. Object.create(): delegate to original definition │
│ → execute() inherited untouched │
│ → only name, promptSnippet, promptGuidelines overridden │
│ │
│ Namespaced names flow into: │
│ _toolDefinitions → _toolRegistry → system prompt → TUI │
│ setActiveTools(), --tools, --exclude-tools │
└────────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ before_agent_start │
│ │
│ Rewrite <skill name="..."> attributes in system prompt │
│ e.g. <skill name="brave-search"> → <skill name="web:brave- │
│ search"> │
└─────────────────────────────────────────────────────────────┘
│
▼
LLM calls "deploy:push"
→ matches namespaced tool in registry
→ original execute() runs via Object.create delegation
Built-in tool override safety
If an extension overrides a built-in tool by name (like pi-code-previews overrides read), and that extension also has a namespace, pi-namespace skips the rewrite — namespacing it would break the override mechanism:
pi-code-previews registers tool { name: "read" } (override)
→ namespace config maps pi-code-previews → "preview"
→ BUT "read" is a built-in override → SKIP
→ tool stays as "read", not "preview:read"
With explicit builtinNamespace: "fs":
→ built-in "read" → "fs:read"
→ override "read" from pi-code-previews → still "read" (override wins)
Only use builtinNamespace when you want to namespace all built-in tools explicitly. It intentionally does not affect extension overrides of those built-ins.
Object.create delegation
The patch does not copy tool definitions. It creates a delegating object:
const namespacedDef = Object.create(tool.definition);
namespacedDef.name = "deploy:push"; // override name
namespacedDef.promptSnippet = "Use deploy:push to..."; // override snippet
namespacedDef.promptGuidelines = ["Use deploy:push"]; // override guidelines
// execute(), parameters(), renderCall(), renderResult() — all inherited
This means the original execute function runs with zero wrapping overhead. No proxy, no shim — just prototype chain delegation.
Known Limitations
| Limitation | Details |
|---|---|
| Built-in type guards break | isToolCallEventType("bash", event) won't match fs:bash. Use stripNamespace() to resolve back to bash. |
| Tool override by name | Registering shell:bash won't override built-in bash since the names differ. |
| Fragile to pi internals | A pi update could change ExtensionRunner API. The patch targets a single stable method. |
| Skill namespacing is prompt-only | Skills are XML text in the system prompt — we rewrite <skill name> attributes but can't intercept /skill:ns:name invocations. |
Development
npm install # install dev dependencies
npm test # run tests (25 tests)
npm run typecheck # TypeScript validation
npm run lint:dead # dead code detection (knip)
npm run test:coverage # tests with coverage
Related Projects
| Project | Description |
|---|---|
| pi-tool-repair | Fix common LLM tool-call mistakes before tools execute |
| pi-lazy-extensions | Lazy-load extensions on demand via ToolSearch-style proxy |
| pi-startup-tracer | Trace per-extension load and handler timing |
| pi-toggle-skills | Toggle skill visibility in the system prompt |
| pi-trust-defer | Skip the startup trust prompt |
License
MIT