# Plugins

> What a plugin is, where elelem loads plugins from and in what order, and how to reload them.


A plugin is any Ruby file or gem that calls `Elelem.configure`. Providers, tools, commands, and the terminal renderer are all plugins. Core has no other extension point.

This is the open/closed principle: the core is closed for modification and open for extension. You add a model, a tool, or an interface by writing a plugin, never by changing elelem itself. The project follows the same rule; in 0.11.0 every provider, tool, and interface moved out of the core into its own gem.

```ruby
# ~/.agents/plugins/hello.rb
Elelem.configure do |config|
  config.setup(:hello) do |session|
    session.commands.register("hello", description: "Say hello") do |name|
      session.output.say("hello, #{name || "world"}")
    end
  end
end
```

Restart elelem and type `/hello`.

## `Elelem.configure`

```ruby
Elelem.configure { |config| ... }
```

The block receives the registration object. It has four methods, and each returns `config`, so calls can be chained:

| Method | Registers |
|---|---|
| `config.provider(name) { \|**options\| ... }` | A provider factory. See [Providers](/docs/providers/). |
| `config.setup(name) { \|session\| ... }` | A setup block that adds tools, hooks, or commands. |
| `config.output { ... }` | A factory for the output object. |
| `config.input { \|commands:\| ... }` | A factory for the input object. |

Names are stored as strings. `Elelem.configure` only records blocks; nothing runs until elelem builds a session.

## Setup blocks

A setup block receives the `Session`: the toolbox, commands, output, input, conversation, and provider. It runs once each time a session is built, after the provider, input, and output exist.

```ruby
config.setup(:audit) do |session|
  session.toolbox.after { |args, result, tool_name:| File.write("audit.log", "#{tool_name} #{args}\n", mode: "a") }
end
```

Setup blocks run in the order their names were first registered. Registering a block under a name that already exists replaces the block and keeps its place in the order. That is how you override a built-in: a plugin file with `config.setup(:execute) { ... }` replaces the `execute` setup from `elelem-builtins`.

An exception inside a setup block is not caught. At startup it stops elelem with `elelem: <message>`.

## Where plugins come from

### Plugin files

elelem loads every `*.rb` file in these directories, in this order:

1. `~/.agents/plugins/`: yours, for every project.
2. `.agents/plugins/`: the project's, relative to the current directory.

Within a directory, files load in alphabetical order. Subdirectories are ignored, and elelem does not search parent directories for `.agents/plugins/`.

Files load once per process, the first time elelem needs a provider, an input, an output, or the setup blocks. They load with Ruby's `load`, so `/reload` can run them again.

> **Note:** A project's `.agents/plugins/` is Ruby code, and elelem runs it when you start elelem in that directory. Read it before running elelem in a repository you did not write.

### Gems

A gem is a plugin when it calls `Elelem.configure` as it loads. elelem requires gems in three cases:

- `exe/elelem` requires `elelem/builtins` if `elelem-builtins` is installed.
- `-p NAME` requires `elelem/NAME` if no provider called `NAME` is registered yet.
- Your plugin file requires it.

Every other gem needs a one-line plugin file. To load `elelem-tools`:

```ruby
# ~/.agents/plugins/tools.rb
require "elelem/tools"
```

The same works for `elelem-mcp`, `elelem-skills`, and any other plugin gem.

### Load order

When you run `elelem chat`, registration happens in this order:

1. Core registers the `stub` provider.
2. `exe/elelem` requires `elelem-builtins`, which registers its setups and its terminal output.
3. Plugin files load: `~/.agents/plugins/*.rb`, then `.agents/plugins/*.rb`. Gems they require register as they load.
4. If `-p NAME` is still unknown, `elelem/NAME` is required.

Setup blocks then run in that same order of registration.

## Input and output factories

Replace how elelem reads and prints by registering factories:

```ruby
Elelem.configure do |config|
  config.output { MyOutput.new }
  config.input { |commands:| MyInput.new(commands: commands) }
end
```

The output factory is called with no arguments. The input factory is called with `commands:`, the session's slash commands, for tab completion. The last factory registered wins. `elelem-builtins` registers an output that renders Markdown with `glow`; `elelem-ratatui` registers both.

An output object must respond to:

```ruby
say(text, as: nil)          # print a line; as: :markdown or :error
print(text)                 # print without a newline
thinking(text)              # the model's thinking
doing(name, args, state: "+") # a tool call
waiting                     # called before each provider request
display_file(path, fallback: nil)
```

An input object must respond to `ask(prompt)`, returning the line or `nil` to end the session, and `interactive?`. Subclassing `Elelem::Output` gives you every method; override the ones you need.

Factories apply to `elelem chat` only. `elelem ask` always uses the core `Output` and a `NullInput` that never prompts.

## Reloading

`/reload`, from `elelem-builtins`, calls `Elelem::Config.reload(session)`:

1. Forget every registered setup block.
2. Remove every tool, hook, and command from the session.
3. Load the plugin files again.
4. Run the setup blocks registered by that load.

Provider factories and input and output factories are kept. The conversation is kept.

> **Note:** Gems are not loaded again, because Ruby's `require` runs a file only once. After `/reload`, only setups from plugin files are back. Tools and commands from gems, including `/reload` itself from `elelem-builtins`, are gone until you restart elelem. `/reload` is for iterating on a plugin file.

## When a plugin fails to load

Each plugin file loads inside a rescue for `StandardError` and `ScriptError`, so a syntax error or a missing gem skips that file and the rest still load. elelem reports it with `warn`:

```text
elelem: failed to load plugin /home/you/.agents/plugins/broken.rb: ...
```

> **Note:** `exe/elelem` starts Ruby with `-W0`, which silences `warn`. When you run the `elelem` command, a broken plugin file is skipped without a message. If a tool or command is missing, check the file with `ruby -c ~/.agents/plugins/NAME.rb`.

## Writing a plugin gem

Name the gem `elelem-NAME` and put the entry point at `lib/elelem/NAME.rb`, so `require "elelem/NAME"` loads it. For a provider, register it under `NAME` too, and `-p NAME` will find the gem without a plugin file.

```ruby
# lib/elelem/wc.rb
require "elelem"

Elelem.configure do |config|
  config.setup(:wc) do |session|
    session.toolbox.add("wc", description: "Count lines in a file",
                              params: { path: { type: "string" } }, required: ["path"]) do |args|
      { lines: File.foreach(args["path"]).count }
    end
  end
end
```

The full list of methods is in the [Plugin API](/docs/plugin-api/).


Canonical: https://elelem.sh/docs/plugins/

