plugin authors
docs/guides/plugin-authors.md
Plugin authors guide — subagent definitions
gbrain discovers subagent definitions from outside this repo via
GBRAIN_PLUGIN_PATH. If you maintain a downstream agent (your OpenClaw
deployment, a workflow host, a private tool) and want to ship custom
subagents alongside it, drop a plugin directory on that env path.
This guide is for plugin authors. The CLI user doesn't need to read it.
Two plugin systems. This doc covers subagent definitions (markdown prompts the
subagentjob handler runs). Custom job handlers (code the Minion worker executes) are a separate system — see plugin-handlers.md.
Minimum viable plugin
/path/to/my-plugin/
├── gbrain.plugin.json
└── subagents/
└── my-summarizer.md
gbrain.plugin.json:
{
"name": "my-plugin",
"version": "1.0.0",
"plugin_version": "gbrain-plugin-v1"
}
subagents/my-summarizer.md:
---
name: my-summarizer
model: claude-sonnet-4-6
allowed_tools:
- brain_search
- brain_get_page
---
You are a brain page summarizer. Given a slug, fetch the page and produce
a 3-sentence summary.
Turning it on
export GBRAIN_PLUGIN_PATH="/path/to/my-plugin"
gbrain jobs work # worker startup prints the plugin load line
gbrain agent run "summarize meetings/2026-04-20" --subagent-def my-summarizer
Multiple plugins: colon-separated, just like $PATH.
export GBRAIN_PLUGIN_PATH="/path/to/plugin-a:/path/to/plugin-b"
Rules (strict by design)
Path policy. Absolute paths only. Relative paths, ~-prefixed paths,
and URL-style paths (https://, file://) are rejected with a warning.
You control where your plugin lives on disk; gbrain doesn't guess.
Collision policy. If two plugins ship a subagent with the same name,
the one listed FIRST in GBRAIN_PLUGIN_PATH wins. The other is dropped
with a warning naming both sources.
Trust policy. Plugins ship subagent definitions ONLY:
- You cannot declare new tools.
- You cannot extend the brain tool allow-list.
- You cannot override any
agentSafeor similar flag. - Your
allowed_tools:frontmatter field MUST subset the derived brain tool registry. Names not in the registry are rejected at plugin load time (worker startup), NOT at subagent dispatch time — so a typo in your plugin gives you a loud startup error, not a silent "tool never fires" at 3am.
Plugin-declared tools would require a new plugin_version contract;
nothing under gbrain-plugin-v1 opens that up.
gbrain.plugin.json
| field | type | required | notes |
|---|---|---|---|
name | string | yes | Human-readable plugin id. Shows up in warnings and collision logs. |
version | string | yes | Your plugin's semver. Informational. |
plugin_version | string | yes | Contract lock. Must equal "gbrain-plugin-v1". |
subagents | string | no | Subdir name (default subagents). Escape-attempts are rejected. |
description | string | no | Informational; appears in load/collision warnings. |
Subagent definition files
Plain markdown with YAML frontmatter. The body is the system prompt. The frontmatter controls runtime behavior.
Recognized frontmatter fields:
| field | type | required | notes |
|---|---|---|---|
name | string | no | Subagent identifier used as --subagent-def. Defaults to the file basename. |
model | string | no | Anthropic model id. Defaults to the handler default (sonnet). |
max_turns | number | no | Cap on assistant turns. Defaults to 20. |
allowed_tools | string[] | no | Whitelist of tool names. Must subset the derived brain registry. Rejected on mismatch. |
Unknown frontmatter fields are preserved but ignored by the handler.
Caveats that will bite you
-
Plugin definitions can't change during a run. The loader reads the disk once at worker startup. Editing a subagent def doesn't re-take effect until you restart the worker. This is deliberate — live reloads would break crash-resumable replay.
-
~/.gbrain/audit/subagent-jobs-*.jsonlis local only. If your worker runs on a different host than thegbrain agent logscaller, the CLI won't see heartbeats from that worker. Assume worker + CLI share a filesystem. -
Tool calls always run with
ctx.remote = true. Even on local CLI invocation. Tools that gate onremote=true(file_upload's strict confinement, put_page's namespace check) will apply. Good default; a subagent definition that wants local-filesystem reach beyond the brain can't have it. -
put_pagewrites are namespace-scoped. A subagent with id 42 can only write underwiki/agents/42/.... This is enforced both in the tool schema (the slug pattern shown to the model) AND server-side in theput_pageoperation (fail-closed ifviaSubagent=true). Don't try to route around it; you'll getpermission_denied.
Example: a downstream-OpenClaw plugin
~/your-openclaw/
└── gbrain-plugin/
├── gbrain.plugin.json
└── subagents/
├── meeting-ingestion.md
├── signal-detector.md
└── daily-task-prep.md
~/your-openclaw/gbrain-plugin/gbrain.plugin.json:
{
"name": "your-openclaw",
"version": "2026.4.20",
"plugin_version": "gbrain-plugin-v1",
"description": "Your OpenClaw's personal-brain subagents"
}
Environment:
export GBRAIN_PLUGIN_PATH="$HOME/your-openclaw/gbrain-plugin"
Then your OpenClaw calls gbrain agent run --subagent-def meeting-ingestion --fanout-by transcript ... and its definitions load automatically.