Customer Onboarding: Conditional Loops with * until(pred) Operator¶
Real-world use case: Customer onboarding flow that collects required information iteratively until all fields are complete. Used by fintech and insurance applications for guided data collection.
In other frameworks: LangGraph requires conditional_edges with a custom routing function to implement loop-until semantics (~30 lines). adk-fluent uses
until(predicate) for declarative conditional loops.
Tip
What you’ll learn How to create looping agent workflows.
Source: 30_until_operator.py
from adk_fluent import Agent, Pipeline, until
# until() creates a spec for the * operator.
# In a customer onboarding flow, we loop until all verification steps pass.
identity_verified = until(lambda s: s.get("identity_status") == "verified", max=5)
# agent * until(pred) — loop the verification flow until identity is confirmed
onboarding_loop = (
Agent("document_checker")
.model("gemini-2.5-flash")
.instruct("Review the uploaded identity documents for completeness and clarity.")
.writes("identity_status")
>> Agent("verification_agent")
.model("gemini-2.5-flash")
.instruct("Cross-reference document data against external databases. Report verification status.")
) * identity_verified
# Default max is 10 — used for compliance checks that may take several rounds
compliance_check = (
Agent("kyc_screener").model("gemini-2.5-flash").instruct("Screen customer against KYC/AML watchlists.")
>> Agent("risk_assessor")
.model("gemini-2.5-flash")
.instruct("Assess customer risk level based on screening results.")
) * until(lambda s: s.get("kyc_clear"))
# Works in larger expressions: full customer onboarding pipeline
full_onboarding = (
Agent("intake_agent").model("gemini-2.5-flash").instruct("Collect customer information and upload instructions.")
>> (
Agent("document_validator")
.model("gemini-2.5-flash")
.instruct("Validate documents meet format and quality requirements.")
>> Agent("identity_verifier")
.model("gemini-2.5-flash")
.instruct("Verify identity using biometric and document matching.")
)
* until(lambda s: s.get("verification_passed"), max=3)
>> Agent("welcome_agent").model("gemini-2.5-flash").instruct("Send welcome package and account activation details.")
)
# int * agent still works — fixed retry count for simple cases
document_retry = Agent("doc_requester").model("gemini-2.5-flash").instruct("Request missing documents.") * 3
# Native ADK has no conditional loop exit built in. You'd need:
# 1. A custom BaseAgent subclass evaluating the predicate
# 2. Yield Event(actions=EventActions(escalate=True)) to break
# 3. Wire it into LoopAgent.sub_agents manually
# This is ~25 lines of boilerplate per loop condition.
graph TD
n1[["intake_agent_then_document_validator_then_identity_verifier_x3_then_welcome_agent (sequence)"]]
n2["intake_agent"]
n3(("document_validator_then_identity_verifier_x3 (loop x3)"))
n4["document_validator"]
n5["identity_verifier"]
n6["welcome_agent"]
n3 --> n4
n3 --> n5
n2 --> n3
n3 --> n6
Equivalence¶
from adk_fluent.workflow import Loop
# * until() creates a Loop
assert isinstance(onboarding_loop, Loop)
assert onboarding_loop._config["max_iterations"] == 5
assert onboarding_loop._config["_until_predicate"] is not None
# Default max is 10
assert compliance_check._config["max_iterations"] == 10
# Builds with checkpoint agent for loop exit
built = onboarding_loop.build()
assert built.sub_agents[-1].name == "_until_check"
# In larger expression — pipeline with loop in middle
assert isinstance(full_onboarding, Pipeline)
built_full = full_onboarding.build()
assert len(built_full.sub_agents) == 3 # intake, loop, welcome
# int * agent still works
assert isinstance(document_retry, Loop)
assert document_retry._config["max_iterations"] == 3