#1048 DSL retry_policy retries non-retryable errors and ignores Retry-After; orchestrator never passes retry_at to jumper

open medium Created 2026-10-22 22:45 · Updated 2026-10-22 22:45

Description

Edit
EARS: When a tool step raises an error the orchestrator classifies as non-retryable (NON_RETRYABLE_ERROR_TYPES / patterns), the DSL retry_policy shall not retry it. When a tool error carries retry_after_seconds, the next attempt (DSL durable sleep or jumper retry) shall not start before that time, within the task's retry policy. FOUND in #1044: (1) engine/interpreters/operators/task.py _execute_with_retry retries EVERY Exception up to retry_policy.max_retries with the fixed retry_policy.delay; it never consults is_retryable_error, so a 401 from an LLM provider is retried max_retries times. (2) engine/orchestrator/failure_handler.py fail_run -> jumper_client.fail_task(conn, run_id, reason) never passes retry_at, although jumper.fail_run(p_retry_at) supports it; the exception object is available in execution_helpers_outcome.handle_failure but only str(error) reaches fail_run. Effect for #1044: Prism 429/503 Retry-After is honoured only inside the adapter's bounded in-call retry (<= [llm.providers.prism] max_retry_after_seconds = 30 s); a longer reset (e.g. daily quota) surfaces as LLMRateLimitedError(retry_after_seconds) and the outer retry uses its own fixed/exponential delay. Proposal: a generic contract (exception attributes retryable: bool, retry_after_seconds: float) read by task.py and by handle_failure -> fail_run(retry_at=max(policy delay, now + retry_after)); tests both directions (blocks before reset, resumes after).

Comments

Loading comments...

Context

Loading context...

Audit History

View All
Loading audit history...