# Dynamic Replanning After Each Action in Embabel: Architecture and Implementation

> Discover Embabel's dynamic replanning architecture. Learn how agents adapt to failures and new info with plan-execute-observe feedback loops and explicit exception handling for robust task completion.

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

---

**Embabel implements a plan-execute-observe feedback loop where the agent evaluates plan viability after every tool execution, triggering dynamic replanning through explicit exceptions or predicate-based decisions to adapt to failures or new information.**

Dynamic replanning is a core capability of the Embabel agent framework that enables autonomous systems to recover from execution failures and adapt to changing conditions. This mechanism operates within the `embabel-agent` repository's execution engine, allowing agents to discard remaining actions and generate fresh strategies whenever the current plan becomes invalid. Understanding this replanning architecture is essential for building resilient agent workflows that can handle real-world unpredictability.

## The Execution Loop Architecture

Embabel's agent execution follows a strict **plan-execute-observe** cycle implemented in the core runtime. After the initial planning phase generates a sequence of actions, the `ToolLoop` iterates through each execution, evaluating whether the current trajectory remains valid before proceeding to the next step.

### Initial Planning and Action Execution

The `PlanningAgent` (or any concrete agent implementation) creates an initial plan consisting of a sequence of `Tool` or `Skill` invocations. The plan is represented as a list of `Action` objects that the `ToolLoop` dispatches sequentially. Each action executes within its own context, allowing the system to capture outputs and exceptions independently.

### Post-Action Observation

After an action finishes, the `EmbabelSpanEventListener` records a span and adds the attribute `embabel.plan.is_replanning` with a value of `false`. The listener also captures any `ReplanRequestedException` thrown by the tool, creating a traceable record of the execution outcome before the replanning decision occurs.

## Mechanisms for Triggering Dynamic Replanning

The system supports two primary mechanisms for initiating a replan after tool execution. Both approaches allow the agent to dynamically adapt without manual intervention.

### Explicit Signals via ReplanRequestedException

Any tool can force an immediate replan by throwing `ReplanRequestedException` (defined in [`embabel-agent-api/src/main/kotlin/com/embabel/agent/core/ReplanRequestedException.kt`](https://github.com/embabel/embabel-agent/blob/main/embabel-agent-api/src/main/kotlin/com/embabel/agent/core/ReplanRequestedException.kt)). This exception carries a human-readable reason string (e.g., "stuck, retrying with new plan") that the engine captures in the span attribute `embabel.replan.reason`.

When the `ToolLoop` catches this exception, it logs the reason, sets the context flag for replanning, and breaks the current execution loop to generate a fresh plan.

### Predicate-Based Replan Decisions

For conditional logic, the `Tool` utility class provides helper methods that inspect outputs without throwing exceptions:

- **`Tool.replanWhen(predicate)`**: Evaluates a predicate against the tool output. If the predicate returns `true`, the system creates a `ReplanDecision` object.
- **`Tool.replanAndAdd(valueComputer)`**: Computes a value to inject into the new plan while simultaneously triggering replanning.

These methods return `ReplanDecision` objects (defined in [`embabel-agent-api/src/main/java/com/embabel/agent/api/tool/ReplanDecision.java`](https://github.com/embabel/embabel-agent/blob/main/embabel-agent-api/src/main/java/com/embabel/agent/api/tool/ReplanDecision.java)) that the loop converts into replan requests.

## Observability and Metrics

Embabel exposes replanning events through both distributed tracing and Prometheus-style metrics, enabling operators to monitor agent adaptability in production.

### Distributed Tracing Attributes

The `EmbabelSpanEventListener` (located in [`embabel-agent-observability/src/main/java/com/embabel/agent/observability/tracing/EmbabelSpanEventListener.java`](https://github.com/embabel/embabel-agent/blob/main/embabel-agent-observability/src/main/java/com/embabel/agent/observability/tracing/EmbabelSpanEventListener.java)) automatically records the following span attributes:

- `embabel.plan.is_replanning`: Boolean flag indicating whether the current iteration represents a replanning phase
- `embabel.replan.reason`: Human-readable explanation when `ReplanRequestedException` triggers the replan
- `embabel.tool_loop.replan_requested`: Boolean indicating whether the tool loop resulted in a replan

These attributes are defined centrally in [`SpanAttributes.java`](https://github.com/embabel/embabel-agent/blob/main/SpanAttributes.java) ([`embabel-agent-observability/src/main/java/com/embabel/agent/observability/SpanAttributes.java`](https://github.com/embabel/embabel-agent/blob/main/embabel-agent-observability/src/main/java/com/embabel/agent/observability/SpanAttributes.java)).

### Prometheus Metrics

The `EmbabelMetricsEventListener` ([`embabel-agent-observability/src/main/java/com/embabel/agent/observability/metrics/EmbabelMetricsEventListener.java`](https://github.com/embabel/embabel-agent/blob/main/embabel-agent-observability/src/main/java/com/embabel/agent/observability/metrics/EmbabelMetricsEventListener.java)) increments the counter `embabel.planning.replanning.total` (tagged by agent name) each time a replan occurs. This metric allows tracking replanning frequency across different agent implementations.

## Implementation Examples

Below are practical implementations demonstrating how to configure tools for dynamic replanning.

### Forcing Replan on Any Failure

Use `Tool.replanAlways` to wrap fragile operations that should trigger strategy regeneration upon any error:

```java
Tool<String> fragileHttpCall = Tool.replanAlways(
    ctx -> {
        // ...perform HTTP request...
        if (response.is5xx()) {
            throw new RuntimeException("Server error");
        }
        return response.body();
    }
);

```

### Conditional Replanning

Use `Tool.replanWhen` to replan only when specific business logic conditions are met:

```java
Tool<String> maybeStale = Tool.replanWhen(
    ctx -> {
        String data = fetchData();
        return data == null || data.isEmpty();   // predicate decides to re‑plan
    },
    ctx -> new ReplanDecision("No data – need fresh plan")
);

```

### Adding Context During Replan

Use `Tool.replanAndAdd` to compute values that should be available in the regenerated plan:

```java
Tool<String> conditionalAdd = Tool.replanAndAdd(
    ctx -> {
        // compute a value that will be injected into the new plan
        return computeNextStep();
    },
    ctx -> {
        // normal execution path
        return performStep();
    }
);

```

### Exception Handling in the ToolLoop

The execution loop handles replanning signals as follows:

```java
try {
    toolResult = tool.execute(context);
} catch (ReplanRequestedException e) {
    observation = Observation.createNotStarted(SpanAttributes.EMBABEL_REPLAN, "replan")
        .lowCardinalityKeyValue(SpanAttributes.EMBABEL_REPLAN_REASON, e.getReason())
        .start();
    // flag next iteration as a re‑plan
    context.setReplanning(true);
    // break out of the current loop – a new plan will be generated
    break;
}

```

## Summary

- **Dynamic replanning** in Embabel occurs after every tool execution through a plan-execute-observe feedback loop.
- **Two trigger mechanisms** exist: explicit `ReplanRequestedException` throws and predicate-based `ReplanDecision` objects created via `Tool.replanWhen` or `Tool.replanAndAdd`.
- **Key source files** include [`ReplanRequestedException.kt`](https://github.com/embabel/embabel-agent/blob/main/ReplanRequestedException.kt) for exception handling, [`Tool.java`](https://github.com/embabel/embabel-agent/blob/main/Tool.java) for utility methods, and [`EmbabelSpanEventListener.java`](https://github.com/embabel/embabel-agent/blob/main/EmbabelSpanEventListener.java) for observability.
- **Observability** is provided through span attributes (`embabel.plan.is_replanning`, `embabel.replan.reason`) and the Prometheus metric `embabel.planning.replanning.total`.
- **Implementation** requires wrapping tool logic with utility methods or throwing specific exceptions to signal the need for strategy regeneration.

## Frequently Asked Questions

### What triggers dynamic replanning after each action in Embabel?

Dynamic replanning triggers when a tool throws `ReplanRequestedException` or when a predicate-based utility like `Tool.replanWhen` evaluates to true. The `ToolLoop` checks for these signals immediately after each action execution, before proceeding to the next step in the plan.

### How does Embabel handle the transition between execution and replanning?

When a replan is triggered, the `ToolLoop` breaks out of the current iteration, discards remaining actions, and sets a context flag indicating `embabel.plan.is_replanning` is true for the next cycle. The engine then invokes the planner again with updated context, including the reason for the replan captured in `embabel.replan.reason`.

### Can developers customize the conditions that cause an agent to replan?

Yes. Developers can use the static utility methods in [`Tool.java`](https://github.com/embabel/embabel-agent/blob/main/Tool.java) to define custom predicates via `replanWhen`, or they can manually throw `ReplanRequestedException` with specific reason strings from within custom tool implementations. This allows fine-grained control over when the agent abandons the current plan.

### How can I monitor replanning frequency in a production Embabel deployment?

Monitor the `embabel.planning.replanning.total` metric emitted by `EmbabelMetricsEventListener`, which tracks replanning events tagged by agent name. Additionally, trace spans containing `embabel.tool_loop.replan_requested` provide granular visibility into which specific tool executions triggered replanning behavior.