What is the problem this feature would solve?
ExecutionPlan provides a clean abstraction for retries, backoff strategies, and provider failover, but it currently doesn't expose any lifecycle events for individual execution attempts.
As a result, applications cannot observe or instrument:
Retry count
Per-attempt success/failure
Attempt duration
Provider failover events
This makes it difficult to integrate ExecutionPlan with observability tools.
While Effect.tap, tapError, and withSpan can observe the final outcome of an effect, they cannot observe intermediate attempts performed internally by ExecutionPlan. For example, if an operation fails twice before succeeding on the third attempt, there is currently no public API to record those retries or attach telemetry to them.
What is the feature you are proposing to solve the problem?
Introduce optional lifecycle hooks (or an observer interface) that can be attached to an ExecutionPlan. These hooks would be invoked during the execution lifecycle without changing the existing retry semantics.
Example:
const plan = ExecutionPlan.make({
provide: GeminiFlash,
attempts: 3,
schedule: Schedule.exponential("100 millis", 1.5),
while: (error) => error._tag === "NetworkError",
observe: {
onAttemptStart(event) {},
onAttemptSuccess(event) {},
onAttemptFailure(event) {},
onRetry(event) {}
},
})
This would allow users to collect metrics, emit tracing events, and implement custom logging while continuing to use ExecutionPlan as the retry mechanism, rather than having to replace it with manual retry logic solely for observability.
What alternatives have you considered?
-
Using Effect.tap, tapError, or tapBoth: These only observe the final outcome of the effect and do not provide visibility into intermediate retry attempts performed by ExecutionPlan.
-
Instrumenting the underlying effect (e.g. LanguageModel.generateObject): This works for specific use cases but couples observability to the implementation being retried. It also doesn't provide ExecutionPlan-specific context such as the current step, attempt number, or provider transition.
-
Replacing ExecutionPlan with Effect.retry: This allows full control over retries and observability, but it means giving up the higher-level ExecutionPlan abstraction for provider failover and execution strategies. The goal is to continue using ExecutionPlan while gaining first-class observability.
-
Implementing a custom wrapper around ExecutionPlan: This would require duplicating or depending on Effect's internal implementation, which is not ideal and could become difficult to maintain as ExecutionPlan evolves.
Providing built-in lifecycle hooks would offer a standardized and maintainable way to integrate metrics, tracing, and logging without changing existing execution behavior.
What is the problem this feature would solve?
ExecutionPlan provides a clean abstraction for retries, backoff strategies, and provider failover, but it currently doesn't expose any lifecycle events for individual execution attempts.
As a result, applications cannot observe or instrument:
Retry count
Per-attempt success/failure
Attempt duration
Provider failover events
This makes it difficult to integrate ExecutionPlan with observability tools.
While
Effect.tap,tapError, andwithSpancan observe the final outcome of an effect, they cannot observe intermediate attempts performed internally by ExecutionPlan. For example, if an operation fails twice before succeeding on the third attempt, there is currently no public API to record those retries or attach telemetry to them.What is the feature you are proposing to solve the problem?
Introduce optional lifecycle hooks (or an observer interface) that can be attached to an ExecutionPlan. These hooks would be invoked during the execution lifecycle without changing the existing retry semantics.
Example:
This would allow users to collect metrics, emit tracing events, and implement custom logging while continuing to use ExecutionPlan as the retry mechanism, rather than having to replace it with manual retry logic solely for observability.
What alternatives have you considered?
Using
Effect.tap,tapError, ortapBoth: These only observe the final outcome of the effect and do not provide visibility into intermediate retry attempts performed by ExecutionPlan.Instrumenting the underlying effect (e.g.
LanguageModel.generateObject): This works for specific use cases but couples observability to the implementation being retried. It also doesn't provide ExecutionPlan-specific context such as the current step, attempt number, or provider transition.Replacing ExecutionPlan with
Effect.retry: This allows full control over retries and observability, but it means giving up the higher-level ExecutionPlan abstraction for provider failover and execution strategies. The goal is to continue using ExecutionPlan while gaining first-class observability.Implementing a custom wrapper around ExecutionPlan: This would require duplicating or depending on Effect's internal implementation, which is not ideal and could become difficult to maintain as ExecutionPlan evolves.
Providing built-in lifecycle hooks would offer a standardized and maintainable way to integrate metrics, tracing, and logging without changing existing execution behavior.