Guides

Write a tool plugin

Add a tool the model can call, in one Ruby file, then package it as a gem.

Beginner 15 minutes elelemelelem-builtins

At the end of this guide the model will have a new tool, wc, that counts the lines, words, and bytes in a file. You will write it as one Ruby file in your project, reload it without restarting, and then package it as a gem named elelem-wc that any project can use.

Before you start #

  • elelem and elelem-builtins installed, and a provider that works. See your first session.
  • A project directory to work in.

1. Create the plugin file #

elelem loads every *.rb file in two directories when it starts: ~/.agents/plugins/ for plugins you want everywhere, and .agents/plugins/ in the current directory for one project. A plugin is any file that calls Elelem.configure.

mkdir -p .agents/plugins

Create .agents/plugins/wc.rb:

Elelem.configure do |config|
  config.setup(:wc) do |agent|
    agent.toolbox.add("wc",
                      description: "Count lines, words, and bytes in a file",
                      params: { path: { type: "string", description: "Path to the file" } },
                      required: ["path"]) do |args|
      path = File.expand_path(args["path"])
      next { error: "not found: #{args["path"]}" } unless File.file?(path)

      text = File.read(path)
      { path: args["path"], lines: text.lines.count, words: text.split.count, bytes: text.bytesize }
    end
  end
end

What each part does:

  • config.setup(:wc) registers a setup block. elelem runs it once per session and passes the session as agent. The name identifies the block; registering the same name again replaces it.
  • agent.toolbox.add adds the tool. The description is what the model reads when it decides whether to call the tool, so write it for the model.
  • params is the JSON Schema properties of the tool’s input, and required lists the keys the model must send. elelem validates every call against this schema before your code runs. A call without path never reaches the block; the model gets back object at root is missing required properties: path.
  • The block receives the arguments as a hash with string keys, as parsed from the model’s JSON. Use args["path"], not args[:path].
  • The block returns a hash. elelem adds ok: true and sends it to the model as JSON. Return a hash with an :error key to report a failure; elelem marks it ok: false. Use next, not return, to leave the block early.

If the block raises, elelem catches it and sends the model the error message, so one broken tool does not end the session.

2. Check that elelem sees it #

elelem chat -p ollama
> /tools wc
## wc

Count lines, words, and bytes in a file

### Parameters

- `path` (string, required) - Path to the file

Ask for it:

> how long is the README? use the wc tool.
+ wc({"path" => "README.md"})

If the file has a syntax error, elelem prints elelem: failed to load plugin /full/path/to/.agents/plugins/wc.rb: ... and carries on without it.

3. Show the result with an after hook #

The model sees the tool’s result. You only see the call line. An after hook runs after the tool and can print something for you. Add this inside the setup block, after toolbox.add:

    agent.toolbox.after("wc") do |_args, result|
      next if result[:error]

      agent.output.say "  #{result[:lines]} lines, #{result[:words]} words, #{result[:bytes]} bytes"
    end

The hook gets the arguments and the hash your tool returned. It runs only when the tool ran: not when validation failed, and not when the block raised. agent.output.say prints a line in the terminal. It does not change what the model sees.

There is also agent.toolbox.before("wc") { |args| ... }, which runs before the tool. Leave out the tool name to hook every tool: agent.toolbox.after { |args, result, tool_name:| ... }.

4. Reload without restarting #

> /reload

/reload clears the registered setup blocks, re-reads every file in ~/.agents/plugins/ and .agents/plugins/, and runs their setup blocks again. Ask for a word count and the new line appears under the call.

Note: In elelem 0.13.0, /reload also drops the tools and commands that come from gems, including read, write, execute, and /reload itself, because gems are not re-read. Your plugin’s tools stay. Use /reload to iterate on a plugin, then restart elelem to get everything back.

5. Package it as a gem #

A plugin file works for one machine. To share it, make a gem named elelem-wc, with the plugin at lib/elelem/wc.rb.

mkdir -p elelem-wc/lib/elelem

Create elelem-wc/lib/elelem/wc.rb: require "elelem", then the same Elelem.configure block as before.

require "elelem"

Elelem.configure do |config|
  config.setup(:wc) do |agent|
    # the toolbox.add and toolbox.after calls from steps 1 and 3
  end
end

Create elelem-wc/elelem-wc.gemspec:

Gem::Specification.new do |spec|
  spec.name = "elelem-wc"
  spec.version = "0.1.0"
  spec.authors = ["Your Name"]
  spec.summary = "A wc tool for elelem."
  spec.license = "MIT"
  spec.required_ruby_version = ">= 4.0.0"
  spec.files = ["lib/elelem/wc.rb"]
  spec.require_paths = ["lib"]
  spec.add_dependency "elelem", "~> 0.13"
end

Build and install it:

cd elelem-wc
gem build elelem-wc.gemspec
gem install ./elelem-wc-0.1.0.gem

gem push elelem-wc-0.1.0.gem publishes it to RubyGems.

6. Load the gem #

elelem does not load tool gems on its own. The only gem it requires at startup is elelem-builtins, and provider gems load when you pick them with -p. Load yours from a plugin file. Replace the project file with a one-line loader in your home directory, so every project gets the tool:

rm .agents/plugins/wc.rb
mkdir -p ~/.agents/plugins
echo 'require "elelem/wc"' > ~/.agents/plugins/wc.rb

Start elelem and run /tools. wc is in the list. elelem-tools, elelem-mcp, and elelem-skills load the same way.

What you built #

A tool with a validated schema, an after hook that reports to you, and the same code packaged as elelem-wc, loaded from one line in ~/.agents/plugins/.

Next steps #