val pipeline = specMaster then coder then reviewer
// Pipeline<TaskRequest, ReviewResult>
val full = (specMaster then coder) then (reviewer then deployer)All agents receive the same input concurrently via coroutines. The next stage receives List<OUT>.
val parallel = securityReview / styleReview / performanceReview
// Parallel<CodeBundle, Review>
val synthesizer = agent<List<Review>, Report>("synthesizer") {
skills {
skill<List<Review>, Report>("merge", "Merges all review results into a single report") {
implementedBy { reviews ->
Report(passed = reviews.all { it.passed }, summary = reviews.joinToString("\n") { it.summary })
}
}
}
}
val pipeline = coder then parallel then synthesizer
// Pipeline<Specification, Report>Liskov: declare agents as the common supertype — subtypes flow through transparently.
sealed interface Review
data class QuickReview(val summary: String) : Review
data class DeepReview(val issues: List<String>, val score: Double) : Review
val quick = agent<CodeBundle, Review>("quick") { skills { skill<CodeBundle, Review>("q", "Quick scan") { implementedBy { QuickReview(briefScan(it)) } } } }
val deep = agent<CodeBundle, Review>("deep") { skills { skill<CodeBundle, Review>("d", "Deep scan") { implementedBy { DeepReview(fullScan(it), score(it)) } } } }
val pipeline = (quick / deep) then synthesizer
// Pipeline<CodeBundle, Report>The * shorthand is convention over configuration: every agent receives the same input, all non-final agents run concurrently as participants, and the last agent is the captain. The captain determines the forum OUT type and is the default finalizer.
val forum = initiator * analyst * critic * captain
// Forum<Specs, Decision>
val pipeline = inputConverter then forum then formatter
// Pipeline<Input, FormattedDecision>
// Track forum outputs as they arrive
forum.onMentionEmitted { agentName, output ->
println("[$agentName]: $output")
}For explicit roles and permissions, use the forum DSL:
val forum = forum<Specs, Decision> {
participant(initiator)
participant(analyst)
captain(captain)
allowForumReturn(analyst) // optional; captain may return by default
}forum_return is a built-in forum capability. The captain gets it automatically; additional registered participants can be granted it with allowForumReturn(...). If nobody calls forum_return, the captain's normal return value becomes the forum result.
The block receives the output and returns the next input to continue, or null to stop. Fully composable.
val refineLoop = refine.loop { result -> if (result.score >= 90) null else result }
val qualityLoop = (generate then evaluate).loop { result ->
if (result.quality >= 90f) null else result.spec
}
val pipeline = prepare then qualityLoop then publishQuality gate with while — agents and pipelines are plain callable functions; standard Kotlin control flow works without any DSL:
var specs = SpecsParcel(description = "build a user API")
var quality = 0f
while (quality < 90f) {
specs = specPipeline(specs)
quality = specsEvaluator(specs)
}Routes the output of an agent to a different handler per sealed variant. All branches must produce the same OUT type. Unhandled variants throw at invocation.
sealed interface ReviewResult
data class Passed(val score: Double) : ReviewResult
data class Failed(val issues: List<String>) : ReviewResult
data class NeedsRevision(val feedback: String) : ReviewResult
val afterReview = reviewer.branch {
on<Passed>() then deployer
on<Failed>() then failReporter
on<NeedsRevision>() then (reviser then reviewer) // pipeline on a variant
}
// Branch<CodeBundle, Report>
val pipeline = coder then afterReview then notifier
// Pipeline<Specification, Notification>Each agent<>() call is an instance. An instance can only be placed in one structure, ever.
val a = agent<A, B>("a") {}
val b = agent<B, C>("b") {}
a then b // ✅ "a" placed in pipeline
a then c // ❌ IllegalArgumentException:
// Agent "a" is already placed in pipeline.
// Create a new instance for "pipeline".
a * forum // ❌ same instance, different structure — also caughtAgent<A, B> : A → B
A then B : Agent<X,Y> then Agent<Y,Z> → Pipeline<X,Z>
A / B : Agent<X,Y> / Agent<X,Y> → Parallel<X,Y> → List<Y> to next
A * B : Agent<X,Y> * Agent<X,Z> → Forum<X,Z>
A.loop { } : (Pipeline<X,Y> | Agent<X,Y>) → Loop<X,Y> (null = stop, X = continue)
A.branch { } : Agent<X, Sealed<Y>) → Branch<X,Z> (all variants → same Z)