How to Implement Custom Condition Planners in Embabel for Advanced Pre‑/Post‑Conditions
You implement the Planner interface from com.embabel.agent.api.common.Planner, expose it as a named Spring bean, and set the planner attribute on your @Agent annotation to that bean name to override Embabel's default planning algorithms with custom condition-aware logic.
Embabel's agent framework separates action planning from condition evaluation, allowing you to inject sophisticated pre‑ and post‑condition logic into the planning pipeline. According to the embabel/embabel-agent source code, the architecture revolves around the Condition, Planner, and PlannerFactory abstractions defined in the API and SPI modules. By implementing a custom planner, you can perform complex condition checks—such as external API calls or probabilistic evaluations—before the planner commits to an action sequence.
Understanding Embabel's Planning Architecture
Embabel evaluates conditions as named predicates that return TRUE, FALSE, or UNKNOWN via ConditionDetermination. These conditions carry evaluation costs and support logical operators, as described in the repository's architectural documentation at README-appendix.md lines 15‑22.
The planner itself is a strategy that transforms a WorldState and Goal into a Plan (a sequence of Actions). Built‑in implementations are selected through the PlannerType enum—such as GOAP or UTILITY—but the framework allows you to substitute these with custom implementations. The PlannerFactory (com.embabel.agent.spi.support.DefaultPlannerFactory) acts as the Spring‑boot‑compatible resolver that instantiates the correct strategy based on the @Agent definition.
Implementing a Custom Condition Planner
To integrate a custom planner that handles advanced pre‑ and post‑conditions, follow these three integration steps.
Create the Planner Implementation
Your class must implement com.embabel.agent.api.common.Planner, which defines the contract:
public interface Planner {
Plan plan(ProcessContext ctx, Goal goal);
}
Inside plan(), retrieve the current WorldState from ProcessContext and evaluate any custom conditions before constructing the action sequence. You can invoke condition methods reflectively or use the framework's ConditionReader utility.
Register as a Spring Bean
Annotate your implementation with @Component and provide a unique bean name. Use @ConditionalOnMissingBean to ensure your custom implementation only overrides defaults when explicitly provided:
@Component("myConditionPlanner")
@ConditionalOnMissingBean(name = "myConditionPlanner")
public class MyConditionPlanner implements Planner {
// implementation
}
This registration allows DefaultPlannerFactory to locate your planner by name during agent initialization.
Reference from an Agent
The @Agent annotation accepts either a PlannerType enum value or a plain string in its planner attribute. Provide your bean name as a string to bypass the built‑in enum lookup:
@Agent(
description = "Fulfillment agent with custom conditions",
planner = "myConditionPlanner" // Resolves to the Spring bean above
)
public class OrderAgent {
// agent definition
}
Complete Working Example
Below is a minimal, end‑to‑end implementation demonstrating custom condition evaluation within a planner.
Custom Planner Implementation:
package com.mycompany.embabel.planner;
import com.embabel.agent.api.common.Planner;
import com.embabel.agent.core.ProcessContext;
import com.embabel.agent.model.Plan;
import com.embabel.agent.model.Goal;
import org.springframework.stereotype.Component;
/**
* A planner that evaluates complex pre‑conditions before selecting actions.
*/
@Component("myConditionPlanner")
@ConditionalOnMissingBean(name = "myConditionPlanner")
public class MyConditionPlanner implements Planner {
@Override
public Plan plan(ProcessContext ctx, Goal goal) {
// Retrieve current world state from the blackboard
var worldState = ctx.getWorldState();
// Evaluate custom condition using the framework's ConditionReader
var conditionReader = ctx.getConditionReader();
boolean ready = conditionReader.evaluate("orderIsReady");
if (!ready) {
// Handle unmet pre‑conditions (e.g., return empty plan or replan)
return Plan.empty();
}
// Construct plan with validated conditions
return Plan.builder()
.addAction(new PackAction())
.addAction(new ShipAction())
.cost(42)
.build();
}
}
Agent Definition with Custom Conditions:
package com.mycompany.embabel.agent;
import com.embabel.agent.api.annotation.Agent;
import com.embabel.agent.api.annotation.Condition;
import com.embabel.agent.core.Action;
@Agent(
description = "Order‑fulfilment agent using custom condition planner",
planner = "myConditionPlanner"
)
public class OrderAgent {
@Condition(cost = 0.2)
public boolean orderIsReady() {
// Expensive check: database query, external API, etc.
return inventoryService.checkAvailability();
}
@Action(name = "pack")
public void packOrder() {
// Execution logic
}
@Action(name = "ship")
public void shipOrder() {
// Execution logic
}
}
Key Source Components Reference
When implementing custom condition planners, reference these specific source locations in the embabel/embabel-agent repository:
com.embabel.agent.api.annotation.Condition– Annotation definition located atembabel-agent-api/src/main/java/com/embabel/agent/api/annotation/Condition.java. Marks methods that return boolean condition states.com.embabel.agent.api.common.Planner– Interface contract definingplan(ProcessContext, Goal). Found in the API module.com.embabel.agent.spi.support.DefaultPlannerFactory– Spring factory that resolves planner beans by name orPlannerTypeenum. Handles the lookup logic for theplannerattribute.com.embabel.agent.api.common.PlannerType– Enum defining built‑in planners (GOAP,UTILITY). Custom bean names bypass this enum.README-appendix.md– Lines 15‑22 contain the architectural description ofConditionDeterminationand the three‑valued logic (TRUE/FALSE/UNKNOWN) used by the evaluation engine.
Summary
- Implement
com.embabel.agent.api.common.Plannerto create custom planning logic that evaluates pre‑ and post‑conditions. - Register your implementation as a named Spring bean using
@Componentand optionally@ConditionalOnMissingBeanto control override behavior. - Reference the bean name in the
@Agentannotation'splannerattribute to routing planning through your custom implementation. - Use
ProcessContext.getConditionReader()to evaluate@Conditionannotated methods within your planner, respecting the cost and determination logic defined in the framework.
Frequently Asked Questions
What is the difference between using PlannerType and a custom planner bean?
PlannerType is an enum (GOAP, UTILITY) handled by DefaultPlannerFactory to instantiate built‑in algorithms. A custom planner bean is referenced by string name in the @Agent annotation, causing the factory to perform a Spring bean lookup instead of enum resolution. This allows you to inject arbitrary planning logic while retaining the framework's lifecycle management.
How do I access condition evaluation results inside my custom planner?
Inject ProcessContext into the plan() method and call ctx.getConditionReader().evaluate(String methodName). This utility reads @Condition annotations from the agent class, executes the corresponding methods, and returns the boolean result while respecting the condition's defined cost and determination state.
Can I combine custom condition logic with existing GOAP or utility algorithms?
Yes. Your custom planner can delegate to the built‑in planners by autowiring the default implementations or extending their classes. For example, you might evaluate expensive pre‑conditions first, then invoke the standard GOAP planner from com.embabel.agent.core.planner.GoapPlanner only if the custom conditions return TRUE, effectively wrapping the default algorithm with your validation layer.
Where does Embabel store condition state during the planning phase?
The WorldState object retrieved via ProcessContext.getWorldState() acts as the blackboard during planning. Conditions derived from @Condition methods are evaluated against this state, and their determinations (TRUE/FALSE/UNKNOWN) are cached in the planning context to avoid redundant computation of high‑cost conditions. The architectural details are documented in README-appendix.md lines 15‑22.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →