How to Define Actions Using the @Action Annotation in Embabel

Use the @Action annotation to mark Java or Kotlin methods as executable operations that the Embabel planner invokes to achieve goals, with optional metadata controlling cost, triggers, and retry behavior.

The embabel/embabel-agent framework implements a goal-driven planning system where agents assemble workflows from discrete, callable units. You define these units using the @Action annotation, which exposes methods to the planner along with metadata describing their behavior, constraints, and execution characteristics.

Understanding the @Action Annotation

Located in embabel-agent-api/src/main/java/com/embabel/agent/api/annotation/Action.java, the @Action annotation transforms ordinary methods into action catalog entries. When the framework scans a class, every method marked with @Action becomes available for plan generation. During execution, the planner evaluates these actions based on their metadata to determine the optimal sequence for achieving a declared goal.

Core Attributes of @Action

The annotation supports several attributes that control how the planner treats each action:

  • description — Human-readable text explaining the action's purpose. The planner uses this to select sensible actions during workflow generation.
  • cost — Static numerical value representing the action's expense. The planner prefers cheaper actions when multiple alternatives exist.
  • costMethod — Name of a method within the same class that returns a dynamic double cost at runtime, enabling context-sensitive pricing.
  • trigger — Event class (extending Event) that automatically fires the action when the specified event occurs.
  • actionRetryPolicy — Enum value (FIRE_ONCE, FIRE_ALWAYS, etc.) controlling retry behavior when the action fails.
  • actionRetryPolicyExpression — SpEL expression that resolves to a retry policy at runtime for dynamic control.
  • pre — Array of method names that must return true before the action executes, serving as pre-conditions.
  • clearBlackboard — Boolean flag that, when true, clears the agent's blackboard after execution, forcing a fresh planning cycle.

Implementing Actions in Java

Basic Action Definition

Define a simple action by annotating a method with a description:

package com.embabel.example;

import com.embabel.agent.api.annotation.Action;
import java.util.concurrent.ThreadLocalRandom;

public class RandomNumberGenerator {

    @Action(description = "Pick a random number between 1 and 100")
    public int pickRandomNumber() {
        return ThreadLocalRandom.current().nextInt(1, 101);
    }
}

Cost-Aware Actions

Control planning decisions by specifying static or dynamic costs. The TestStarNewsFinder.java file demonstrates both approaches:

// Static cost discourages use unless necessary
@Action(cost = 500.0, description = "Send a bulk email")
public void sendBulkEmail(String subject, String body) {
    // email-sending logic
}

// Dynamic cost computed at runtime via method reference
@Action(costMethod = "dynamicCost",
        description = "Perform a data-intensive operation")
public void heavyComputation() {
    // computation logic
}

private double dynamicCost() {
    // Return cost based on current system load
    return SystemCpuLoad.getCurrentLoad() * 1000;
}

Event-Triggered Actions

Use the trigger attribute to automatically execute actions when specific events occur:

import com.embabel.agent.api.annotation.Action;

@Action(trigger = IncomingMessage.class,
        description = "Echo the received message")
public String echoMessage(IncomingMessage msg) {
    return "You said: " + msg.getText();
}

Retry Policies and Pre-conditions

Configure failure handling and execution guards using attributes tested in RetryActionAnnotationJavaTest.java:

// Attempt only once regardless of failure
@Action(actionRetryPolicy = ActionRetryPolicy.FIRE_ONCE,
        description = "Call external API with one retry")
public String callExternalApi() {
    // API call logic
}

// Require authentication before execution
@Action(pre = {"userAuthenticated"},
        description = "Access user profile")
public UserProfile getProfile() {
    // retrieval logic
}

// Clear blackboard after execution to reset state
@Action(clearBlackboard = true,
        description = "Reset the workflow state")
public void resetWorkflow() {
    // reset logic
}

Connecting Actions to Goals

Actions become powerful when combined with the @AchievesGoal annotation from embabel-agent-api/src/main/java/com/embabel/agent/api/annotation/AchievesGoal.java. The MealPreparationStages.java test fixture demonstrates how the planner assembles multiple actions to satisfy a goal:

package com.embabel.example;

import com.embabel.agent.api.annotation.Action;
import com.embabel.agent.api.annotation.AchievesGoal;

public class MealPreparation {

    @Action(description = "Choose a cook from the user input")
    public String chooseCook(String userChoice) {
        return userChoice;
    }

    @Action(description = "Take a food order from user input")
    public Order takeOrder(String userInput) {
        return new Order(userInput);
    }

    @Action(description = "Prepare the final meal")
    public Meal prepareMeal(String cook, Order order) {
        return new Meal(cook, order);
    }

    @AchievesGoal
    public Meal prepareDinner(String userChoice, String userInput) {
        // The planner generates a workflow using the actions above
        // rather than executing this method body directly
        return null;
    }
}

In this pattern, the @AchievesGoal method declares the target state, while the planner evaluates the three @Action methods—considering their descriptions, costs, and pre-conditions—to build an execution sequence: choose cook → take order → prepare meal.

Summary

  • The @Action annotation in Action.java marks methods as callable units in the Embabel planning framework.
  • Metadata attributes like cost, costMethod, trigger, and actionRetryPolicy control how the planner selects and executes actions.
  • Use pre conditions to guard execution and clearBlackboard to reset agent state between planning cycles.
  • Combine @Action methods with @AchievesGoal methods to create goal-driven workflows where the planner automatically orchestrates execution sequences.

Frequently Asked Questions

What is the difference between @Action and @AchievesGoal?

@Action marks methods that perform specific operations and may be used by the planner, while @AchievesGoal marks a method that represents the target state the planner attempts to reach. The planner analyzes @AchievesGoal methods to determine which @Action methods to invoke in sequence.

How does the planner choose between multiple actions that could achieve the same goal?

The planner evaluates the description for semantic relevance and the cost or costMethod for efficiency. Actions with lower cost values are preferred when multiple alternatives exist, and descriptions help the planner determine which action best fits the current context.

Can I use @Action with Kotlin classes?

Yes, the annotation works with Kotlin. Since Embabel supports both Java and Kotlin, you can annotate Kotlin functions with @Action using the same syntax, and the framework will include them in the action catalog during component scanning.

How do dynamic costs work with the costMethod attribute?

The costMethod specifies the name of a parameterless method in the same class that returns a double. At planning time, the framework invokes this method to determine the current cost of the action, allowing costs to vary based on runtime conditions like system load, time of day, or external API pricing.

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 →