Plugin docs
Amber is built to be extended. Its own providers, core tools, status bar and registry console are all plugins, the same API described here. There is no privileged internal path.
Two tiers
Where a plugin runs is an isolation decision, not a size decision. Pick deliberately:
| Core plugin | External plugin | |
|---|---|---|
| Language | C++17, compiled in | Any executable |
| Isolation | Same process; a crash takes amber down | Separate process; a crash cannot |
| Can contribute | Tools, providers, prompt blocks, status segments, panels, wallets, allowances, event subscriptions | Tools only |
| Distribution | plugins/<id>/, compiled in | ~/.config/amber/plugins/<id>/ |
| Cost per call | none (direct call) | IPC round trip |
Which tier?
flowchart TD
start(["Which plugin tier?"]) --> isolation{"Needs arbitrary or<br/>other-language code,<br/>or crash containment?"}
isolation -->|yes| external["External plugin<br/>separate process · JSON-RPC<br/>tools only"]
isolation -->|no| contributes{"Contributes anything<br/>beyond a tool?"}
contributes -->|yes| core["Core plugin<br/>C++17 · compiled in"]
contributes -->|no| wire{"OpenAI-wire<br/>compatible endpoint?"}
wire -->|yes| file["No code needed<br/>a provider file is enough"]
wire -->|no| coreQuickstart
A complete, compiled, tested template ships in the repository atplugins/_template/ (exercised bytests/example_plugin_test.cpp). The shape:
class HelloPlugin : public agent::IPlugin {
public:
std::string id() const override { return "hello"; }
std::string version() const override { return "1.0.0"; }
std::string name() const override { return "Hello"; }
bool initialize(const agent::PluginContext&) override { return true; }
void shutdown() override {}
std::vector<std::unique_ptr<agent::Capability>> capabilities() override;
};Then declare what it contributes:
std::vector<std::unique_ptr<agent::Capability>> HelloPlugin::capabilities() {
std::vector<std::unique_ptr<agent::Capability>> caps;
caps.push_back(std::make_unique<agent::ToolCapability>(
"greet", [](agent::PluginServices&) {
std::vector<std::unique_ptr<agent::Tool>> tools;
tools.push_back(std::make_unique<GreetTool>());
return tools; // empty list = declined, not an error
}));
return caps;
}- Create
plugins/<id>/with your plugin class. - Declare capabilities in
capabilities(). - Register it in
make_bundled_plugins()(lib/plugins_bundled.cpp) and add the object toMakefile.in. - Write a hermetic test (see Testing).
You declare; the runtime installs. Every contribution goes through the ledger, which is what makes disable total. A plugin that registers outside this path cannot be disabled cleanly, that is the one rule the framework is built around.
Slash command quickstart
To expose logic through the slash engine, contribute aCommandCapability: declare the JSON mapping, expose the callback, and the host binds it. The bundled plugins/hello/ is this exact example, type /hello greet amber and it returnsHello, amber!
agent::CommandSpec spec;
spec.root = "hello";
spec.help = "Say hello";
spec.subtree = agent::json::parse(R"({"greet": {"help": "Greet someone"}})");
spec.handlers["greet"] = [](const std::string& arg) {
return "Hello, " + (arg.empty() ? std::string("world") : arg) + "!";
};
caps.push_back(std::make_unique<agent::CommandCapability>(std::move(spec)));The handler returns the text to display and the host prints it, so the plugin stays free of any UI dependency. SeeCapabilities → Command for the full contract.
In this reference
- Plugin anatomy,
IPlugin,PluginContext,PluginServices, the ledger, lifecycle - Capabilities, every capability kind, with code
- Events, the typed event bus, subscribe and intercept, full catalogue
- Providers, add an LLM provider, with wallet and allowance
- External plugins, the JSON-RPC protocol and manifest
- Testing & review, hermetic tests and the PR checklist
Availability
What ships today, at a glance. Thedeveloper guideis the canonical source; this table mirrors it.
| Capability | Status |
|---|---|
| External tool plugin (JSON-RPC) | ✅ Available |
| Tool contribution (incl. core tools) | ✅ Available |
| Typed event subscription | ✅ Available |
| Prompt block contribution | ✅ Available |
| Provider contribution | ✅ Available |
| Status segment contribution | ✅ Available |
| Panel contribution | ✅ Available |
| Wallet / allowance readout | ✅ Available |
| Command contribution (slash namespace) | ✅ Available |
| Host services to the user (ask/choose/confirm) | Not available |
| Per-plugin settings, log sinks | Not available |
PluginLoaded / PluginUnloaded events | Declared, never published |
Source
- Repository
- docs/spec/plugins/, design record, developer guide, external protocol
- lib/plugins_bundled.cpp, the shipped set
Found a gap in the framework? That is abug report or feature request, and a good one.