-
Notifications
You must be signed in to change notification settings - Fork 0
Composition While Loops
Agents and pipelines in Agents.KT are callable functions. An Agent<IN, OUT> has operator fun invoke(input: IN): OUT. A Pipeline<IN, OUT> has the same. This means you can call them inside any standard Kotlin control flow -- including while, do-while, for, and if.
No special API is needed. Just call the agent or pipeline like a function:
var result = 0
while (result < 5) {
result = increment(result)
}An agent that increments by 1, called until the result reaches a threshold:
import agents_engine.core.*
val increment = agent<Int, Int>("inc") {
skills { skill<Int, Int>("inc") { implementedBy { it + 1 } } }
}
var result = 0
while (result < 5) {
result = increment(result)
}
// result == 5A two-stage pipeline called repeatedly:
import agents_engine.composition.pipeline.then
val addOne = agent<Int, Int>("addOne") { skills { skill<Int, Int>("addOne") { implementedBy { it + 1 } } } }
val doubled = agent<Int, Int>("doubled") { skills { skill<Int, Int>("doubled") { implementedBy { it * 2 } } } }
val pipeline = addOne then doubled
var result = 1
while (result < 100) {
result = pipeline(result)
}
// Trace: 1 -> 4 -> 10 -> 22 -> 46 -> 94 -> 190
// result == 190Use a separate agent to evaluate the stop condition:
val process = agent<Int, Int>("process") { skills { skill<Int, Int>("process") { implementedBy { it + 3 } } } }
val isDone = agent<Int, Boolean>("isDone") { skills { skill<Int, Boolean>("isDone") { implementedBy { it >= 15 } } } }
var result = 0
while (!isDone(result)) {
result = process(result)
}
// Trace: 0 -> 3 -> 6 -> 9 -> 12 -> 15 (isDone returns true)
// result == 15Track state across iterations using a data class:
data class State(val value: Int, val log: List<Int>)
val step = agent<State, State>("step") {
skills { skill<State, State>("step") { implementedBy { s ->
State(s.value - 1, s.log + s.value)
} } }
}
var state = State(5, emptyList())
while (state.value > 0) {
state = step(state)
}
// state.log == [5, 4, 3, 2, 1]The full power of while loops appears when combined with other composition primitives. Here is a realistic example where parallel agents generate specifications, an evaluator scores them, and the outer while loop drives iteration until quality is sufficient:
import agents_engine.composition.parallel.div
import agents_engine.composition.pipeline.then
// ... (define SpecsParcel, spec types, etc.)
val specsGeneration = useCasesMaster / glossaryMaster / outerRefsMaster /
systemActorsMaster / featuresMaster / requirementsMaster
val total = specsGeneration then specsGatheringMaster
val specsEvaluator = agent<SpecsParcel, Float>("evaluator") {
skills { skill<SpecsParcel, Float>("evaluator") { implementedBy { specs ->
// Score based on completeness
val completeness = (specs.useCases.size + specs.glossaryTerms.size +
specs.outerReferences.size + specs.systemActors.size +
specs.features.size + specs.requirements.size).toFloat()
minOf(completeness * 15f * (iteration + 1), 100f)
} } }
}
var specsGoodness = 0f
var currentState = SpecsParcel(description = "Build something people want")
while (specsGoodness < 90f) {
val newSpecs = total(currentState)
specsGoodness = specsEvaluator(newSpecs)
currentState = newSpecs
}
// Iterates until the evaluator gives a score >= 90A pipeline that must run at least once:
val process = agent<Int, Int>("process") {
skills { skill<Int, Int>("process") { implementedBy { it + 10 } } }
}
var result = 0
do {
result = process(result)
} while (result < 5)
// Runs once: 0 -> 10 (10 >= 5, stop)
// result == 10Both achieve iterative execution, but they serve different purposes:
.loop{} |
while |
|
|---|---|---|
| Composable | Yes -- embeds in pipelines with then
|
No -- imperative, lives outside compositions |
| Type-safe feedback | The next function signature enforces types |
You manage variables manually |
| Pipeline position | Can be a stage: a then loop then b
|
Cannot be placed inside a pipeline |
| Flexibility | Feedback function only | Full Kotlin: multiple agents, side effects, logging |
| When to use | The iteration is a self-contained stage in a pipeline | The iteration needs external state, multiple agents, or complex control flow |
Use .loop{} when the iteration is a clean, self-contained transformation that belongs inside a pipeline:
// .loop{} is a pipeline stage
val pipeline = parse then refine.loop { r -> if (r.score >= 90) null else r } then formatUse while when you need full control over the iteration -- calling multiple independent agents, updating external state, or making complex decisions:
// while gives full control
var state = initial
while (evaluator(state) < threshold) {
val specs = generator(state)
val reviewed = reviewer(specs)
state = merger(state, reviewed)
logger.info("Iteration complete, score: ${evaluator(state)}")
}You can use .loop{} inside a pipeline for a self-contained iterative stage, and while for outer iteration that orchestrates the full system:
// Inner loop: refine until good enough (composable, inside pipeline)
val refinementLoop = refiner.loop { r -> if (r.quality >= 80) null else r }
val pipeline = generate then refinementLoop then format
// Outer loop: run the whole pipeline until external criteria met
var document = Document(topic = "distributed systems")
while (!reviewer(document).approved) {
document = pipeline(document)
}The .loop{} is the right choice for iteration that is part of a data flow. Plain while is the right choice for orchestration logic that sits above the data flow.
Getting Started
Core Concepts
Composition Operators
LLM Integration
- Model & Tool Calling
- Tool Error Recovery
- Skill Selection & Routing
- Budget Controls
- Observability Hooks
Guided Generation
Agent Memory
Reference