# Providers

> The one method a provider implements, the messages and events it handles, and how -p NAME finds it.


A provider connects elelem to a model. It is any Ruby object with a `fetch` method, registered under a name with `config.provider`. You pick one with `-p NAME`.

## The interface

```ruby
def fetch(messages, tools = [], &block)
```

That is the whole contract. elelem calls `fetch` once per round trip to the model, passes the conversation and the tool definitions, and reads events from the block. The return value is ignored.

### `messages`

An array of hashes with symbol keys. The first is always the system prompt.

```ruby
[
  { role: "system", content: "You are a terminal coding agent.\n\n..." },
  { role: "user", content: "what is in README.md?" },
  { role: "assistant", content: "", tool_calls: [{ id: "toolu_01", name: "read", arguments: { "path" => "README.md" } }] },
  { role: "tool", tool_call_id: "toolu_01", content: "{\"content\":\"# My project\",\"path\":\"README.md\",\"ok\":true}" }
]
```

| Role | Keys |
|---|---|
| `system` | `content` |
| `user` | `content` |
| `assistant` | `content`, and `tool_calls` when the model asked for tools earlier in this turn |
| `tool` | `tool_call_id`, `content` (the tool result as a JSON string) |

Each entry in `tool_calls` is `{ id:, name:, arguments: }`, in the same shape your provider emitted. Your provider translates all of this into whatever its API expects. `elelem-anthropic`, for example, pulls the system message out into Anthropic's `system` field.

### `tools`

An array of tool definitions in the OpenAI function format:

```ruby
[
  {
    type: "function",
    function: {
      name: "read",
      description: "Read file",
      parameters: { type: "object", properties: { path: { type: "string" } }, required: ["path"] }
    }
  }
]
```

### Events

Call the block with a hash. The `type` is a string.

| Event | Keys | What elelem does |
|---|---|---|
| `saying` | `text` | Appends to the reply. |
| `thinking` | `text` | Prints it dimmed. Not saved. |
| `doing` | `id`, `name`, `arguments` | Records a tool call to run after `fetch` returns. |

Any other `type` is ignored. Stream `saying` and `thinking` as the tokens arrive; elelem prints the reply once `fetch` returns. Emit `doing` once per complete tool call, with `arguments` already parsed into a hash with string keys, as `JSON.parse` returns it.

When `fetch` returns, elelem runs the tool calls in the order you emitted them. If there were none, the turn is over.

### Errors

Raise. elelem catches `StandardError` and `ScriptError` around `fetch`, prints the message, and ends the turn with the reply `Error: <message>`. Do not retry or swallow errors inside the provider unless the API calls for it.

## A complete provider

Save this as `.agents/plugins/echo.rb`:

```ruby
class EchoProvider
  def initialize(prefix:)
    @prefix = prefix
  end

  def fetch(messages, tools = [], &block)
    last = messages.reverse.find { |m| m[:role] == "user" }
    block.call(type: "saying", text: "#{@prefix}#{last[:content]}")
  end
end

Elelem.configure do |config|
  config.provider(:echo) do |model: nil, **|
    EchoProvider.new(prefix: model ? "#{model}: " : "")
  end
end
```

```bash
elelem ask -p echo --model parrot "hello there"
```

```text
parrot: hello there
```

## The factory

```ruby
config.provider(:name) { |model: nil, **| ... }
```

The block is a factory: it returns a new provider. elelem calls it every time it builds a provider, with no caching. The keyword arguments are the provider options. Today there is one, `model:`, set by `--model`. Accept `**` as well, so new options do not break your factory.

A factory that takes no arguments also works. It ignores `--model`. `elelem-ggml`, `elelem-candle`, and `elelem-llama2` work this way and read their model from environment variables only.

Registering a second factory under the same name replaces the first.

## How `-p NAME` resolves

1. elelem loads plugin files from `~/.agents/plugins/` and `.agents/plugins/`, once.
2. It looks for a provider registered as `NAME`. Names are compared as strings, so `:echo` and `"echo"` are the same.
3. If there is none, it runs `require "elelem/NAME"`, which loads the gem `elelem-NAME`, and looks again.
4. If the require fails, you get:

   ```text
   elelem: unknown provider: "NAME". Available: stub, echo. Run: gem install elelem-NAME
   ```

5. If the gem loads but registers no provider called `NAME`, you get `elelem-NAME did not register a provider named "NAME"`.
6. It calls the factory with the provider options.

Two things follow from step 3:

- A gem is found automatically only under its own name. `elelem-anthropic` also registers `vertex`, but `-p vertex` requires `elelem/vertex`, which does not exist. Load the gem first with a one-line plugin file, `~/.agents/plugins/anthropic.rb`:

  ```ruby
  require "elelem/anthropic"
  ```

- Any `LoadError` during that require, including one from a missing dependency inside the gem, is reported as `unknown provider`. If a provider you installed shows up as unknown, try `ruby -e 'require "elelem/NAME"'` to see the real error.

## Known providers

| `-p` name | Gem | Honors `--model` |
|---|---|---|
| `stub` | `elelem` (core) | No |
| `anthropic` | `elelem-anthropic` | Yes |
| `vertex` | `elelem-anthropic` (load it first, as above) | Yes |
| `openai` | `elelem-openai` | Yes |
| `ollama` | `elelem-ollama` | Yes |
| `llama` | `elelem-llama` | Yes, a path to a `.gguf` file |
| `ggml` | `elelem-ggml` | No |
| `candle` | `elelem-candle` | No |
| `llama2` | `elelem-llama2` | No |

Each provider's settings are on its plugin page and in [Environment variables](/docs/environment/).

## Switching provider in a session

`elelem-builtins` adds `/provider`. With no argument it lists registered providers. With a name, it builds that provider and swaps it into the running session. The conversation is kept. `--model` is not passed on a switch, so the new provider uses its default model.

In your own code, set it on the session:

```ruby
session.provider = Elelem::Config.build_provider("ollama", model: "gpt-oss:latest")
```


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

