530 lines
15 KiB
Markdown
530 lines
15 KiB
Markdown
# Task 4: OpenCode File-Based Agent Format Research
|
||
|
||
**Date**: 2026-04-10
|
||
**Status**: ✅ Complete
|
||
**Research Method**: WebFetch + Documentation Analysis
|
||
|
||
---
|
||
|
||
## Executive Summary
|
||
|
||
OpenCode supports **two agent configuration methods**:
|
||
1. **JSON** - Embedded in `opencode.json` (config.json)
|
||
2. **Markdown Files** - File-based in `.opencode/agents/` directory (per-project) or `~/.config/opencode/agents/` (global)
|
||
|
||
This research focuses on the **file-based markdown format**, which is the target for the harness-agnostic migration.
|
||
|
||
---
|
||
|
||
## File Location & Discovery
|
||
|
||
### Directory Structure
|
||
|
||
**Per-project agents** (takes precedence):
|
||
```
|
||
.opencode/agents/
|
||
├── agent-name.md
|
||
├── another-agent.md
|
||
└── ...
|
||
```
|
||
|
||
**Global agents** (fallback):
|
||
```
|
||
~/.config/opencode/agents/
|
||
├── agent-name.md
|
||
├── another-agent.md
|
||
└── ...
|
||
```
|
||
|
||
### Discovery Mechanism
|
||
|
||
- OpenCode **scans both directories** for `*.md` files
|
||
- The **filename (without .md extension)** becomes the **agent name**
|
||
- Per-project agents **override** global agents with the same name
|
||
- All agents are loaded at startup and available via `Tab` switching or `@mention`
|
||
|
||
### Key Finding
|
||
|
||
**The agent name is derived from the filename**, not from a `name` field in the frontmatter. Example:
|
||
- File: `review.md` → Agent name: `review`
|
||
- File: `code-reviewer.md` → Agent name: `code-reviewer`
|
||
|
||
---
|
||
|
||
## YAML Frontmatter Specification
|
||
|
||
All file-based agent markdown files must include YAML frontmatter with the following fields:
|
||
|
||
### Required Fields
|
||
|
||
| Field | Type | Description | Example |
|
||
|-------|------|-------------|---------|
|
||
| `description` | string | Brief description of agent purpose and when to use it. **REQUIRED**. | `"Reviews code for quality and best practices"` |
|
||
|
||
### Optional Fields
|
||
|
||
| Field | Type | Default | Description |
|
||
|-------|------|---------|-------------|
|
||
| `mode` | string | `all` | Agent mode: `primary`, `subagent`, or `all` |
|
||
| `model` | string | Model globally configured in config | Override LLM model for this agent |
|
||
| `temperature` | float | Model-specific (usually 0 or 0.55 for Qwen) | LLM response randomness (0.0–1.0) |
|
||
| `top_p` | float | — | Alternative to temperature for diversity control |
|
||
| `steps` | integer | No limit | Max agentic iterations before forced text-only response |
|
||
| `disable` | boolean | `false` | Set to `true` to disable the agent |
|
||
| `hidden` | boolean | `false` | Hide from `@` autocomplete (subagents only) |
|
||
| `color` | string | — | Hex color (e.g., `#FF5733`) or theme color (primary, secondary, accent, success, warning, error, info) |
|
||
| `permission` | object | — | Permission rules for edit, bash, webfetch, question, websearch, external_directory |
|
||
| `task` | object | — | Control which subagents this agent can invoke via Task tool |
|
||
|
||
### Provider-Specific Fields
|
||
|
||
Any additional fields are **passed through directly to the LLM provider**. Example for OpenAI reasoning models:
|
||
```yaml
|
||
---
|
||
description: Agent using high reasoning effort
|
||
model: openai/gpt-5
|
||
reasoningEffort: high
|
||
textVerbosity: low
|
||
---
|
||
```
|
||
|
||
---
|
||
|
||
## Permission Format (YAML)
|
||
|
||
Permissions control what actions an agent can perform. The format supports two styles:
|
||
|
||
### Simple Format (Single Action)
|
||
|
||
```yaml
|
||
permission:
|
||
edit: deny
|
||
bash: ask
|
||
webfetch: allow
|
||
```
|
||
|
||
### Granular Format (Rules Array)
|
||
|
||
For more control over specific patterns:
|
||
|
||
```yaml
|
||
permission:
|
||
edit:
|
||
"*": allow
|
||
"/run/agenix/**": deny
|
||
bash:
|
||
"*": ask
|
||
"git status*": allow
|
||
"git log*": allow
|
||
"git push": ask
|
||
"grep *": allow
|
||
webfetch: deny
|
||
question: allow
|
||
websearch: allow
|
||
external_directory:
|
||
"*": ask
|
||
"~/p/**": allow
|
||
"~/.config/opencode/**": allow
|
||
"/tmp/**": allow
|
||
```
|
||
|
||
### Permission Actions
|
||
|
||
| Value | Meaning |
|
||
|-------|---------|
|
||
| `allow` | Tool allowed without approval |
|
||
| `ask` | Prompt user for approval before running |
|
||
| `deny` | Tool disabled |
|
||
|
||
### Supported Permission Keys
|
||
|
||
| Key | Values | Notes |
|
||
|-----|--------|-------|
|
||
| `edit` | `allow\|ask\|deny` or nested rules | File write/patch operations |
|
||
| `bash` | `allow\|ask\|deny` or nested rules | Bash command execution; supports glob patterns |
|
||
| `webfetch` | `allow\|ask\|deny` | HTTP requests |
|
||
| `question` | `allow\|ask\|deny` | User questions/clarification |
|
||
| `websearch` | `allow\|ask\|deny` | Web search operations |
|
||
| `external_directory` | `allow\|ask\|deny` or nested rules | Access to external directories |
|
||
| `task` | nested rules | Subagent invocation control (glob patterns) |
|
||
|
||
### Glob Pattern Support
|
||
|
||
Patterns support wildcards and recursion:
|
||
- `*` — single-level wildcard
|
||
- `**` — recursive wildcard
|
||
- `git push*` — suffix matching
|
||
- `~/p/**` — home directory paths
|
||
- `/run/agenix/**` — absolute paths
|
||
|
||
### Rule Precedence
|
||
|
||
When multiple rules match, the **last matching rule wins**:
|
||
|
||
```yaml
|
||
bash:
|
||
"*": ask
|
||
"git status*": allow
|
||
"git push*": deny
|
||
```
|
||
|
||
In this example:
|
||
- `git status` matches both `*` and `git status*` → result: **allow** (last rule wins)
|
||
- `git push origin main` matches both `*` and `git push*` → result: **deny**
|
||
- `ls -la` matches only `*` → result: **ask**
|
||
|
||
---
|
||
|
||
## Mode Field Values
|
||
|
||
| Mode | Type | Description |
|
||
|------|------|-------------|
|
||
| `primary` | Primary agent | Agent available via `Tab` key switching; handles main conversation |
|
||
| `subagent` | Specialized agent | Invoked via `@mention` or automatically by other agents for specific tasks |
|
||
| `all` | Flexible | Can be used as both primary and subagent (default if omitted) |
|
||
|
||
---
|
||
|
||
## System Prompt Delivery
|
||
|
||
The markdown file body (after the YAML frontmatter) contains the **system prompt**:
|
||
|
||
```markdown
|
||
---
|
||
description: Code review without edits
|
||
mode: subagent
|
||
permission:
|
||
edit: deny
|
||
---
|
||
You are a code reviewer. Focus on:
|
||
- Code quality and best practices
|
||
- Potential bugs and edge cases
|
||
- Performance implications
|
||
- Security considerations
|
||
|
||
Provide constructive feedback without making direct changes.
|
||
```
|
||
|
||
The **markdown content is passed directly as the system prompt** to the LLM. It supports:
|
||
- Inline markdown formatting
|
||
- Lists and sections
|
||
- Structured instructions
|
||
- Code examples (fenced with backticks)
|
||
|
||
---
|
||
|
||
## Default Behavior for Omitted Fields
|
||
|
||
| Field | Default | Notes |
|
||
|-------|---------|-------|
|
||
| `description` | **ERROR** | Required; absence causes parse failure |
|
||
| `mode` | `all` | Agent can be used as primary or subagent |
|
||
| `model` | Global config model | Primary agents use global model; subagents use parent's model |
|
||
| `temperature` | Model-specific | Usually 0 for most models; 0.55 for Qwen models |
|
||
| `permission` | Full access | If omitted, all tools enabled (no restrictions) |
|
||
| `disable` | `false` | Agent is enabled by default |
|
||
| `hidden` | `false` | Agent visible in `@` autocomplete (if subagent) |
|
||
|
||
---
|
||
|
||
## Interaction with config.json (JSON Format)
|
||
|
||
### Current State (Task 1 Finding)
|
||
|
||
The current system embeds agents in **config.json** via JSON:
|
||
|
||
```json
|
||
{
|
||
"agent": {
|
||
"build": {
|
||
"description": "...",
|
||
"mode": "primary",
|
||
"permission": { ... }
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### File-Based Agents Complement, Don't Replace
|
||
|
||
- **JSON agents** (in config.json) are loaded from embedded config
|
||
- **Markdown agents** (.opencode/agents/*.md files) are symlinked
|
||
- **Both are loaded** and available simultaneously
|
||
- **Markdown agents override** JSON agents with the same name
|
||
|
||
### Migration Path
|
||
|
||
The harness-agnostic migration will:
|
||
1. Move agent definitions from `agents.json` → `.opencode/agent/{name}.md` files
|
||
2. Update home-manager deployment to symlink `.opencode/agents/` instead of embedding `agents.json`
|
||
3. System prompt changes (markdown file edits) will **NOT require `home-manager switch`**
|
||
|
||
---
|
||
|
||
## Key Advantage: Prompt Changes Don't Require home-manager switch
|
||
|
||
### Current Limitation (JSON/Embedded)
|
||
|
||
```
|
||
agents.json → home-manager → embedded into config.json
|
||
↓
|
||
Change required in nixpkgs module
|
||
↓
|
||
home-manager switch (full system rebuild)
|
||
```
|
||
|
||
### New Capability (File-Based)
|
||
|
||
```
|
||
.opencode/agents/{name}.md → home-manager → symlinks to ~/.config/opencode/agents/
|
||
↓
|
||
Change markdown file directly
|
||
↓
|
||
OpenCode reloads on next startup (NO home-manager switch needed)
|
||
```
|
||
|
||
**This is the KEY ADVANTAGE** of file-based agents: faster iteration on prompts and agent configuration.
|
||
|
||
---
|
||
|
||
## Limitations & Gotchas
|
||
|
||
### No Name Field in Frontmatter
|
||
|
||
- Agent name comes from **filename only**
|
||
- No `name: foo` field in frontmatter
|
||
- Renaming file renames the agent
|
||
|
||
### Model References with {file:...}
|
||
|
||
In JSON config, you can reference external files:
|
||
```json
|
||
{
|
||
"prompt": "{file:./prompts/build.txt}"
|
||
}
|
||
```
|
||
|
||
In markdown files, the **body IS the prompt** — no `{file:...}` syntax. The entire markdown content after frontmatter is the system prompt.
|
||
|
||
### Subdirectories Not Scanned
|
||
|
||
- Only files directly in `.opencode/agents/` are loaded
|
||
- Subdirectories are ignored
|
||
- All agent definitions must be in one directory level
|
||
|
||
### Filename Validation
|
||
|
||
The filename should follow these conventions (not enforced, but recommended):
|
||
- Lowercase letters, numbers, hyphens: `[a-z0-9-]+`
|
||
- No spaces, no special characters
|
||
- Examples: `code-reviewer.md`, `security-auditor.md`, `docs-writer.md`
|
||
|
||
---
|
||
|
||
## Complete Example: File-Based Agent
|
||
|
||
### File: `.opencode/agents/code-reviewer.md`
|
||
|
||
```markdown
|
||
---
|
||
description: Performs comprehensive code review focusing on quality, security, and performance
|
||
mode: subagent
|
||
model: anthropic/claude-sonnet-4-20250514
|
||
temperature: 0.1
|
||
permission:
|
||
edit:
|
||
"*": deny
|
||
bash:
|
||
"*": allow
|
||
"grep *": allow
|
||
"git diff*": allow
|
||
webfetch: allow
|
||
question: allow
|
||
---
|
||
You are an expert code reviewer with deep knowledge of software architecture, security best practices, and performance optimization.
|
||
|
||
## Your Mission
|
||
|
||
Review code for:
|
||
1. **Correctness** - Logic errors, edge cases, off-by-one bugs
|
||
2. **Security** - Input validation, injection vulnerabilities, data exposure
|
||
3. **Performance** - Algorithmic efficiency, memory usage, unnecessary allocations
|
||
4. **Maintainability** - Code clarity, naming, documentation, SOLID principles
|
||
5. **Testing** - Coverage gaps, missing test cases, integration test concerns
|
||
|
||
## Process
|
||
|
||
1. Ask clarifying questions about context and constraints
|
||
2. Provide specific, actionable feedback with examples
|
||
3. Suggest refactorings with rationale
|
||
4. Never make changes directly (read-only mode)
|
||
5. Prioritize critical issues over style concerns
|
||
|
||
## Output Format
|
||
|
||
- **Critical Issues** (must fix before merge)
|
||
- **Important Improvements** (should fix)
|
||
- **Nice-to-Have Suggestions** (consider for future)
|
||
- **Questions** (for author clarification)
|
||
```
|
||
|
||
---
|
||
|
||
## Complete Example: JSON Config Format (For Reference)
|
||
|
||
For comparison, here's the equivalent in JSON config.json:
|
||
|
||
```json
|
||
{
|
||
"$schema": "https://opencode.ai/config.json",
|
||
"agent": {
|
||
"code-reviewer": {
|
||
"description": "Performs comprehensive code review focusing on quality, security, and performance",
|
||
"mode": "subagent",
|
||
"model": "anthropic/claude-sonnet-4-20250514",
|
||
"temperature": 0.1,
|
||
"permission": {
|
||
"edit": {
|
||
"*": "deny"
|
||
},
|
||
"bash": {
|
||
"*": "allow",
|
||
"grep *": "allow",
|
||
"git diff*": "allow"
|
||
},
|
||
"webfetch": "allow",
|
||
"question": "allow"
|
||
},
|
||
"prompt": "You are an expert code reviewer...\n\n## Your Mission\n..."
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Source Materials
|
||
|
||
### Documentation
|
||
|
||
- **Official**: https://opencode.ai/docs/agents
|
||
- **Agents Section**: Comprehensive spec for all agent config options
|
||
- **Markdown Example**: Review agent example provided in docs
|
||
- **Security Auditor Example**: Security-focused agent example
|
||
|
||
### Code References
|
||
|
||
- **GitHub**: https://github.com/anomalyco/opencode (dev branch)
|
||
- **Config Spec**: schema.json embedded in docs
|
||
- **Test Cases**: `.opencode/agents/` in opencode repo (example files)
|
||
|
||
### Current System Reference
|
||
|
||
- **Nix Module**: `/home/m3tam3re/p/NIX/nixpkgs/modules/home-manager/coding/opencode.nix`
|
||
- Line 149: `agent = builtins.fromJSON (builtins.readFile "${inputs.agents}/agents/agents.json");`
|
||
- Line 149: Shows current embedding pattern
|
||
|
||
- **AGENTS repo**: `/home/m3tam3re/p/AI/AGENTS/agents/agents.json`
|
||
- 6 agents: Chiron, Chiron Forge, Hermes, Athena, Apollo, Calliope
|
||
- Permission structure: nested objects with wildcard patterns
|
||
|
||
---
|
||
|
||
## Questions Addressed
|
||
|
||
### Q: Do file-based agents need `home-manager switch` for prompt changes?
|
||
|
||
**A: NO** ✅
|
||
|
||
- File changes are immediately available
|
||
- `.opencode/agents/` is symlinked (not embedded)
|
||
- OpenCode reloads agent definitions at startup
|
||
- Prompt changes require only file edit + app restart
|
||
|
||
**This is the KEY ADVANTAGE** driving the migration.
|
||
|
||
### Q: What directory: `agent` or `agents`?
|
||
|
||
**A: `agents` (plural)** (both global and per-project)
|
||
- Global: `~/.config/opencode/agents/`
|
||
- Per-project: `.opencode/agents/`
|
||
|
||
### Q: Do agent names need a `name` field in frontmatter?
|
||
|
||
**A: NO**
|
||
|
||
- Agent name comes from **filename only**
|
||
- No `name: foo` field in frontmatter
|
||
- Example: `review.md` → agent name is `review`
|
||
|
||
### Q: What YAML frontmatter fields are required?
|
||
|
||
**A: Only `description`** is truly required
|
||
|
||
- All other fields have sensible defaults
|
||
- Missing fields use their defaults
|
||
- Frontmatter-less file will fail to parse
|
||
|
||
### Q: How are permissions specified in markdown?
|
||
|
||
**A: Same nested object format as JSON**
|
||
|
||
```yaml
|
||
permission:
|
||
edit:
|
||
"*": allow
|
||
"/sensitive/**": deny
|
||
bash:
|
||
"*": ask
|
||
"git push": deny
|
||
```
|
||
|
||
---
|
||
|
||
## Confirmation Summary
|
||
|
||
| Question | Finding |
|
||
|----------|---------|
|
||
| **Directory**: `agent` or `agents`? | `agents/` (both global and per-project) |
|
||
| **File naming**: How determined? | Filename (without .md) becomes agent name |
|
||
| **Required fields**: What's mandatory? | `description` only; others have defaults |
|
||
| **Permission format**: YAML or different? | Same nested object format as JSON |
|
||
| **Mode values**: Options? | `primary` \| `subagent` \| `all` |
|
||
| **Prompt format**: How specified? | Markdown body after frontmatter |
|
||
| **Requires HM switch for prompt changes?** | **NO** ✅ (major advantage) |
|
||
| **Does frontmatter need `name` field?** | **NO** (filename is the name) |
|
||
| **Can agents be in subdirectories?** | **NO** (only root level of `.opencode/agents/`) |
|
||
| **Can you override agents from JSON config?** | **YES** (markdown agents override JSON with same name) |
|
||
|
||
---
|
||
|
||
## Next Steps (Task 9: OpenCode Renderer)
|
||
|
||
The renderer will generate `.opencode/agents/{name}.md` files with:
|
||
|
||
1. **Frontmatter generation**:
|
||
- Convert agent.toml `[description]` → YAML `description:`
|
||
- Convert `[mode]` → YAML `mode:`
|
||
- Convert `[temperature]` → YAML `temperature:`
|
||
- Convert `[permission]` from two-level format → nested YAML objects
|
||
|
||
2. **Body generation**:
|
||
- Use agent.toml `system_prompt` field → markdown body
|
||
|
||
3. **File naming**:
|
||
- Filename: `{agent_name}.md` (from agent.toml `name` field)
|
||
- Agent name in OpenCode: derived from filename automatically
|
||
|
||
---
|
||
|
||
## Evidence Collection
|
||
|
||
- **Source 1**: https://opencode.ai/docs/agents (Official documentation)
|
||
- **Source 2**: `/home/m3tam3re/p/NIX/nixpkgs/modules/home-manager/coding/opencode.nix` (Current deployment)
|
||
- **Source 3**: `/home/m3tam3re/p/AI/AGENTS/agents/agents.json` (Current agent definitions)
|
||
- **Source 4**: `/home/m3tam3re/p/AI/AGENTS/AGENTS.md` (Repository documentation)
|
||
|
||
**Research Date**: 2026-04-10
|
||
**Researcher**: Sisyphus-Junior
|
||
**Task**: Task 4 of harness-agnostic-migration plan
|