Anthropic's Building Effective Agents catalog maps one-to-one onto Agents.KT primitives — no new abstractions, just the operators the framework already ships. Each recipe below is the canonical shape; every operator keeps the typed boundaries, the single-placement rule, streaming sessions, and audit events.
| Anthropic pattern | Agents.KT primitive | Recipe |
|---|---|---|
| Augmented LLM (ReAct) | one agent + tools(...) allowlist |
§1 |
| Prompt chaining | then |
§2 |
| Routing | handoff / branch |
§3 |
| Parallelization — sectioning | / + .aggregate { } |
§4 |
| Parallelization — voting | / + majorityVote() / weighted(...) |
§4 |
| Orchestrator-workers | forum { } + captain |
§5 |
| Evaluator-optimizer | .loopUntil { } + evalGate |
§6 |
| Reflexion | same loop, self-critique stage | §6 |
| Multi-agent debate | forum + consensusCaptain |
§7 |
| Speculative sampling (latency) | firstOf / .speculative(n) |
§8 |
| Human-in-the-loop | humanApproval / HumanGateRegistry |
§9 |
| Retrieval (RAG) | knowledge(key, desc, ragRetriever(...)) |
§10 |
One agent, a model, and a least-privilege tool allowlist — the runtime refuses anything outside it:
val researcher = agent<String, String>("researcher") {
model { claude("claude-opus-4-7"); apiKey = key }
tools { +perplexitySearchTool(perplexityKey) } // untrusted-output, cited
skills { skill<String, String>("research", "Research with citations") { tools("perplexitySearch") } }
budget { maxTurns = 10; maxToolCalls = 16 }
}Sequential stages with compiler-checked boundaries; every stage streams through the parent session (#3866):
val pipeline = parse then generate then review // Pipeline<RawText, ReviewResult>
pipeline.session(input).events // all three stages' events, by agentIdhandoff is branch plus the audit contract — the transfer fires onHandoff / HandoffPerformed, and the specialist receives only its declared input type, never the router's history (#3871):
val flow = triage handoff {
on<BillingTask>() then billing
on<TechTask>() then tech
}Sectioning and voting are / plus a one-line aggregator (#3872):
val sections = (summarizer / riskScanner / toneChecker) // List<Finding>
val vote = (a / b / c).aggregate { majorityVote() } // first-encountered tie-break
val expertly = (expert / intern1 / intern2).aggregate { weighted(mapOf(expert to 3.0)) }
val best = (a / b / c).aggregate { bestOfN { judge(it).score } }forum runs the workers concurrently; the captain synthesizes. Use transcriptCaptain when the orchestrator needs to see what every worker said:
val panel = forum<Brief, Plan> {
participant(architect); participant(securityReviewer); participant(estimator)
transcriptCaptain(synthesizer) // Agent<ForumTranscript<Brief>, Plan>
}The named loop + judge gate (#3870). Reflexion is the same loop with the critique folded into the drafting stage:
val gate = evalGate(qualityRubric, threshold = 7)
val refiner = drafter.loopUntil(maxIterations = 5) { draft -> gate.pass(draft) }
// reflexion: drafter = (draft then selfCritique then revise) as the looped pipelineMembers argue concurrently; the verdict needs real agreement — consensusCaptain fails loud with the tally rather than silently picking a side (#3877):
val debate = forum<Claim, Verdict> {
participant(advocate); participant(skeptic); participant(empiricist)
transcriptCaptain(consensusCaptain(quorum = 2))
// adversarial-robust numeric variant: transcriptCaptain(byzantineCaptain())
}LLM latency is variance-dominated — race equivalents and keep the first success (#3869):
val fast = firstOf(primary, fallbackProvider)
val sampled = generator.speculative(3)
fast.onRaceSettled { winner, cancelled, ms -> audit(winner, cancelled, ms) }Budget honesty: losers' tokens up to cancellation are real spend — bound N.
Tool-level pause with typed resume (#2489), or the named registry surface (#3868):
tools { tool("charge_card", "Charges") { args -> humanApproval { title = "Charge ${args["amount"]}?" } } }
when (val run = gates.guard(checkout, order)) {
is GateOutcome.Completed -> run.output
is GateOutcome.Paused -> notifyReviewer(run.gate.gateId, run.gate.reason)
}
// later: gates.find(gateId)?.approve(reviewer = "alice@acme.com")Query-aware knowledge over any EmbeddingStore (#3863); retrieval rides the tool path, so it lands in the audit trail with provenance:
skill<String, String>("answer-from-docs", "Answers from project docs") {
knowledge("project-docs", "Specs and ADRs", ragRetriever(pgvectorStore, embedder) { topK = 8; minScore = 0.55f })
tools()
}Every recipe composes with the rest of the runtime: permission manifests capture the full shape (agent.permissionManifest() — now with @Generable schemas, #3875), manifestHash correlates runtime events back to the reviewed capability graph, and cross-model regression (suite.runAcrossModels(...), #3876) keeps the behavior pinned across providers.