Files
my-pi/pi-permission-system/docs/guides/permission-frontmatter-for-subagent-extensions.md
T

170 lines
7.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 | `<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:
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