How to Add New Actions to Embabel Agents Without Modifying Existing Code

You can add new actions to Embabel agents by creating a JAR that contains a class implementing the Tool interface annotated with @Action and registering it via the Java ServiceLoader mechanism in META-INF/services/com.embabel.agent.api.tool.Tool, which the ActionRegistry discovers at runtime without requiring any changes to the core agent codebase.

The Embabel agent framework (embabel/embabel-agent) is built on a modular, plugin-based architecture that separates core orchestration logic from executable capabilities. This design allows developers to extend agent functionality by packaging new actions as standalone dependencies rather than forking or patching the existing source tree.

Understanding the Embabel Agent Plugin Architecture

Embabel agents execute discrete operations called actions, which are discovered at runtime through the standard Java Service Provider Interface (SPI). The framework defines two critical contracts in the embabel-agent-api module:

The ActionRegistry class in embabel-agent-core/src/main/java/com/embabel/agent/core/ActionRegistry.java bootstraps the agent's capability set by scanning the classpath for implementations of Tool using ServiceLoader, enabling true zero-touch extensibility.

Step-by-Step Guide to Adding Custom Actions

Create a New Maven Module

Start by creating a new Maven project that depends only on the Embabel agent API. This ensures your extension remains lightweight and decoupled from core implementation details.

<dependency>
    <groupId>com.embabel</groupId>
    <artifactId>embabel-agent-api</artifactId>
    <version>0.9.0</version>
</dependency>

Implement the Tool Interface and Annotate with @Action

Create a public class with a no-arg constructor that implements Tool and is annotated with @Action. The annotation marks the class for discovery, while the interface enforces the run(ActionContext ctx, ...) contract.

package com.mycompany.embabel.actions;

import com.embabel.agent.api.annotation.Action;
import com.embabel.agent.api.tool.Tool;
import com.embabel.agent.api.tool.ActionContext;

@Action(name = "summarizeText", description = "Summarizes a block of text using the LLM")
public class SummarizeTextAction implements Tool {

    @Override
    public Object run(ActionContext ctx, String text) {
        return ctx.llm().generateSummary(text);
    }
}

This implementation references the ActionContext to access shared services like the LLM client, demonstrating how actions integrate with the agent's execution environment.

Register the Action via Java SPI

To make the implementation discoverable, create a service provider configuration file at src/main/resources/META-INF/services/com.embabel.agent.api.tool.Tool. List the fully qualified class name of your implementation:


com.mycompany.embabel.actions.SummarizeTextAction

The ActionRegistry loads these entries during agent initialization, instantiating each class via reflection and adding it to the available toolset.

Deploy the Extension JAR

Build your module into a JAR file and place it on the application's classpath. When the Embabel agent starts, it automatically detects and registers your new action without requiring configuration changes or restarts of the core system.

Complete Code Example: Creating a Custom Action

The following example demonstrates a concrete action that extracts email addresses from text, showing the full implementation pattern including the service descriptor.

Action Implementation

package com.mycompany.embabel.actions;

import com.embabel.agent.api.annotation.Action;
import com.embabel.agent.api.tool.Tool;
import com.embabel.agent.api.tool.ActionContext;
import java.util.regex.Pattern;
import java.util.List;
import java.util.stream.Collectors;

@Action(name = "extractEmails", description = "Extracts all email addresses from a block of text")
public class ExtractEmailsAction implements Tool {

    @Override
    public Object run(ActionContext ctx, String text) {
        return Pattern.compile("[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-z]{2,}")
                .matcher(text)
                .results()
                .map(match -> match.group())
                .collect(Collectors.toList());
    }
}

Service Provider Descriptor

File: src/main/resources/META-INF/services/com.embabel.agent.api.tool.Tool


com.mycompany.embabel.actions.ExtractEmailsAction

Maven Configuration

<project>
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.mycompany.embabel</groupId>
    <artifactId>embabel-custom-actions</artifactId>
    <version>1.0.0</version>

    <dependencies>
        <dependency>
            <groupId>com.embabel</groupId>
            <artifactId>embabel-agent-api</artifactId>
            <version>0.9.0</version>
        </dependency>
    </dependencies>
</project>

How the ActionRegistry Discovers New Actions at Runtime

The ActionRegistry class leverages Java's ServiceLoader utility to maintain a registry of available tools. During the agent's boot sequence, the registry iterates over all providers found in META-INF/services/com.embabel.agent.api.tool.Tool across the classpath. For each discovered class, it instantiates the object using the default constructor and extracts the @Action metadata to build the action's public-facing interface. This architecture ensures that adding new capabilities is purely additive—deploying a new JAR extends the agent's functionality without invalidating existing configurations or requiring recompilation of the core framework.

Summary

  • Implement the Tool interface from embabel-agent-api and annotate your class with @Action to define new capabilities.
  • Register via Java SPI by adding your class name to META-INF/services/com.embabel.agent.api.tool.Tool.
  • Package as a standalone JAR with a dependency on embabel-agent-api and deploy to the classpath.
  • Rely on ActionRegistry in embabel-agent-core to automatically discover and load your actions at startup without modifying existing source code.

Frequently Asked Questions

Do I need to modify the embabel-agent-core source code to add actions?

No. The framework is designed specifically to avoid source modifications. You create a separate JAR containing your action implementation and service descriptor, and the ActionRegistry discovers it automatically through the classpath scanning mechanism.

What interface must my custom action implement?

Your class must implement com.embabel.agent.api.tool.Tool, which defines the run(ActionContext ctx, ...) method. This interface is located in embabel-agent-api/src/main/java/com/embabel/agent/api/tool/Tool.java.

Can I add multiple actions in a single JAR?

Yes. You can package any number of action implementations into one JAR file. Simply list each fully qualified class name on a separate line in the META-INF/services/com.embabel.agent.api.tool.Tool file, or provide multiple service files if you prefer modular organization within the same archive.

How does the agent handle action name collisions?

The ActionRegistry uses the name attribute from the @Action annotation as the unique identifier. If two implementations register with identical names, the later discovery order typically overrides the earlier registration, though production deployments should enforce unique naming conventions across extension modules to avoid ambiguity.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →