Building
Plugin SDK
What a plugin is, what it can change, and how the pieces fit.
A plugin is one package that changes what your agents can do. It can give them a new tool, rewrite a message before the model sees it, install a whole crew of specialist agents, connect them to a chat app, replace the web-search engine, give them a voice, or put a 3D face on them. You write TypeScript or JavaScript against a typed SDK, upload the package, and anyone who installs it gets exactly what the code does — nothing more.
This page is the mental model. The Quickstart gets a plugin running in about ten minutes.
One package, many capabilities
A single package can carry any mix of these. Each is a capability: something the platform discovers in your package at publish time and switches on when someone installs it.
| Capability | What it does for the user | How you declare it | Guide |
|---|---|---|---|
| Tool | A function the model can call, next to the built-in tools | tools.<name> = { description, parameters, handler } |
Tools |
| Middleware | Rewrites or vetoes a message, prompt, todo, timer, SSH command or scheduled run as it happens — 14 hooks | middleware.<hook> = (context, tools) => … |
Middleware |
| Lifecycle event | Runs when your plugin is installed, updated or removed, or a chat starts, is cleared or deleted — 6 events | events.<event> = (context) => … |
Lifecycle events |
| Agent crew | Creates and heals a team of real agents your plugin owns | host.agents.* + manifest.agents |
Agent crews |
| Command | A slash command such as /research <question> |
commands/<name>.md |
Commands and skills |
| Skill | Instructions (and files) an agent loads only when it needs them | skills/<key>/skill.md |
Commands and skills |
| Channel | A two-way chat app: people message the agent there, it answers there | channel.{receive, send, typing, fetchAttachment} |
Channels |
| Live connection | A socket AgentParley holds open for you for days (Discord, IRC-style protocols) | connection.{open, receive, timer} |
Live connections |
| Chat model provider | Your vendor's models become selectable agent brains | modelProvider.complete |
Model providers |
| Decision model provider | Fast typed yes/no, choose and score answers | modelProvider.{noul, choose, score} |
Model providers |
| Voice provider | Text-to-speech voices for companions | modelProvider.{speak, voices} |
Voice and speech-to-text |
| Speech-to-text provider | Turns what the user says to a companion into text | modelProvider.transcribe |
Voice and speech-to-text |
| Web search / web fetch provider | Replaces the engine behind the built-in web_search / web_fetch tools |
web.search, web.fetch |
Web providers |
| Compaction provider | Replaces how long conversations are summarized | compaction.summarize |
Compaction |
| Companion motion set | How a 3D companion moves — idle, talk, listen, think, gestures | companion-motions/<key>/motions.json |
Companion packs |
| Companion model | A 3D avatar (VRM or GLB) | companion-models/<key>/model.json |
Companion packs |
| Companion character | A ready-made face: look + motions + suggested personality and voice | companion-characters/<key>/character.json |
Companion packs |
Companion packs, commands and skills are data: no code runs for them. Everything else is code you write.
Code declares itself
There is no capability list to maintain. The platform gives your code a set of empty objects — middleware,
events, tools, compaction, web, modelProvider, channel, connection — and you assign handlers to them.
At publish time the platform runs your bundle once, reads back what you assigned, and turns each assignment into a
capability.
// plugins/caveman/index.ts — the whole plugin
const GUIDANCE =
"\n\n## Caveman mode (output-brevity skill)\n" +
"Rewrite your responses to be dramatically shorter while keeping technical accuracy.\n" +
// …
"Say the least that fully answers. Example: 'New ref each render. Wrap in useMemo.' not a paragraph.";
globalThis.middleware.systemPrompt = async (context) => {
return context.systemPrompt + GUIDANCE;
};
That one assignment is a complete, publishable plugin: one middleware capability, zero permissions.
Two rules follow from "the platform reads what you assigned":
- Register at the top level. An assignment made inside a handler, or computed from a
host.*call, is invisible at publish time and is never switched on. See Package format. - Everything else goes in
globalThis.manifest— the hosts you call, the settings form users fill in, the agents you will create, the provider you register as. See the Manifest reference.
Your code talks to the world through host
Plugin code runs in a sandbox with no network, no filesystem and no process access. Everything goes through one
object, host: host.fetch for HTTP (only to hosts you declared), host.settings for the installer's settings and
secrets, host.session to read and act on the current chat, host.agents to manage your crew, host.llm to call a
model, host.crypto to check webhook signatures, and so on. The Host API reference lists every
member.
A member is only there if you were granted it. You don't write a permission list. At publish time the platform
reads your code, finds every host.x.y(...) call, and derives the permissions from them. The installer sees that
list before installing. At run time, a member you weren't granted is simply absent. See
Permissions.
Lifecycle of a plugin
- Write — in the console's plugin editor (one script) or locally (any number of
.ts/.jsfiles). - Publish — upload a zip or press Publish. The platform bundles your code, discovers the capabilities, derives the permissions, and stores an immutable version. A version is private to your account until you choose List in Browse.
- Install — the user sees what your plugin can reach and what it adds (for example "Adds 7 agents"), then installs. Installing switches on every capability in the version.
- Configure — the user fills in your settings form; secrets are write-only and encrypted.
- Use — middleware and lifecycle hooks run for the whole account; tools, commands and skills appear on every agent (each agent's Access tab can narrow them); providers are picked under AI Providers → Add provider or Settings → Engines; channels under Channels → Connect; companion characters in the Companions gallery.
- Update — publish a new version. Installs on the Latest policy move to it automatically — unless it asks for more access (a new permission, a new host, a new agent). Then it waits for the user to accept it.
What makes this different
- A real sandbox, per call. Every invocation runs in a fresh V8 isolate with a memory cap and a wall-clock limit, and is thrown away afterwards. Secrets and the platform's API token never enter it. See Sandbox model.
- Permissions you can't under-declare. They come from your code. A call the scanner can't see doesn't get secret access — it fails, because the member was never bound.
- Updates can't sneak in new access. A version that asks for more waits for the owner's OK.
- Breadth. Tools, 14 middleware hooks (including SSH command rewriting and output redaction, and vetoing scheduled missions), lifecycle events, crews, commands, skills, channels, held sockets, chat / decision / voice / speech-to-text providers, web search, web fetch, compaction and 3D companion assets — all in one package format.
- Plugins can ship a team. A crew plugin creates real agents — each with its own model, persona and instructions — keeps them healthy, and shapes how they work together. See Agent crews.
- Official plugins use the same SDK. Discord, Brave Search and Wiki Web are ordinary plugins built on exactly what this documentation describes.