mirror of
https://bitbucket.org/siakitem/my-pi.git
synced 2026-08-28 08:35:57 +00:00
feat: vendor permission system source
This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
# 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
|
||||
@@ -0,0 +1,113 @@
|
||||
# Upstream Issue Template
|
||||
|
||||
Template text for proposing the `permission:` frontmatter convention to subagent extension repositories.
|
||||
Customize the placeholders (`{{...}}`) for each target repo.
|
||||
|
||||
---
|
||||
|
||||
## Issue Title
|
||||
|
||||
> Proposal: document `permission:` frontmatter for per-agent permission policy
|
||||
|
||||
## Issue Body
|
||||
|
||||
````markdown
|
||||
## Context
|
||||
|
||||
Users of both `{{your-extension}}` and [`pi-permission-system`](https://github.com/gotgenes/pi-permission-system) currently configure tool restrictions in two places:
|
||||
|
||||
1. **Tool visibility** via `{{your-key}}` in agent frontmatter (consumed by your extension)
|
||||
2. **Permission policy** via `permission:` in the same frontmatter (consumed by pi-permission-system)
|
||||
|
||||
These two layers compose correctly today — there is no conflict — but users may not realize they can combine them in the same agent file.
|
||||
|
||||
## Proposal
|
||||
|
||||
Document the `permission:` key as an optional companion to `{{your-key}}` in your agent authoring docs.
|
||||
This is purely a documentation change — no code dependency on pi-permission-system is needed.
|
||||
|
||||
### What `permission:` provides
|
||||
|
||||
- **Three-state policy**: `allow`, `ask` (prompt the user), or `deny` — richer than binary allow/deny
|
||||
- **Multiple surfaces**: tools, bash commands (glob patterns), MCP operations, skills, external directories
|
||||
- **Prompt forwarding**: `ask` decisions in headless child agents surface in the parent session's UI
|
||||
- **Service accessor API**: other extensions can query policy at runtime via a `Symbol.for()`-backed accessor, with only a dynamic `import()` and no required peer dependency
|
||||
|
||||
### Example
|
||||
|
||||
```yaml
|
||||
---
|
||||
# {{your-extension}}: restrict visible tools
|
||||
{{your-key}}: {{example-value}}
|
||||
|
||||
# pi-permission-system (optional): policy within the visible set
|
||||
permission:
|
||||
"*": ask
|
||||
read_file: allow
|
||||
bash:
|
||||
"*": ask
|
||||
"git *": allow
|
||||
---
|
||||
```
|
||||
|
||||
### Two-layer model
|
||||
|
||||
```text
|
||||
Layer 1 – Visibility ({{your-extension}})
|
||||
→ Controls which tools are registered before the session starts
|
||||
|
||||
Layer 2 – Policy (pi-permission-system)
|
||||
→ Controls allow/ask/deny decisions on every tool call, bash command, etc.
|
||||
```
|
||||
|
||||
A tool hidden by Layer 1 is never evaluated by Layer 2.
|
||||
A tool denied by Layer 2 cannot be restored by Layer 1.
|
||||
Both mechanisms are additive — a tool blocked by either stays blocked.
|
||||
|
||||
### What adoption requires from you
|
||||
|
||||
1. Add a section to your README noting that `permission:` is an optional key consumed by pi-permission-system
|
||||
2. Show a combined example with `{{your-key}}` + `permission:`
|
||||
3. Link to the [convention guide](https://github.com/gotgenes/pi-permission-system/blob/main/docs/guides/permission-frontmatter-for-subagent-extensions.md) for full details
|
||||
|
||||
No code changes, no new dependency, no schema enforcement.
|
||||
|
||||
## References
|
||||
|
||||
- [Convention guide](https://github.com/gotgenes/pi-permission-system/blob/main/docs/guides/permission-frontmatter-for-subagent-extensions.md)
|
||||
- [Subagent integration docs](https://github.com/gotgenes/pi-permission-system/blob/main/docs/subagent-integration.md)
|
||||
- [Cross-extension API docs](https://github.com/gotgenes/pi-permission-system/blob/main/docs/cross-extension-api.md)
|
||||
````
|
||||
|
||||
---
|
||||
|
||||
## Per-Repository Customization
|
||||
|
||||
### nicobailon/pi-subagents
|
||||
|
||||
| Placeholder | Value |
|
||||
| -------------------- | --------------------------- |
|
||||
| `{{your-extension}}` | `pi-subagents` |
|
||||
| `{{your-key}}` | `tools` |
|
||||
| `{{example-value}}` | `bash,read_file,write_file` |
|
||||
|
||||
### tintinweb/pi-subagents
|
||||
|
||||
| Placeholder | Value |
|
||||
| -------------------- | ------------------ |
|
||||
| `{{your-extension}}` | `pi-subagents` |
|
||||
| `{{your-key}}` | `disallowed_tools` |
|
||||
| `{{example-value}}` | `write_file,bash` |
|
||||
|
||||
Additional note for tintinweb: since this extension runs subagents in-process via `createAgentSession()`, mention the [service accessor](https://github.com/gotgenes/pi-permission-system/blob/main/docs/cross-extension-api.md#service-accessor) as an optional runtime integration path for querying policy without spawning a subprocess.
|
||||
|
||||
### HazAT/pi-interactive-subagents
|
||||
|
||||
| Placeholder | Value |
|
||||
| -------------------- | -------------------------- |
|
||||
| `{{your-extension}}` | `pi-interactive-subagents` |
|
||||
| `{{your-key}}` | `deny-tools` |
|
||||
| `{{example-value}}` | `write_file,bash` |
|
||||
|
||||
Additional note for HazAT: this extension already uses `PI_DENY_TOOLS` env var for subprocess tool denial.
|
||||
The `permission:` frontmatter provides the same effect via `tool_name: deny` but adds `ask` as an intermediate option and covers surfaces beyond tools.
|
||||
Reference in New Issue
Block a user