Retry If: API Integration Agent That Retries on Transient Failures

Tip

What you’ll learn How to use retry if: api integration agent that retries on transient failures with the fluent API.

Source: 38_loop_while.py

from adk_fluent import Agent, Loop

# Scenario: A payment processing agent that calls an external gateway.
# Transient failures (timeouts, rate limits) should trigger automatic retries,
# but permanent failures (invalid card) should stop immediately.

# .loop_while(): keep retrying while the predicate returns True
payment_processor = (
    Agent("payment_processor")
    .model("gemini-2.5-flash")
    .instruct(
        "Process the payment through the gateway. Report status as 'success', 'transient_error', or 'permanent_error'."
    )
    .writes("payment_status")
    .loop_while(lambda s: s.get("payment_status") == "transient_error", max_iterations=3)
)

# loop_while on a pipeline -- retry the entire charge-then-verify flow
charge_and_verify = (
    Agent("charge_agent")
    .model("gemini-2.5-flash")
    .instruct("Submit charge to payment gateway.")
    .writes("charge_result")
    >> Agent("verification_agent")
    .model("gemini-2.5-flash")
    .instruct("Verify the charge was recorded by the bank.")
    .writes("verified")
).loop_while(lambda s: s.get("verified") != "confirmed", max_iterations=5)

# Equivalence: loop_while(p) == loop_until(not p)
# These produce identical behavior for an inventory sync agent:
via_retry = (
    Agent("inventory_sync")
    .model("gemini-2.5-flash")
    .loop_while(lambda s: s.get("sync_status") != "complete", max_iterations=4)
)
via_loop = (
    Agent("inventory_sync")
    .model("gemini-2.5-flash")
    .loop_until(lambda s: s.get("sync_status") == "complete", max_iterations=4)
)
# Native ADK has no built-in conditional retry. You'd need to:
#   1. Wrap the agent in a LoopAgent
#   2. Create a custom checkpoint BaseAgent that evaluates a predicate
#   3. Yield Event(actions=EventActions(escalate=True)) to exit when satisfied
# For a payment gateway integration, this means 30+ lines of boilerplate
# just to handle transient 503 errors.
        graph TD
    n1(("charge_agent_then_verification_agent_x5 (loop x5)"))
    n2["charge_agent"]
    n3["verification_agent"]
    n1 --> n2
    n1 --> n3
    

Equivalence

from adk_fluent.workflow import Loop as LoopBuilder

# loop_while creates a Loop builder
assert isinstance(payment_processor, LoopBuilder)

# max_iterations maps to max_iterations
assert payment_processor._config["max_iterations"] == 3

# Pipeline retry also creates a Loop
assert isinstance(charge_and_verify, LoopBuilder)
assert charge_and_verify._config["max_iterations"] == 5

# The predicate is inverted: loop_while(p) stores not-p as until_predicate
until_pred = payment_processor._config["_until_predicate"]
assert until_pred({"payment_status": "success"}) is True  # exit: stop retrying
assert until_pred({"payment_status": "transient_error"}) is False  # continue retrying

# Both loop_while and loop_until produce Loop builders with identical structure
assert isinstance(via_retry, LoopBuilder)
assert isinstance(via_loop, LoopBuilder)
assert via_retry._config["max_iterations"] == via_loop._config["max_iterations"]

# Build verifies checkpoint agent is injected
built = payment_processor.build()
assert len(built.sub_agents) >= 2
assert built.sub_agents[-1].name == "_until_check"

See also

API reference: Loop