Files
AGENTS/agents/SCHEMA.md

276 lines
9.6 KiB
Markdown
Raw Normal View History

# Canonical `agent.toml` Schema
This document defines the canonical TOML schema for agent definitions in the AGENTS
repository. The format is **harness-agnostic**: every renderer (OpenCode, Claude Code,
Pi) consumes the same file and silently drops fields it cannot map.
The schema is intentionally minimal. Fields that belong to the deployment environment
(model selection, MCP configuration) are excluded. Fields that belong to a specific
renderer (hooks, datetime) are excluded. The TOML file describes *what the agent is
and what it can do*, not *how it is deployed*.
---
## Required Fields
| Field | Type | Constraints |
|-------|------|-------------|
| `name` | string | kebab-case slug; pattern `[a-z0-9-]+`; must be unique across all agents |
| `description` | string | Human-readable purpose statement; single line; no trailing period |
### Examples
```toml
name = "chiron"
description = "Personal AI assistant (Plan Mode). Read-only analysis, planning, and guidance"
```
```toml
name = "chiron-forge"
description = "Personal AI assistant (Build Mode). Full execution and task completion capabilities with safety prompts"
```
**Constraint notes**:
- `name` must be kebab-case (lowercase, hyphens only). The OpenCode renderer uses it as
the agent identifier; Claude Code requires `[a-z0-9-]+` and will reject spaces.
- `description` is required by Claude Code. OpenCode uses it in the agent picker.
---
## Optional Fields
| Field | Type | Default | Notes |
|-------|------|---------|-------|
| `display_name` | string | — | Human-readable label (e.g. `"Chiron (Assistant)"`). Used by OpenCode in the agent picker. Ignored by Claude Code. |
| `mode` | string | `"all"` | One of: `"primary"`, `"subagent"`, `"all"` |
| `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.
| Field | OpenCode | Claude Code | Pi |
|-------|----------|-------------|----|
2026-04-13 16:53:17 +02:00
| `name` | ✓ agent identifier | ✓ must be `[a-z0-9-]+` | ✓ subagent frontmatter |
| `description` | ✓ agent picker | ✓ required | ✓ subagent frontmatter |
| `display_name` | ✓ picker label | ignored | ✓ in AGENTS.md |
| `mode` | ✓ maps to `mode` | all are subagents | primary only → `SYSTEM.md` |
| `tags` | ~ future use | ignored | ignored |
| `max_turns` | ✓ maps to `steps` | ✓ maps to `maxTurns` | ignored |
2026-04-13 16:53:17 +02:00
| `skills` | ✓ SKILL.md loaded | ✓ SKILL.md loaded | ✓ subagent `skill` field |
| `context` | ✓ injected | ✓ injected | ~ manual inclusion |
| `rules` | ✓ rule injection | ✓ rule injection | ignored |
2026-04-13 16:53:17 +02:00
| `permissions.bash` | ✓ rule DSL | ✓ bash tool perms | ~ tool enable/disable |
| `permissions.edit` | ✓ path rules | ✓ path rules | ~ tool enable/disable |
| `permissions.webfetch` | ✓ intent only | ✓ intent only | ~ tool enable/disable |
| `permissions.websearch` | ✓ intent only | ✓ intent only | ~ tool enable/disable |
| `permissions.question` | ✓ intent only | not a tool | not a concept |
2026-04-13 16:53:17 +02:00
| `permissions.external_directory` | ✓ path rules | not supported | ~ tools list (allow/ask → include) |
**Renderer summary**:
- **OpenCode** — full support; most fields have direct mappings
- **Claude Code** — strong support; drops `display_name`, `external_directory`, `mode`
2026-04-13 16:53:17 +02:00
- **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
---
## Sample `agent.toml`
The following is a complete, valid example for the "chiron" agent. It demonstrates all
field categories and can be parsed with `builtins.fromTOML`.
```toml
# agents/chiron/agent.toml
# Chiron — Personal AI Assistant (Plan Mode)
name = "chiron"
display_name = "Chiron (Assistant)"
description = "Personal AI assistant (Plan Mode). Read-only analysis, planning, and guidance"
mode = "primary"
tags = ["assistant", "plan-mode", "read-only"]
max_turns = 50
skills = ["systematic-debugging", "git-master", "brainstorming"]
context = ["../../context/profile.md"]
rules = ["languages/nix", "languages/python", "concerns/testing"]
[permissions.question]
intent = "allow"
[permissions.webfetch]
intent = "allow"
[permissions.websearch]
intent = "allow"
[permissions.edit]
intent = "deny"
[permissions.bash]
intent = "ask"
rules = [
"git status*:allow",
"git log*:allow",
"git diff*:allow",
"git branch*:allow",
"git show*:allow",
"grep *:allow",
"ls *:allow",
"cat *:allow",
"head *:allow",
"tail *:allow",
"wc *:allow",
"which *:allow",
"echo *:allow",
"nix *:allow",
]
[permissions.external_directory]
intent = "ask"
rules = [
"~/p/**:allow",
"~/.config/opencode/**:allow",
"/tmp/**:allow",
"/run/agenix/**:allow",
]
```
### Parse verification
This sample can be verified with:
```bash
nix eval --impure \
--expr 'builtins.fromTOML (builtins.readFile /path/to/agent.toml)' \
--json | jq .
```
Expected top-level keys: `name`, `display_name`, `description`, `mode`, `tags`,
`max_turns`, `skills`, `context`, `rules`, `permissions`.
Expected `permissions` keys: `question`, `webfetch`, `websearch`, `edit`, `bash`,
`external_directory`.
---
## Schema Version
This document describes schema version **1.0.0** (initial canonical definition).
Changes to field names, types, or semantics must be reflected here with a version bump.