# How Embabel Dynamic Replanning Works: Architecture and Implementation

> Discover how Embabel's dynamic replanning works. Learn about its architecture, implementation, and how agents handle invalidated plans for seamless execution with OpenTelemetry.

- Repository: [Embabel/embabel-agent](https://github.com/embabel/embabel-agent)
- Tags: architecture
- Published: 2026-08-08

---

**Embabel's dynamic replanning system allows agents to discard invalidated plans during execution by emitting a `ReplanRequestedEvent`, which triggers the core planner to clear state and generate a fresh plan while recording telemetry via OpenTelemetry spans and metrics.**

Embabel dynamic replanning enables AI agents to adapt when tools determine that the current plan is no longer viable. In the `embabel/embabel-agent` repository, this mechanism is implemented through a sophisticated event-driven architecture that bridges tool execution with planning telemetry. The system provides developers with multiple integration points—from high-level wrappers to low-level exceptions—while maintaining full observability through structured metadata.

## The Plan-Execute-Observe Loop

Embabel agents operate on a continuous **plan-execute-observe** cycle. During the execution phase, each tool evaluates whether the current plan remains valid given the context and results. When a tool detects that the plan has become obsolete—whether due to changed conditions, policy violations, or unexpected outputs—it signals the need for regeneration.

This signal propagates through the agent core as a `ReplanRequestedEvent` defined in [`ReplanRequestedEvent.java`](https://github.com/embabel/embabel-agent/blob/main/ReplanRequestedEvent.java) (or via a thrown `ReplanRequestedException` from [`ReplanRequestedException.java`](https://github.com/embabel/embabel-agent/blob/main/ReplanRequestedException.java)), carrying a human-readable **reason** string that explains why the current plan must be abandoned. The core planner receives this event, immediately clears the existing `Plan` object, resets the iteration counter, and initiates a new planning cycle with the updated context.

## Triggering a Replan

Developers can initiate dynamic replanning through three distinct mechanisms, each suited to different architectural needs.

### Using Tool Wrappers

The `Tool` utility class in [`Tool.java`](https://github.com/embabel/embabel-agent/blob/main/Tool.java) provides three static helper methods that transform standard tools into replan-capable components. These wrappers return a `ReplanDecision` object—defined in [`ReplanDecision.java`](https://github.com/embabel/embabel-agent/blob/main/ReplanDecision.java)—that carries an optional reason string back to the agent core:

- `Tool.replanAlways(delegate)` – Forces a replan after every tool execution, regardless of outcome.
- `Tool.replanWhen(delegate, predicate)` – Conditionally triggers replanning only when the supplied predicate evaluates to `true` against the tool's output.
- `Tool.replanAndAdd(delegate, valueComputer)` – Forces a replan while attaching a computed value to the decision metadata.

```java
// Force replanning after every execution
Tool<String> myTool = Tool.replanAlways(delegateTool);

// Conditional replanning based on output analysis
Tool<String> conditional = Tool.replanWhen(delegateTool,
    result -> result.contains("retry"));

// Replan with custom diagnostic information
Tool<String> withReason = Tool.replanAndAdd(delegateTool,
    output -> new ReplanDecision("Stuck on \"%s\"".formatted(output)));

```

### Throwing ReplanRequestedException

For imperative control flow, code within skills or tool implementations can throw `ReplanRequestedException` with a descriptive reason. The agent's process-event listener intercepts this exception and converts it into a `ReplanRequestedEvent` for consistent handling.

```java
public void someSkill() {
    if (needsNewPlan()) {
        throw new ReplanRequestedException("policy violation – re‑plan");
    }
}

```

### Automatic Detection

Certain internal Embabel components invoke `Tool.replanWhen` automatically. For example, tool loops that exhaust their configured retry limits trigger replanning without requiring explicit user code, ensuring the agent can recover from transient failures autonomously.

## The Replanning Flow

When a replan is triggered, the system executes a precise six-step sequence:

1. **Tool Execution** – The tool runs within the `embabel.tool_loop` span context.
2. **Decision Generation** – The tool returns a `ReplanDecision` or throws `ReplanRequestedException`.
3. **Event Emission** – The agent core creates a `ReplanRequestedEvent` containing the reason string.
4. **Observer Handling** – `EmbabelSpanEventListener` receives the event and starts a new `embabel.replan` span with `gen_ai.operation.name = "replan"`, adding the `embabel.replan.reason` attribute when present.
5. **Metric Update** – `EmbabelMetricsEventListener` increments the `embabel.planning.replanning.total` counter, tagged with the agent name.
6. **Planner Restart** – The planning component discards the old plan, resets the iteration counter to 1, and invokes the planner to generate a fresh plan. The attribute `embabel.plan.is_replanning` resets to `"false"` until another request occurs.

## Observability and Telemetry

Embabel dynamic replanning exposes rich telemetry through OpenTelemetry-compatible spans and metrics, enabling debugging of planning behavior in production.

The `EmbabelSpanEventListener` in [`EmbabelSpanEventListener.java`](https://github.com/embabel/embabel-agent/blob/main/EmbabelSpanEventListener.java) tracks two critical metadata attributes:

| Attribute | Type | Description |
|-----------|------|-------------|
| `embabel.plan.is_replanning` | Boolean string | Set to `"true"` when the current planning iteration was triggered by a replan request |
| `embabel.replan.reason` | String | Free-form text supplied by the tool that caused the replan |

Additionally, the `EmbabelMetricsEventListener` in [`EmbabelMetricsEventListener.java`](https://github.com/embabel/embabel-agent/blob/main/EmbabelMetricsEventListener.java) maintains a counter metric `embabel.planning.replanning.total` that increments for every replan request, enabling aggregation of replanning frequency by agent name.

```java
// Reading replanning telemetry in a custom span listener
public void onProcessEvent(AgentProcessEvent event) {
    if (event instanceof ReplanRequestedEvent rep) {
        Observation observation = Observation.create(SpanAttributes.EMBABEL_REPLAN, "replan")
            .lowCardinalityKeyValue(SpanAttributes.GEN_AI_OPERATION_NAME, "replan")
            .lowCardinalityKeyValue(SpanAttributes.EMBABEL_REPLAN_REASON, rep.getReason())
            .start();
        // ... observation handling ...
    }
}

```

## Summary

- Embabel dynamic replanning operates through a **plan-execute-observe** loop where tools signal plan invalidation via `ReplanRequestedEvent` or `ReplanRequestedException`.
- Developers trigger replans using **tool wrappers** (`replanAlways`, `replanWhen`, `replanAndAdd`), **manual exceptions**, or rely on **automatic detection** for retry exhaustion.
- The `EmbabelSpanEventListener` records replanning context through attributes `embabel.plan.is_replanning` and `embabel.replan.reason`.
- The counter metric `embabel.planning.replanning.total` provides quantitative insights into replanning frequency per agent.
- All replanning events carry human-readable reasons, enabling transparent debugging of why agents abandon plans during execution.

## Frequently Asked Questions

### What is the difference between ReplanRequestedEvent and ReplanRequestedException?

`ReplanRequestedException` is a throwable exception designed for imperative code paths within skills or tools, while `ReplanRequestedEvent` is the internal event object that propagates through the agent's event bus. When code throws `ReplanRequestedException`, the agent's exception handler catches it and converts it into a `ReplanRequestedEvent` for uniform processing by listeners like `EmbabelSpanEventListener`.

### How can I track how many times an agent replans?

The `embabel.planning.replanning.total` counter metric increments for every replan request and is tagged with the agent name. You can query this metric in your observability backend to analyze replanning frequency. Additionally, the `embabel.plan.is_replanning` span attribute marks individual planning iterations that were triggered by replan requests.

### Can I provide a custom reason when requesting a replan?

Yes. When using `Tool.replanAndAdd()`, the `ReplanDecision` constructor accepts a reason string that appears in the `embabel.replan.reason` span attribute. Similarly, `ReplanRequestedException` accepts a reason parameter in its constructor. This text helps operators understand why specific tools determined the plan was no longer viable.

### Which Embabel component actually generates the new plan?

While the `ReplanRequestedEvent` signals the need for regeneration, the core **planner** component (integrated with the agent's planning cycle) generates the new plan. When the event is received, the planner discards the existing `Plan` object, resets the iteration counter to 1, and invokes the LLM or planning algorithm to create a fresh strategy based on current context.