# Permission Frontmatter for Subagent Extensions A convention guide for pi-subagent extension authors who want to offer users richer per-agent permission control. ## Motivation Pi subagent extensions already let users restrict **which tools** an agent can see via frontmatter keys like `tools:`, `disallowed_tools:`, or `deny-tools:`. These binary allow/deny mechanisms are simple and effective for tool visibility. The `pi-permission-system` extension adds a second layer: **policy evaluation** with three states — `allow`, `ask`, and `deny` — across multiple permission surfaces (tools, bash commands, MCP operations, skills, external directories, and special operations). By documenting the `permission:` frontmatter key in your extension, you give users a single agent file that expresses both visibility restrictions (your extension) and runtime policy (the permission system) without any code coupling between the two extensions. ## The Two-Layer Model ```text ┌──────────────────────────────────────────────────────┐ │ Layer 1 – Visibility (your extension) │ │ Controls which tools are registered / active │ │ before the agent session starts. │ ├──────────────────────────────────────────────────────┤ │ Layer 2 – Policy (pi-permission-system) │ │ Controls allow / ask / deny decisions on every │ │ tool call, bash command, MCP operation, etc. │ └──────────────────────────────────────────────────────┘ ``` The two layers compose additively: 1. A tool hidden by your extension is never seen by the permission system — policy for it is irrelevant. 2. A tool denied by the permission system is removed from the active set before the agent starts — your extension's allowlist cannot restore it. 3. Both denylist mechanisms are additive. A tool blocked by either layer stays blocked. ## The `permission:` Frontmatter Format The `permission:` key uses a flat policy map. Each top-level key is either a tool name (for per-tool policy) or a named surface (`bash`, `mcp`, `skill`, `external_directory`, `special`). The special key `"*"` is the universal fallback. ### Minimal example ```yaml --- permission: "*": ask read: allow write: deny --- ``` This means: allow all read operations without prompting, deny all write operations, and ask the user for everything else. ### Full example with bash patterns ```yaml --- permission: "*": ask read: allow bash: "*": ask "git status": allow "git diff *": allow "npm test": allow mcp: "*": deny skill: "*": ask external_directory: "*": deny "~/projects/*": allow --- ``` ### Composing with your extension's keys Users can freely combine `permission:` with your extension's tool restriction key: ```yaml --- # nicobailon/pi-subagents: restrict visible tools tools: bash,read_file,write_file # pi-permission-system: policy within the visible set permission: "*": ask read_file: allow bash: "*": ask "git *": allow --- ``` Both keys are read independently by their respective extensions. There is no key collision — `permission:` is exclusively consumed by `pi-permission-system`. ## Permission Surfaces | Surface | Key | Value format | Description | | -------------------- | -------------------- | ---------------------------- | ------------------------------------------ | | Tools | `` | `"allow" \| "ask" \| "deny"` | Per-tool invocation policy | | Bash | `bash` | `{ pattern: decision }` | Pattern-matched bash commands (glob-style) | | MCP | `mcp` | `{ pattern: decision }` | MCP tool-level policy | | Skills | `skill` | `{ pattern: decision }` | Skill invocation policy | | External directories | `external_directory` | `{ pattern: decision }` | Path-based access outside the project | | Special | `special` | `{ pattern: decision }` | Special operations (e.g. `subagent_spawn`) | | Universal fallback | `"*"` | `"allow" \| "ask" \| "deny"` | Applies when no specific rule matches | Pattern maps use last-match-wins ordering: put broad catch-alls first and specific overrides after. ## What Adoption Looks Like Adopting this convention does **not** require your extension to: - Import or depend on `pi-permission-system` - Evaluate the `permission:` key at runtime - Change your existing tool restriction mechanism Adoption means: 1. **Document** the `permission:` key as an optional frontmatter field in your extension's README or agent authoring guide. 2. **Explain** that it is consumed by `pi-permission-system` when both extensions are installed. 3. **Show** a combined example with your extension's key alongside `permission:`. The permission system handles all evaluation, prompt dialogs, and policy enforcement independently. ## Runtime Integration (Optional) If your extension runs subagents in-process (e.g. via `createAgentSession()`), you can optionally query the permission system's policy at runtime via the `Symbol.for()`-backed service accessor — no required peer dependency, just a dynamic `import()`. ### Querying policy ```typescript try { const { getPermissionsService } = await import( "@gotgenes/pi-permission-system" ); const permissions = getPermissionsService(); if (permissions) { const result = permissions.checkPermission("bash", "git push", "Worker"); console.log(result.state); // "allow" | "deny" | "ask" } } catch { // Not installed — graceful degradation } ``` If `pi-permission-system` is not installed, `import()` throws; if it has not published a service yet (or has been unloaded), `getPermissionsService()` returns `undefined`. Guard both cases as shown above. Prompt forwarding for headless child agents is an internal subagent-to-parent mechanism, not a public cross-extension operation — there is no service-accessor equivalent to call directly. For full API documentation, see [Cross-extension API](../cross-extension-api.md). ## Benefits for Your Users 1. **Richer semantics** — `ask` is more useful than binary allow/deny; users can permit a tool but require approval for each invocation. 2. **Unified config** — one `permission:` block per agent instead of separate restriction keys in multiple extensions. 3. **Surface coverage** — policy covers bash patterns, MCP tools, skills, external directories, and special operations, not just tool names. 4. **Forwarding** — permission prompts from headless child agents surface in the parent session's UI. 5. **Programmatic access** — the `Symbol.for()` service accessor lets your extension query policy at runtime with only a dynamic `import()`, no required peer dependency. ## Further Reading - [Subagent Integration](../subagent-integration.md) — full coexistence documentation and interaction rules - [Cross-extension API](../cross-extension-api.md) — service accessor, event bus reference (decision and UI-prompt broadcasts) - [Configuration](../configuration.md) — full policy reference including merge precedence - [Schema](../../schemas/permissions.schema.json) — canonical JSON Schema for the flat permission format