15 KiB
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:
- JSON - Embedded in
opencode.json(config.json) - 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
*.mdfiles - 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
Tabswitching 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:
---
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)
permission:
edit: deny
bash: ask
webfetch: allow
Granular Format (Rules Array)
For more control over specific patterns:
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 wildcardgit push*— suffix matching~/p/**— home directory paths/run/agenix/**— absolute paths
Rule Precedence
When multiple rules match, the last matching rule wins:
bash:
"*": ask
"git status*": allow
"git push*": deny
In this example:
git statusmatches both*andgit status*→ result: allow (last rule wins)git push origin mainmatches both*andgit push*→ result: denyls -lamatches 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:
---
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:
{
"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:
- Move agent definitions from
agents.json→.opencode/agent/{name}.mdfiles - Update home-manager deployment to symlink
.opencode/agents/instead of embeddingagents.json - 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: foofield in frontmatter - Renaming file renames the agent
Model References with {file:...}
In JSON config, you can reference external files:
{
"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
---
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:
{
"$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
- Line 149:
-
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: foofield in frontmatter - Example:
review.md→ agent name isreview
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
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:
-
Frontmatter generation:
- Convert agent.toml
[description]→ YAMLdescription: - Convert
[mode]→ YAMLmode: - Convert
[temperature]→ YAMLtemperature: - Convert
[permission]from two-level format → nested YAML objects
- Convert agent.toml
-
Body generation:
- Use agent.toml
system_promptfield → markdown body
- Use agent.toml
-
File naming:
- Filename:
{agent_name}.md(from agent.tomlnamefield) - Agent name in OpenCode: derived from filename automatically
- Filename:
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