7.6 KiB
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
┌──────────────────────────────────────────────────────┐
│ 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:
- A tool hidden by your extension is never seen by the permission system — policy for it is irrelevant.
- A tool denied by the permission system is removed from the active set before the agent starts — your extension's allowlist cannot restore it.
- 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
---
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
---
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:
---
# 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 | <tool_name> |
"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:
- Document the
permission:key as an optional frontmatter field in your extension's README or agent authoring guide. - Explain that it is consumed by
pi-permission-systemwhen both extensions are installed. - 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
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.
Benefits for Your Users
- Richer semantics —
askis more useful than binary allow/deny; users can permit a tool but require approval for each invocation. - Unified config — one
permission:block per agent instead of separate restriction keys in multiple extensions. - Surface coverage — policy covers bash patterns, MCP tools, skills, external directories, and special operations, not just tool names.
- Forwarding — permission prompts from headless child agents surface in the parent session's UI.
- Programmatic access — the
Symbol.for()service accessor lets your extension query policy at runtime with only a dynamicimport(), no required peer dependency.
Further Reading
- Subagent Integration — full coexistence documentation and interaction rules
- Cross-extension API — service accessor, event bus reference (decision and UI-prompt broadcasts)
- Configuration — full policy reference including merge precedence
- Schema — canonical JSON Schema for the flat permission format