| description | Extending Zero — how to write AGENTS.md files, custom specialists, skills, hooks, MCP servers, and plugins for the open-source CLI coding agent. |
|---|---|
| globs | *.go, *.js, *.md, *.json, *.toml, *.yaml, *.yml |
| alwaysApply | false |
Zero is an open-source terminal coding agent. Out of the box it does the obvious things — read, edit, run, search — but the design point of the project is that every surface is configurable. This document is the user-facing guide for that configuration.
If you only want to use Zero, the README is enough. This page is for the other three jobs:
- Tell the agent about your project (drop an
AGENTS.mdin your repo). - Add new specialist sub-agents.
- Wire Zero into the rest of your toolchain (MCP, skills, hooks, plugins).
When Zero starts in a directory, it looks for project-level instructions and injects them into the system prompt. The lookup walks from your current working directory up to the nearest git root and reads the first matching file at each level — general rules at the repo root, more specific rules in sub-trees. Files are labeled with their directory in the prompt (e.g. ## Project guidelines (services/api/AGENTS.md)).
Accepted file names, in priority order at each level:
| Path | Notes |
|---|---|
./AGENTS.md |
The classic spot — committed to your repo, shared with the team. |
./ZERO.md |
Brand-specific alias. Same format, lower priority. |
./.zero/AGENTS.md |
Project-local, hidden, gitignored. Personal notes that stay out of git. |
Matching is case-insensitive on the basename, so AGENTS.md, Agents.md, and agents.md resolve to the same file on Windows and macOS. The git-tracked filename in this repo is AGENTS.md — keep that on case-sensitive filesystems (Linux, the WSL filesystem, or a CI runner) to match what the loader looks for.
Both files use the same format. YAML frontmatter is optional; the markdown body is loaded as instructions for the agent. Zero reads the file once at session start, so changes take effect on the next zero launch — not mid-session.
# Project conventions for <your project>
- Build with `make`, not `go build` directly.
- Tests live next to the source file (`foo_test.go` next to `foo.go`).
- Run `make lint` before opening a PR.
- Never edit files under `third_party/` — those are vendored.Tips:
- Keep each file under ~8 KiB. Zero caps the total across all matched files at 32 KiB; everything past the cap is dropped.
- Re-state rules in the imperative voice: "Run
make lint", not "you should consider running the linter". - Don't put secrets, model IDs, or environment-specific paths in
AGENTS.md. Useconfig.jsonfor those. - In a monorepo, drop a narrower
AGENTS.mdin each sub-tree (e.g.services/api/AGENTS.md). Zero picks those up automatically when you launch from inside the sub-tree. - A YAML frontmatter block (
---\n...\n---) at the top is preserved verbatim in the injected prompt but is not parsed forglobs:oralwaysApply:scoping today — keep the body self-contained.
Specialists are Zero's sub-agents. Three scopes, in priority order:
| Scope | Path | Shared? |
|---|---|---|
| Built-in | compiled into Zero | yes — worker, explorer, code-review |
| User | ~/.config/zero/specialists/*.md |
no — your machine only |
| Project | ./.zero/specialists/*.md |
yes — the repo team |
Project overrides user overrides built-in when names collide.
A specialist is a markdown manifest with frontmatter and a system prompt:
---
description: Reviews API changes for breaking-change risk and missing tests.
tools: read-only,plan
---
You review API changes. For every changed hunk in `internal/api/` or any file
that ends in `_api.go`:
1. Confirm the public signature is backward-compatible, or note the breaking
change explicitly with the migration path.
2. Confirm a corresponding test exists in `internal/api/*_test.go` and that
the new behaviour is exercised.
3. Flag any new exported symbol without a doc comment.
Reply with one JSON object per finding: `{"file", "line", "severity", "message", "fix"}`.CLI management (the prompt is passed inline via --prompt):
zero specialist list
zero specialist show api-reviewer
zero specialist create api-reviewer \
--project \
--description "Reviews API changes" \
--tools read-only,plan \
--prompt "$(cat api-reviewer.md)"
zero specialist edit api-reviewer --project
zero specialist delete api-reviewer --project
zero specialist path # prints the resolved specialists directoryThe full format spec (frontmatter fields, tool scopes, prompt conventions) is in docs/SPECIALISTS.md.
Roadmap. An in-UI specialist manager (create / edit / delete / preview) is on the backlog. Today you use the
zero specialistCLI subcommands above.
Skills are markdown instruction packs the agent can pull in on demand. Each skill is a directory containing a SKILL.md. Skills are user-level only in this version — there's no project-scoped skill directory yet, so anything you want shared with the team goes in AGENTS.md (section 1) or as a hook (section 4).
Discovery root: $ZERO_SKILLS_DIR → $XDG_DATA_HOME/zero/skills → ~/.local/share/zero/skills/. A missing directory is fine — Zero just reports "no skills".
~/.local/share/zero/skills/
run-benchmarks/
SKILL.md
write-changelog/
SKILL.md
SKILL.md format:
---
description: Run the project's benchmark suite and summarize the deltas.
---
# Run benchmarks
1. `make bench` — captures the wall-clock and RSS before and after.
2. `benchstat before.txt after.txt` — diffs the two.
3. Report any regression > 5% with the function name and the previous value.Only name and description are recognized in the frontmatter today. The name defaults to the directory name. If two skills declare the same name, the one in the lexicographically-first directory wins — duplicates are dropped silently. Plugin-declared skills (section 6) are not yet merged into the loader, so a skills: entry inside a plugin's plugin.json is not visible to the skill tool today.
The skill core tool lets the agent load any discovered skill by name.
Hooks fire shell commands on lifecycle events. Configure them in JSON:
- User:
~/.config/zero/hooks.json - Project:
./.zero/hooks.json
{
"enabled": true,
"hooks": [
{
"id": "block-rm-rf",
"event": "beforeTool",
"matcher": "bash",
"command": "/usr/local/bin/zero-hook-block-rmrf.sh",
"enabled": true
},
{
"id": "log-session",
"event": "sessionStart",
"command": "/usr/local/bin/zero-hook-log.sh",
"enabled": true
}
]
}The args array (when present) is passed verbatim to exec.CommandContext. The actual hook payload — event name, matcher, tool call id, tool name, tool input, tool output, status — is delivered to the command as JSON on stdin, not via ${...} substitution. A typical handler reads stdin and decides what to do:
#!/usr/bin/env bash
# /usr/local/bin/zero-hook-block-rmrf.sh
set -euo pipefail
payload="$(cat)"
if printf '%s' "$payload" | grep -q '"input":"[^"]*rm[[:space:]]+-rf'; then
echo "refusing rm -rf" >&2
exit 1
fiEvents the agent emits (in dispatch order):
| Event | Fires when | Matcher allowed? |
|---|---|---|
beforeTool |
A tool is about to run | yes (tool name) |
afterTool |
A tool just returned | yes (tool name) |
sessionStart |
A session begins | no |
sessionEnd |
A session ends | no |
specialistStart |
A sub-agent is spawned | yes (specialist name) |
specialistStop |
A sub-agent ends | yes (specialist name) |
A hook's exit code decides what happens next: 0 continues, non-zero blocks the tool call (beforeTool) or surfaces an error (afterTool). Hook execution is recorded in the audit log; the audit is reachable from the agent's view of past actions, not from a dedicated zero doctor check.
Roadmap. An in-UI hooks manager is on the backlog. Today you edit the JSON directly.
Zero is both an MCP client (it can call external MCP servers) and an MCP server (other agents can call its tools).
{
"mcp": {
"servers": {
"docs": {
"type": "stdio",
"command": "docs-mcp",
"args": ["--port", "7777"]
},
"github": {
"type": "http",
"url": "https://api.example.com/mcp",
"headers": { "Authorization": "Bearer YOUR_TOKEN_HERE" }
}
}
}
}Manage via CLI:
zero mcp add docs --type stdio -- docs-mcp --port 7777
zero mcp add github --type http --url https://api.example.com/mcp \
--header "Authorization=Bearer YOUR_TOKEN_HERE"
zero mcp list
zero mcp check docs
zero mcp remove github
zero mcp oauth login githubServers are merged from user and project configs (project wins on conflicts). Token-bearing values in config.json are sent verbatim — there is no ${env:...} expansion — so prefer one of:
- A wrapper script that sources the secret and execs the real command.
- A
--headervalue produced by command substitution ("Authorization=Bearer $(print-token)") in a private shell config that you keep out of git. - A secret manager that injects the env var your MCP server reads on its own (the
commandandargsthen run inside that environment).
zero serve --mcpThe server speaks MCP over stdio. Configure it from the receiving side as a stdio server whose command is zero serve --mcp.
A plugin is a self-contained directory that bundles tools, hooks, and skills for one capability. Plugins live at:
- User:
~/.config/zero/plugins/<id>/ - Project:
./.zero/plugins/<id>/
Each plugin has a plugin.json manifest:
{
"id": "github-pr-review",
"name": "GitHub PR Review",
"description": "Adds review tools for GitHub PRs.",
"version": "1.0.0",
"tools": [
{ "name": "list_prs", "command": "./tools/list_prs.sh" }
],
"hooks": [
{ "name": "pre-merge-check", "event": "beforeTool", "command": "./hooks/pre-merge.sh" }
],
"skills": [
{ "path": "./skills/review-checklist/SKILL.md" }
]
}Install and manage:
zero plugins add ./github-pr-review # copy into ~/.config/zero/plugins/ or ./.zero/plugins/
zero plugins list
zero plugins remove github-pr-review # alias: rmA plugin is enabled by being present in the plugins directory and disabled by removing it (or by the user setting "enabled": false in its plugin.json). Plugins are not enabled or disabled by a CLI subcommand today.
Plugin commands run with the plugin directory as their working directory. Use relative paths; the loader resolves them at activation time.
Roadmap. An in-UI plugins manager (browse, install, enable / disable) is on the backlog. Today you use the
zero pluginsCLI subcommands above. Skills declared inside a plugin'splugin.jsonare not yet merged into theskilltool's discovery (see section 3).
Three layers, applied in order (later layers override earlier ones):
| Layer | Path | Notes |
|---|---|---|
| Built-in defaults | compiled in | Lowest priority. |
| User config | ~/.config/zero/config.json |
Your machine. Never committed. |
| Project config | ./.zero/config.json |
The repo. Committed (or not, your call). |
| CLI flags | --model, --mode, ... |
Highest priority, per-invocation. |
| Environment | ZERO_* |
Provider commands, secrets, skills dir override. |
The user config holds things that should follow the user across projects (default provider, default model, theme). The project config holds things the team agreed on (provider catalog, sandbox policies, model restrictions).
The sandbox additionalWriteRoots key is ignored in project config by design — a checked-out repo cannot widen its own sandbox. Set it in the user config or pass --add-dir per-invocation.
A team that wants every contributor's Zero to behave the same way commits:
AGENTS.md— project conventions, build commands, do-not-edit lists..zero/config.json— provider catalog, default model, allowed tools..zero/specialists/api-reviewer.md— the team's PR-review specialist..zero/hooks.json— blockrm -rfandgit push --forceonbeforeTool..zero/plugins/internal-tooling/— a plugin that adds the team's internal CLI tools to the agent's toolset.
Each contributor adds only:
~/.config/zero/config.json— their personal API keys, theme, default mode.~/.local/share/zero/skills/— personal skills they keep across projects.
That's it. Run zero from the repo root and the agent has the team's full instruction set, every contributor's personal setup, and nothing else.
- README — install, quickstart, command reference.
- docs/SPECIALISTS.md — full specialist manifest spec.
- docs/STREAM_JSON_PROTOCOL.md —
zero execI/O contract. - docs/INSTALL.md — install from source or release.