Skip to content

Composition While Loops

skobeltsyn edited this page Mar 28, 2026 · 1 revision

Composition: While Loops

Overview

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)
}

Example: Simple While Loop

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 == 5

Example: Pipeline in a While Loop

A 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 == 190

Example: External Condition Agent

Use 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 == 15

Example: Accumulator Pattern

Track 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]

Example: Quality Gate with Parallel and While

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 >= 90

Example: Do-While

A 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 == 10

When to Use While vs .loop{}

Both 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 format

Use 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)}")
}

Combining Both

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.


Next: Pipeline | Parallel | Loop | Branch | Forum

Clone this wiki locally