Skip to content
Merged
50 changes: 27 additions & 23 deletions packages/opencode/src/cli/cmd/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ import type { Argv } from "yargs"

type AgentMode = "all" | "primary" | "subagent"

const AVAILABLE_TOOLS = ["bash", "read", "write", "edit", "glob", "grep", "webfetch", "task", "todowrite"]
// Permission keys (not raw tool names). Multiple tools can map to a single
// permission — e.g. write/edit/apply_patch all gate on `edit` — so we configure
// agents at the permission level to match how the runtime actually enforces it.
Comment thread
rekram1-node marked this conversation as resolved.
Comment thread
rekram1-node marked this conversation as resolved.
const AVAILABLE_PERMISSIONS = ["bash", "read", "edit", "glob", "grep", "webfetch", "task", "todowrite", "websearch", "codesearch", "lsp", "skill"]

const AgentCreateCommand = cmd({
command: "create",
Expand All @@ -35,9 +38,10 @@ const AgentCreateCommand = cmd({
describe: "agent mode",
choices: ["all", "primary", "subagent"] as const,
})
.option("tools", {
.option("permissions", {
type: "string",
describe: `comma-separated list of tools to enable (default: all). Available: "${AVAILABLE_TOOLS.join(", ")}"`,
alias: ["tools"],
describe: `comma-separated list of permissions to allow (default: all). Available: "${AVAILABLE_PERMISSIONS.join(", ")}"`,
})
.option("model", {
type: "string",
Expand All @@ -51,9 +55,9 @@ const AgentCreateCommand = cmd({
const cliPath = args.path
const cliDescription = args.description
const cliMode = args.mode as AgentMode | undefined
const cliTools = args.tools
const perms = args.permissions

const isFullyNonInteractive = cliPath && cliDescription && cliMode && cliTools !== undefined
const isFullyNonInteractive = cliPath && cliDescription && cliMode && perms !== undefined

if (!isFullyNonInteractive) {
UI.empty()
Expand Down Expand Up @@ -120,21 +124,21 @@ const AgentCreateCommand = cmd({
})
spinner.stop(`Agent ${generated.identifier} generated`)

// Select tools
let selectedTools: string[]
if (cliTools !== undefined) {
selectedTools = cliTools ? cliTools.split(",").map((t) => t.trim()) : AVAILABLE_TOOLS
// Select permissions to allow
let selected: string[]
if (perms !== undefined) {
selected = perms ? perms.split(",").map((t) => t.trim()) : AVAILABLE_PERMISSIONS
} else {
const result = await prompts.multiselect({
message: "Select tools to enable (Space to toggle)",
options: AVAILABLE_TOOLS.map((tool) => ({
label: tool,
value: tool,
message: "Select permissions to allow (Space to toggle)",
options: AVAILABLE_PERMISSIONS.map((permission) => ({
label: permission,
value: permission,
})),
initialValues: AVAILABLE_TOOLS,
initialValues: AVAILABLE_PERMISSIONS,
})
if (prompts.isCancel(result)) throw new UI.CancelledError()
selectedTools = result
selected = result
}

// Get mode
Expand Down Expand Up @@ -167,25 +171,25 @@ const AgentCreateCommand = cmd({
mode = modeResult
}

// Build tools config
const tools: Record<string, boolean> = {}
for (const tool of AVAILABLE_TOOLS) {
if (!selectedTools.includes(tool)) {
tools[tool] = false
// Build permissions config — deny anything not explicitly selected.
const permissions: Record<string, "deny"> = {}
Comment thread
rekram1-node marked this conversation as resolved.
for (const permission of AVAILABLE_PERMISSIONS) {
if (!selected.includes(permission)) {
permissions[permission] = "deny"
}
}

// Build frontmatter
const frontmatter: {
description: string
mode: AgentMode
tools?: Record<string, boolean>
permission?: Record<string, "deny">
} = {
description: generated.whenToUse,
mode,
}
if (Object.keys(tools).length > 0) {
frontmatter.tools = tools
if (Object.keys(permissions).length > 0) {
frontmatter.permission = permissions
Comment thread
rekram1-node marked this conversation as resolved.
}

// Write file
Expand Down
66 changes: 44 additions & 22 deletions packages/web/src/content/docs/agents.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -149,29 +149,26 @@ Configure agents in your `opencode.json` config file:
"mode": "primary",
"model": "anthropic/claude-sonnet-4-20250514",
"prompt": "{file:./prompts/build.txt}",
"tools": {
"write": true,
"edit": true,
"bash": true
"permission": {
"edit": "allow",
"bash": "allow"
}
},
"plan": {
"mode": "primary",
"model": "anthropic/claude-haiku-4-20250514",
"tools": {
"write": false,
"edit": false,
"bash": false
"permission": {
"edit": "deny",
"bash": "deny"
}
},
"code-reviewer": {
"description": "Reviews code for best practices and potential issues",
"mode": "subagent",
"model": "anthropic/claude-sonnet-4-20250514",
"prompt": "You are a code reviewer. Focus on security, performance, and maintainability.",
"tools": {
"write": false,
"edit": false
"permission": {
"edit": "deny"
}
}
}
Expand All @@ -193,10 +190,9 @@ description: Reviews code for quality and best practices
mode: subagent
model: anthropic/claude-sonnet-4-20250514
temperature: 0.1
tools:
write: false
edit: false
bash: false
permission:
edit: deny
bash: deny
---

You are in code review mode. Focus on:
Expand Down Expand Up @@ -417,12 +413,39 @@ You can also use wildcards in legacy `tools` entries to control multiple tools a

### Permissions

You can configure permissions to manage what actions an agent can take. Currently, the permissions for the `edit`, `bash`, and `webfetch` tools can be configured to:
You can configure permissions to manage what actions an agent can take. Each permission key can be set to:

- `"ask"` — Prompt for approval before running the tool
- `"allow"` — Allow all operations without approval
- `"deny"` — Disable the tool

The available permission keys are:

| Key | Tools it gates |
| -------------------- | ----------------------------------------------------------------------------- |
| `read` | `read` |
| `edit` | `write`, `edit`, `apply_patch` |
| `glob` | `glob` |
| `grep` | `grep` |
| `list` | `list` |
| `bash` | `bash` |
| `task` | `task` |
| `external_directory` | Any tool that reads or writes files outside the project worktree |
| `todowrite` | `todowrite`, `todoread` |
| `webfetch` | `webfetch` |
| `websearch` | `websearch` |
| `codesearch` | `codesearch` |
| `lsp` | `lsp` |
| `skill` | `skill` |
| `question` | `question` |
| `doom_loop` | Recovery prompts when an agent appears stuck |

`read`, `edit`, `glob`, `grep`, `list`, `bash`, `task`, `external_directory`, `lsp`, and `skill` accept either a shorthand action (`"allow" | "ask" | "deny"`) or an object of glob/pattern → action for fine-grained control. The remaining keys accept the shorthand action only.

:::note
Permission keys are matched as wildcard patterns against the underlying tool name, so the same syntax works for built-ins, custom tools, and MCP tools — for example `"mymcp_*": "deny"` denies every tool from an MCP server, and `"mymcp_search": "ask"` targets a single one.
:::

```json title="opencode.json"
{
"$schema": "https://opencode.ai/config.json",
Expand Down Expand Up @@ -680,7 +703,7 @@ This interactive command will:
1. Ask where to save the agent; global or project-specific.
2. Description of what the agent should do.
3. Generate an appropriate system prompt and identifier.
4. Let you select which tools the agent can access.
4. Let you select which permissions the agent should be allowed (anything you don't select is denied).
5. Finally, create a markdown file with the agent configuration.

---
Expand Down Expand Up @@ -713,8 +736,8 @@ Do you have an agent you'd like to share? [Submit a PR](https://github.com/anoma
---
description: Writes and maintains project documentation
mode: subagent
tools:
bash: false
permission:
bash: deny
---

You are a technical writer. Create clear, comprehensive documentation.
Expand All @@ -735,9 +758,8 @@ Focus on:
---
description: Performs security audits and identifies vulnerabilities
mode: subagent
tools:
write: false
edit: false
permission:
edit: deny
---

You are a security expert. Focus on identifying potential security issues.
Expand Down
14 changes: 13 additions & 1 deletion packages/web/src/content/docs/cli.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,19 @@ Create a new agent with custom configuration.
opencode agent create
```

This command will guide you through creating a new agent with a custom system prompt and tool configuration.
This command will guide you through creating a new agent with a custom system prompt and permission configuration. Anything you don't allow is denied in the generated agent's frontmatter.

#### Flags

| Flag | Description |
| ---------------- | ---------------------------------------------------------------------------------------------------------- |
| `--path` | Directory to write the agent file to (defaults to global or `.opencode/agent` based on the prompt) |
| `--description` | What the agent should do |
| `--mode` | Agent mode: `all`, `primary`, or `subagent` |
| `--permissions` | Comma-separated list of permissions to allow (default: all). Available: `bash`, `read`, `edit`, `glob`, `grep`, `webfetch`, `task`, `todowrite`, `websearch`, `codesearch`, `lsp`, `skill`. Anything omitted is denied. Alias: `--tools` |
| `--model`, `-m` | Model to use, in `provider/model` format |

Passing all of `--path`, `--description`, `--mode`, and `--permissions` runs the command non-interactively.

---

Expand Down
Loading