mirror of
https://github.com/garrytan/gbrain.git
synced 2026-07-30 03:12:32 +00:00
Plumbing that makes Wintermute (and future downstream agents) day-1 usable on v0.15. Host repos drop a `gbrain.plugin.json` + `subagents/` directory somewhere, set GBRAIN_PLUGIN_PATH (colon-separated like \$PATH), and their custom subagent defs load at worker startup. Path policy is strict: absolute paths only. Relative, ~-prefixed, and URL-style (https://, file://) all rejected with warnings — the user controls where plugins live. Non-existent paths and files (not dirs) are warned and skipped so a typo doesn't crash worker startup. Collision policy: left-wins. If two plugins ship a subagent with the same name, the first one in GBRAIN_PLUGIN_PATH keeps it and the other gets a warning naming both sources. Deterministic + debuggable. Trust policy: plugins ship subagent defs ONLY. Cannot declare new tools, cannot extend the brain allow-list, cannot override safety flags. The subagent def's `allowed_tools:` frontmatter MUST subset the derived registry — validation happens at load time (worker startup), not at dispatch time, so a typo in a skill gives a loud startup error instead of silently "tool never fires at 3am." Manifest `plugin_version: "gbrain-plugin-v1"` locks the contract. Unknown versions rejected. `subagents` field escape attempts (`../../../etc` etc) rejected. gray-matter handles the markdown frontmatter parse — subagent defs don't conform to the page schema, so we don't use parseMarkdown. docs/guides/plugin-authors.md is the Wintermute-facing walkthrough. Covers the minimum viable plugin shape, the three policies, the frontmatter fields, known caveats (audit JSONL is local-only, tool calls always run remote=true, put_page is namespace-scoped). 22 unit tests pin path rejection, missing/invalid manifest, unsupported version, escape-attempt, basename fallback for missing frontmatter.name, allowed_tools round-trip, unknown-tool rejection with validAgentToolNames, empty env, multi-path, collision warning with left-wins, trimmed paths, manifest-rejection as warning. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
164 lines
5.9 KiB
Markdown
164 lines
5.9 KiB
Markdown
# Plugin authors guide (v0.15)
|
|
|
|
`gbrain` discovers subagent definitions from outside this repo via
|
|
`GBRAIN_PLUGIN_PATH`. If you maintain a downstream agent (Wintermute,
|
|
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.
|
|
|
|
## Minimum viable plugin
|
|
|
|
```
|
|
/path/to/my-plugin/
|
|
├── gbrain.plugin.json
|
|
└── subagents/
|
|
└── my-summarizer.md
|
|
```
|
|
|
|
`gbrain.plugin.json`:
|
|
|
|
```json
|
|
{
|
|
"name": "my-plugin",
|
|
"version": "1.0.0",
|
|
"plugin_version": "gbrain-plugin-v1"
|
|
}
|
|
```
|
|
|
|
`subagents/my-summarizer.md`:
|
|
|
|
```markdown
|
|
---
|
|
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
|
|
|
|
```bash
|
|
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`.
|
|
|
|
```bash
|
|
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 in v0.15:
|
|
|
|
- You **cannot** declare new tools.
|
|
- You **cannot** extend the brain tool allow-list.
|
|
- You **cannot** override any `agentSafe` or 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.
|
|
|
|
v0.16+ may open up plugin-declared tools with a separate contract. Don't
|
|
expect it.
|
|
|
|
## `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"` for v0.15. |
|
|
| `subagents` | string | no | Subdir name (default `subagents`). Escape-attempts are rejected. |
|
|
| `description` | string | no | Shown in future `gbrain plugin list`. |
|
|
|
|
## 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. v0.16
|
|
may consume more of them.
|
|
|
|
## Caveats that will bite you
|
|
|
|
1. **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.
|
|
|
|
2. **`~/.gbrain/audit/subagent-jobs-*.jsonl` is local only.** If your
|
|
worker runs on a different host than the `gbrain agent logs` caller,
|
|
the CLI won't see heartbeats from that worker. v0.16 will unify this;
|
|
for now assume worker + CLI share a filesystem.
|
|
|
|
3. **Tool calls always run with `ctx.remote = true`.** Even on local CLI
|
|
invocation. Tools that gate on `remote=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.
|
|
|
|
4. **`put_page` writes are namespace-scoped.** A subagent with id 42 can
|
|
only write under `wiki/agents/42/...`. This is enforced both in the
|
|
tool schema (the slug pattern shown to the model) AND server-side in
|
|
the `put_page` operation (fail-closed if `viaSubagent=true`). Don't
|
|
try to route around it; you'll get `permission_denied`.
|
|
|
|
## Example: a Wintermute-flavored plugin
|
|
|
|
```
|
|
~/wintermute/
|
|
└── gbrain-plugin/
|
|
├── gbrain.plugin.json
|
|
└── subagents/
|
|
├── meeting-ingestion.md
|
|
├── signal-detector.md
|
|
└── daily-task-prep.md
|
|
```
|
|
|
|
`~/wintermute/gbrain-plugin/gbrain.plugin.json`:
|
|
|
|
```json
|
|
{
|
|
"name": "wintermute",
|
|
"version": "2026.4.20",
|
|
"plugin_version": "gbrain-plugin-v1",
|
|
"description": "Wintermute's personal-brain subagents"
|
|
}
|
|
```
|
|
|
|
Environment:
|
|
|
|
```bash
|
|
export GBRAIN_PLUGIN_PATH="$HOME/wintermute/gbrain-plugin"
|
|
```
|
|
|
|
Then Wintermute calls `gbrain agent run --subagent-def meeting-ingestion
|
|
--fanout-by transcript ...` and its definitions load automatically.
|