# Plugin API

> Every public method a plugin author touches, with signatures, defaults, and short examples.


Signatures are copied from elelem 0.13.0. Arguments shown with `=` or `:` and a value are optional, with that default. For the ideas behind these methods, read [Plugins](/docs/plugins/) and [Tools and commands](/docs/tools-and-commands/) first.

## Elelem

### `Elelem.configure`

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

Yields the registration object, described below. Call it at the top level of a plugin file or gem.

### `Elelem.start`

```ruby
Elelem.start(provider: "stub", toolbox: Toolbox.new, provider_options: {}, template: nil, system_prompt: nil)
```

Builds a session and runs the REPL until input ends. This is what `elelem chat` calls.

### `Elelem.ask`

```ruby
Elelem.ask(prompt, provider: "stub", toolbox: Toolbox.new, output: NullOutput.new, provider_options: {},
           template: nil, system_prompt: nil)
```

Builds a session with an input that never prompts, runs one turn, and returns the reply as a string.

```ruby
require "elelem"

require "elelem/builtins"

reply = Elelem.ask("Summarize README.md in one line.", provider: "anthropic")
```

> **Note:** `NullOutput` only silences `say`. Thinking text and anything a tool sends to `output.print`, such as the output of `execute`, still go to stdout. Pass `output: Elelem::Output.new(stream: $stderr)` to move them.

### `Elelem.build`

```ruby
Elelem.build(provider: "stub", toolbox: Toolbox.new, input: nil, output: nil, provider_options: {},
             template: nil, system_prompt: nil)
```

Returns a `Session` with setup blocks applied, without running anything. `nil` input or output means the registered factory, or the core `Input` or `Output`.

| Keyword | Meaning |
|---|---|
| `provider:` | Provider name, resolved as in [Providers](/docs/providers/#how--p-name-resolves). |
| `toolbox:` | Starting toolbox. Setup blocks add to it. |
| `provider_options:` | Keyword arguments for the provider factory, such as `{ model: "..." }`. |
| `template:` | ERB template replacing the default system prompt. |
| `system_prompt:` | Role text placed before the rendered template. Not rendered as ERB. |

### `Elelem.logger`

```ruby
Elelem.logger # => Logger
```

A `Logger` on stderr. Its level comes from `ELELEM_LOG_LEVEL` (`debug`, `info`, `warn`, `error`, `fatal`), default `warn`.

### `Elelem::VERSION`

The core version string, `"0.13.0"`.

## Registration

The object `Elelem.configure` yields. Every registering method returns the registration, so calls chain.

```ruby
config.provider(name) { |model: nil, **| provider }   # name: String or Symbol
config.setup(name) { |session| ... }                  # name: String or Symbol
config.output { output }
config.input { |commands:| input }
```

| Method | Notes |
|---|---|
| `provider(name, &factory)` | The factory runs on every build and receives the provider options. Same name replaces. |
| `setup(name, &block)` | Runs against each new session, in registration order. Same name replaces in place. |
| `output(&factory)` | Called with no arguments. Last one wins. |
| `input(&factory)` | Called with `commands:`. Last one wins. |
| `providers` | Hash of name to factory. |
| `setups` | Hash of name to block. |
| `factories` | Hash with `:input` and `:output` keys when registered. |
| `clear_setups!` | Removes every setup. Used by reload. |

## Elelem::Config

Module functions on the shared registry.

```ruby
Elelem::Config.build_provider(name, **options)
Elelem::Config.build_session(provider, toolbox: Toolbox.new, input: nil, output: nil, provider_options: {},
                             template: nil, system_prompt: nil)
Elelem::Config.apply(session)
Elelem::Config.reload(session)
Elelem::Config.names
```

| Method | Returns |
|---|---|
| `build_provider` | A new provider. Loads plugin files first, and the `elelem-NAME` gem if needed. |
| `build_session` | A `Session` with setups applied. `Elelem.build` calls this. |
| `apply` | Runs every setup block against the session. |
| `reload` | Clears setups, tools, hooks, and commands, reloads plugin files, and applies again. See [Reloading](/docs/plugins/#reloading). |
| `names` | Registered provider names, as strings. |

```ruby
session.provider = Elelem::Config.build_provider("openai", model: "gpt-4o")
```

## Session

What setup blocks receive.

```ruby
Elelem::Session.new(agent, input: NullInput.new, commands: Commands.new)
```

| Method | Returns |
|---|---|
| `agent` | The `Agent`. |
| `input` | The input object. |
| `commands` | The `Commands`. |
| `provider`, `provider=` | The agent's provider. Assign to swap it. |
| `toolbox` | The agent's `Toolbox`. |
| `output` | The agent's output object. |
| `conversation` | The agent's `Transcript`. |
| `context` | The messages the provider would get now, system prompt first. |
| `turn(prompt)` | Runs a turn and returns the reply. |

## Agent

```ruby
Elelem::Agent.new(provider, toolbox: Toolbox.new, output: NullOutput.new, template: nil, system_prompt: nil)
```

| Method | Returns |
|---|---|
| `turn(prompt)` | The reply string. Adds the prompt and the reply to the conversation. |
| `context` | The current messages, system prompt first. |
| `conversation` | The `Transcript`. |
| `provider`, `provider=` | The provider. |
| `toolbox`, `output` | As given. |

A second agent that shares the provider, tools, and output but has its own conversation and template is a sub-agent. `elelem-tools` builds its `task` tool this way:

```ruby
sub = Elelem::Agent.new(session.provider, toolbox: session.toolbox, output: session.output,
                        template: "Research agent. Search, analyze, report. Be concise.")
sub.turn("Find where plugins are loaded.")
```

## Transcript

`session.conversation`. The conversation plus the system prompt.

```ruby
Elelem::Transcript.new(template: nil, system_prompt: nil)
```

| Method | Notes |
|---|---|
| `add(role:, content:, **extra)` | `role` must be `"user"`, `"assistant"`, or `"tool"`, or it raises `ArgumentError`. Keys with `nil` values are dropped. |
| `last` | The last message hash, or `nil`. |
| `clear!` | Removes every message. The system prompt stays. |
| `to_a(extra = [])` | System message, then the conversation, then `extra`. Renders the template each call. |

```ruby
session.conversation.add(role: "user", content: "Context: the build is red on main.")
```

`Elelem::Conversation` is the plain list underneath, with `add`, `last`, `length`, `clear!`, and `to_a(system_prompt: nil)`.

## Toolbox

`session.toolbox`.

```ruby
toolbox.add(name, description:, params: {}, required: [], aliases: [], &handler)
toolbox.before(tool_name = :*) { |args| ... }                  # global: { |args, tool_name:| }
toolbox.after(tool_name = :*) { |args, result| ... }           # global: { |args, result, tool_name:| }
toolbox.run(name, args)        # => Elelem::Result
toolbox.tool_for(name)         # => Tool, following aliases, or a stand-in for unknown names
toolbox.to_a                   # => tool definitions for the provider
toolbox.tools                  # => { "name" => Tool }
toolbox.aliases                # => { "alias" => "name" }
toolbox.hooks                  # => { before: { name => [block] }, after: { name => [block] } }
toolbox.clear!                 # removes every tool, alias, and hook
```

```ruby
session.toolbox.add("now", description: "Current time in UTC") { |_args| { time: Time.now.utc.to_s } }
session.toolbox.after("write") { |args, result| system("rubocop", "-a", args["path"]) unless result[:error] }
```

Handlers get string keys and must return a hash. Details in [Tools and commands](/docs/tools-and-commands/#tools).

## Tool and Schema

`toolbox.add` builds these for you. You need them only to inspect a tool or validate arguments yourself.

```ruby
Elelem::Tool.new(name, description:, schema: Schema.new, aliases: [], &handler)
Elelem::Schema.new(params: {}, required: [])
```

| Method | Returns |
|---|---|
| `tool.name`, `tool.description`, `tool.aliases` | As given. |
| `tool.call(args)` | The handler's return value. No validation or hooks. |
| `tool.validate(args)` | An array of error strings, empty when valid. |
| `tool.to_h` | `{ type: "function", function: { name:, description:, parameters: } }` |
| `schema.to_h` | `{ type: "object", properties: params, required: required }` |
| `schema.validate(args)` | An array of error strings. `nil` args are treated as `{}`. |

## Result

What `toolbox.run` returns.

```ruby
Elelem::Result.success(payload)   # payload.merge(ok: true)
Elelem::Result.failure(payload)   # payload.merge(ok: false)
```

| Method | Returns |
|---|---|
| `ok?` | `true` when `:ok` is truthy. |
| `error?` | `true` when `:error` is present. |
| `error` | The `:error` value. |
| `[]`, `each`, `key?`, `merge`, `to_h`, `to_json` | Delegated to the payload hash. |

```ruby
result = session.toolbox.run("execute", { "command" => "bin/test" })
session.output.say("tests failed", as: :error) unless result[:exit_status].to_i.zero?
```

## Commands

`session.commands`.

```ruby
commands.register(name, description: "", completions: nil) { |args| ... }
commands.run(name, args = nil)          # => true if found, false if not
commands.names                          # => ["/name", ...]
commands.each { |slash_name, description| ... }
commands.completions_for(name, partial = "")
commands.clear!
```

`args` is the text after the command name, or `nil`. `completions:` is an array or a lambda returning one. `Commands` includes `Enumerable`.

```ruby
session.commands.register("model", description: "Show the provider class") do
  session.output.say(session.provider.class.name)
end
```

## Output

The core terminal output. Subclass it to make your own.

```ruby
Elelem::Output.new(stream: $stdout)
```

| Method | Behavior in core |
|---|---|
| `say(text, as: nil)` | Prints `text` and a newline. `as: :markdown` wraps it in a code fence. `as: :error` prefixes `error: `. Blank text prints nothing. |
| `print(text)` | Prints without a newline. Blank text prints nothing. |
| `thinking(text)` | Prints dimmed italic text. |
| `doing(name, args, state: "+")` | Prints `+ name(args)`, with the name in cyan. |
| `waiting` | Does nothing. Called before each provider request. |
| `display_file(path, fallback: nil)` | Says `fallback`, or `path` when there is none. |

`Elelem::NullOutput` is an `Output` whose `say` does nothing. The other methods still write to its stream.

`elelem-builtins` registers its own output with the same methods. It renders Markdown with `glow`, shows files with `bat`, and prints dots while waiting.

## Input

```ruby
Elelem::Input.new(commands: [])
```

| Method | Returns |
|---|---|
| `ask(prompt)` | The line you typed, stripped, or `nil` at end of input. Uses Reline, with history. |
| `interactive?` | `true` when stdin is a terminal. |

`Elelem::NullInput` returns `nil` from `ask` and `false` from `interactive?`. Check `session.input.interactive?` before asking a question, as the confirmation hook in `elelem-tools` does.

## SystemPrompt

```ruby
Elelem::SystemPrompt.new(template = nil)
```

| Member | Notes |
|---|---|
| `render` | The template rendered with ERB, `trim_mode: "-"`. |
| `template`, `template=` | The ERB source. Defaults to `DEFAULT`. |
| `DEFAULT` | The contents of `lib/elelem/prompts/default.erb`. |
| `USER_AGENTS_MD` | `~/.agents/AGENTS.md` as a `Pathname`. |

Inside a template, `agents_md` returns the global and project `AGENTS.md` joined by a blank line, or `nil`.

## StubProvider

```ruby
Elelem::StubProvider.new.fetch(messages, tools = [], &block)
```

The built-in `stub` provider. Useful in tests: it calls the first tool once and then stops. See [Installation](/docs/installation/#with-no-provider-the-stub).

## Repl and CLI

For building your own executable.

```ruby
Elelem::Repl.new(session).run
Elelem::CLI.new(ARGV, provider: "stub").run
```

`CLI.new` takes the default provider, which `-p` overrides. Every provider executable is three lines around it:

```ruby
#!/usr/bin/env ruby
require "elelem/cli"

Signal.trap("INT") { exit 1 }

Elelem::CLI.new(ARGV, provider: "anthropic").run
```

## Plugins and Registry

Rarely needed outside core.

```ruby
Elelem::Plugins.new(load_paths: Elelem::Plugins::LOAD_PATHS).load!(force: false)
Elelem::Registry.new(plugins: Plugins.new)
```

`Plugins::LOAD_PATHS` is `["~/.agents/plugins", ".agents/plugins"]`. `load!` runs once unless `force: true`. `Registry` is the class behind `Elelem::Config`; `Elelem::Config.default` returns the shared instance.


Canonical: https://elelem.sh/docs/plugin-api/

