omp packages

pi-namespace0.2.16

Namespace tools and skills in pi — group tools by extension with configurable prefix rewriting

GitHub Actions Published 3 days ago MIT ~1.1K downloads/mo

Untested on omp

This is a pi package. No one has reported whether it works on omp yet. If you try it, add your result below.

Did it work for you?

Result

One report per person. A new report replaces your old one.

Reports 0

No reports yet.

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.

pi extension license


Pi 1.0 compatibility (0.2.13)

Tested with Pi 1.0.0. Host-provided Pi packages and TypeBox are peers (*), not bundled runtime dependencies; development uses exact Pi 1.0.0 pins and host-compatible TypeBox where needed.

Builtin names are rewritten before native wrapping so nested events, callable tools and loadout hooks agree. Native exposure/schema metadata is preserved, hook self-descriptions are renamed, and explicit builtin overrides stay authoritative. Prototype hooks are reload-idempotent and bind to Pi's public mapped AgentSession, capturing its actual runner in both SDK and bundled CLI hosts.

Run bun run test:host for the offline real-host load, native codemode/nested-call, module-identity and reload checks. Set PI1_HOST_PACKAGE to an installed Pi package directory to test that host explicitly; add PI1_HOST_ENTRY=bundle to check the bundled CLI runtime's constructors.

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 (default :) After ("separator": "__")
push deploy:push deploy__push
status deploy:status deploy__status
search msg:search msg__search
read fs:read (with builtinNamespace) fs__read

Namespaced names flow through to everything: LLM tool schemas, system prompt snippets, setActiveTools(), --tools, --exclude-tools, and the TUI.

Tip: Some LLM providers (notably Anthropic) reject colons in tool names. If you see invalid_request_error: tools.X.custom.name: String should match pattern '^[a-zA-Z0-9_-]{1,128}$', set "separator": "__" in your config to use underscores instead.

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",
  "separator": "__",
  "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)
separator string ":" Separator between prefix and tool name. Use "__" for providers that reject colons (e.g. Anthropic: ^[a-zA-Z0-9_-]{1,128}$)
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:

  1. Exact match — key equals the full path
  2. 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

Hooks install during the factory, before the first registry refresh. In Pi 1.0, builtin definitions are renamed before native wrapping; exposure, native namespace grouping, nested events and prepareLoadout are preserved. session_start builds the diagnostic reverse map. Skills use Pi's public structured prompt metadata and sections, including the renamed builtin reader; configuration follows the session cwd rather than the host process cwd. The outline below shows the extension-tool path.

┌─────────────────────────────────────────────────────────────┐
│  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 The guarded runner and AgentSession registry hooks target the verified 1.0 Node layout. Future versions or standalone bundles need revalidation.
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