| `tags` | array of strings | `[]` | Freeform labels for grouping/filtering |
| `max_turns` | integer | — | Max agentic loop iterations. Maps to `steps` in OpenCode, `maxTurns` in Claude Code. Ignored by Pi. |
| `skills` | array of strings | `[]` | Skill names to load (e.g. `["systematic-debugging", "git-master"]`). Same `SKILL.md` format across all renderers. |
| `context` | array of strings | `[]` | Relative file paths to inject as context (e.g. `["../context/profile.md"]`) |
| `rules` | array of strings | `[]` | Rule references from the `rules/` directory (e.g. `["languages/nix", "concerns/testing"]`) |
### `mode` semantics
| Value | Meaning |
|-------|---------|
| `"primary"` | Agent is offered as a top-level choice to the user |
| `"subagent"` | Agent is invoked programmatically by other agents; not shown in primary picker |
| `"all"` | Both primary and subagent (default if omitted) |
**Renderer note**: Claude Code has no distinction between primary and subagent — all
agents are effectively subagents. Pi only renders a `SYSTEM.md` for primary agents.
---
## Permission Schema
Permissions describe what each tool is allowed to do. The schema uses two-level TOML
tables: one section per tool.
### Structure
```toml
[permissions.TOOL_NAME]
intent = "allow" | "deny" | "ask" # required
rules = ["pattern:action", ...] # optional
```
- **`intent`** — the default action taken when no specific rule matches.
-`"allow"` — permit the operation without prompting
-`"deny"` — block the operation silently
-`"ask"` — prompt the user for confirmation
- **`rules`** — ordered list of override entries. Each entry is a string in the form
`"pattern:action"` where `action` is one of `allow`, `deny`, or `ask`.
Rules are evaluated first-match; the `intent` applies only when no rule matches.
### Supported tool names
| Tool | Description |
|------|-------------|
| `bash` | Shell command execution |
| `edit` | File creation and modification |
| `webfetch` | HTTP fetch to external URLs |
| `websearch` | Web search queries |
| `question` | Interactive user question prompts |
| `external_directory` | Access to filesystem paths outside the project |
### Simple permission (no per-pattern overrides)
```toml
[permissions.webfetch]
intent = "allow"
[permissions.websearch]
intent = "deny"
[permissions.question]
intent = "allow"
```
### Structured permission with rules
```toml
[permissions.bash]
intent = "ask"
rules = [
"git status*:allow",
"git log*:allow",
"git diff*:allow",
"git branch*:allow",
"rm -rf *:deny",
"git push --force*:deny",
]
[permissions.edit]
intent = "allow"
rules = [
"/run/agenix/**:deny",
]
[permissions.external_directory]
intent = "ask"
rules = [
"~/p/**:allow",
"~/.config/opencode/**:allow",
"/tmp/**:allow",
"/run/agenix/**:allow",
]
```
### Pattern syntax
Patterns follow glob conventions:
-`*` — matches any characters within a single path segment or command token
-`**` — matches any characters including path separators
- Patterns are matched left-to-right; first match wins
---
## Excluded Fields
The following fields are intentionally absent from the canonical schema.
| Field | Reason |
|-------|--------|
| `model` | Per-machine concern. Model selection is configured in `home-manager` (via `programs.opencode.settings`) and varies by host. Including it in `agent.toml` would couple the agent definition to deployment infrastructure. |
| `prompt` | System prompt content lives in a sibling `system-prompt.md` file alongside `agent.toml`. This allows renderers to consume it independently (e.g., Claude Code reads `prompt.md`, Pi generates `SYSTEM.md`). Embedding prompt paths in TOML adds indirection without benefit. |
| `mcp` | MCP server configuration is tool-specific infrastructure (e.g., `claude mcp add`). It belongs to the deployment layer, not the agent definition. |
| `hooks` | Claude Code-exclusive concept. Lifecycle hooks (pre-tool, post-tool, etc.) have no equivalent in OpenCode or Pi. Including them would leak renderer-specific concerns into the canonical schema. |
| Datetime types | `builtins.fromTOML` in Nix does not support TOML datetime values. This is a confirmed parser limitation (verified in Task 2 spike). All date/time data must be represented as strings if needed. |
---
## Per-Renderer Support Matrix
The table below shows how each field is consumed by each renderer. "✓" means full
support, "~" means partial/mapped support, "–" means ignored.
- **Pi** — subagent support via `pi-subagents`; agent .md files with YAML frontmatter; permissions mapped to Pi tool list; skills via `SKILL.md`; AGENTS.md for discovery; SYSTEM.md for primary agent prompt