05-embabel-agent-orchestration

When to Reach for Embabel vs Plain Spring AI

The same refund-adjudication problem, solved three ways — a plain @Tool loop, a hand-rolled Plan-and-Execute pattern, and an Embabel agent — with a concrete decision framework for picking between them.

August 13, 2026
spring-aiembabeldecision-frameworkagent-architecture

Three Ways to Solve the Same Problem

This roadmap has now built the same kind of agent logic three separate times, each with a different tool:

  1. A plain @Tool loop (ToolCallingAdvisor, tool-calling guide) — the model decides which tools to call, in what order, entirely on its own.
  2. A hand-rolled Plan-and-Execute pattern (agentic-patterns guide) — your code asks for an upfront plan, then executes it step by step.
  3. An Embabel agent (building-agents guide, this phase) — you describe actions as typed methods; a GOAP planner computes the path.

None of these is strictly "better." Each is the right answer to a different shape of problem, and picking the wrong one shows up as either wasted complexity or an unmaintainable pile of if statements — the exact failure mode that opened this phase's first guide.


Side by Side

The same task — decide whether order 4521 qualifies for a refund, and respond accordingly — through all three:

java
// 1. Plain @Tool loop — the model orchestrates itself
String answer = chatClient.prompt()
    .user("Is order 4521 eligible for a refund, and if so how much?")
    .tools(new OrderTools(orderRepository), new CalculatorTools())
    .call()
    .content();
java
// 2. Hand-rolled Plan-and-Execute — your code owns the branching
Plan plan = chatClient.prompt().user(question).call().entity(Plan.class);
List<String> observations = new ArrayList<>();
for (PlanStep step : plan.steps()) {
    observations.add(executeToolByName(step.tool(), step.toolInput()));
}
// ... your code decides what happens with those observations next
java
// 3. Embabel — a planner owns the branching, driven by types
@Action
OrderLookup lookupOrder(ExtractedOrder extracted) { /* plain code, no LLM */ }
 
@AchievesGoal(description = "Explain the refund outcome")
@Action
RefundResponse explain(OrderLookup lookup, ExtractedOrder extracted, Ai ai) { /* ... */ }

The plain @Tool loop is the least code and the least control — you trust the model's own judgment about ordering and stopping. Plan-and-Execute gives you a visible, upfront plan at the cost of writing and maintaining the code that walks it. Embabel gives you both a visible plan and automatic ordering, at the cost of a new dependency, a new mental model (the Blackboard), and — as the last guide showed — real, if modest, setup investment.


A Decision Table, Not a Rule

SignalReach for
One or two tools, the model can reasonably pick and sequence them itselfPlain @Tool loop
You need to see or approve a plan before anything executes, but the steps are fairly predictableHand-rolled Plan-and-Execute
You need a structured, inspectable reasoning trace for audit or complianceHand-rolled ReAct
The branching logic itself has outgrown a maintainable if/else treeEmbabel
Steps need to dynamically replan when an earlier step's result changes what should happen nextEmbabel
The domain deserves real typed objects (OrderLookup, RefundResponse) rather than strings passed between promptsEmbabel
Different steps genuinely benefit from different models — a cheap local model for extraction, a stronger one for the customer-facing explanationEmbabel (.withLlm(...) per action)
A single, well-scoped judgment call, even a nuanced one (like SupportAgent's risk assessment)Either a single @Tool-equipped ChatClient call, or a single-action Embabel agent — genuinely close, and it's fine to start with the simpler option

Notice what's not on this list as a reason to reach for Embabel: "the task involves multiple steps." Guide two's RefundResearchAgent had three actions and could just as easily have been three sequential ChatClient calls, plain-code-and-all — the value showed up in the type-driven ordering and eventual extensibility, not in the step count itself. A three-step task with a genuinely fixed order rarely needs a planner; a three-step task where new business rules keep adding new possible paths increasingly does.

⚠️

The cost of choosing wrong runs in both directions. Reaching for Embabel on a single, well-defined tool call adds a dependency, a Java 21 requirement, and a new concept for every teammate who touches that code, for no planning benefit at all — over-engineering, not caution. Sticking with a hand-rolled if/else tree past the point this phase's opening guide described is the opposite mistake, and the one more likely to page someone at 2am.


Embabel Sits On Top of Spring AI, Not Instead of It

SupportAgent from the last guide is proof, not just a claim: its one @Action used Embabel's OperationContext and @Agent/@AchievesGoal annotations and Spring AI's own @Tool annotation on the Customer record, in the same method, without friction. Nothing in this phase replaced anything from the earlier phases of this roadmap — ChatClient, ChatMemory, QuestionAnswerAdvisor, MCP, the evaluators — all of it keeps working exactly as built. Embabel adds a planning layer above Spring AI's model abstraction; it was never a competing way to talk to a model.

A support endpoint doesn't have to be all-Embabel or all-plain-Spring-AI. A reasonable production shape: most endpoints stay plain ChatClient calls with tools, RAG, and memory — exactly what most of this roadmap builds — and the specific flows that have genuinely outgrown hand-written branching (the refund-adjudication logic that opened this phase) become Embabel agents, sitting in the same codebase, calling the same VectorStore and ChatMemory beans where it makes sense to.


What's Next

This phase covered the JVM-native planner built specifically on Spring AI. The next phase looks sideways instead of up the stack: LangChain4j, the other serious framework for building LLM applications on the JVM — where it overlaps with everything this roadmap has built so far, where it genuinely differs, and when reaching for it instead of (or alongside) Spring AI makes sense.

Frequently asked questions

Can I call an Embabel agent from inside a hand-rolled ReAct loop, or vice versa?

Nothing stops you architecturally — an Embabel agent is just Spring beans underneath, invokable via AgentPlatform from anywhere, including from inside a @Tool method called by a plain ChatClient loop. Whether that's a good idea is a case-by-case call; it's usually a sign that the sub-task being delegated to Embabel is exactly the kind of outgrown-branching problem this guide describes, even if the outer flow doesn't need a planner itself.

Does adopting Embabel for one agent mean adopting it everywhere in the app?

No — this roadmap's whole point in this guide is that it doesn't. Add the starter, write one @Agent for the flow that actually needs planning, and leave every other ChatClient-based endpoint untouched. There's no framework-wide switch to flip.

Is Embabel's GOAP planner going to make worse decisions than a model reasoning about the plan itself (like hand-rolled ReAct)?

They're not really solving the same problem. GOAP decides which of your predefined actions to run and in what order, deterministically, based on types and conditions you've defined — it's not "reasoning" about anything outside that action set. A ReAct loop's LLM is reasoning more freely, including potentially in ways you didn't anticipate, which is exactly the tradeoff: more flexibility, less predictability and auditability.

Given how fast Embabel is moving, is it production-ready?

That's a judgment call for your team and your risk tolerance, not something this roadmap can answer generically — treat the version and stability caveats in the last two guides as a genuine signal, evaluate it against your project's own tolerance for a fast-moving dependency, and prefer the hand-rolled patterns from the previous phase if that tolerance is low.