Koog 1.0 idioms, gotchas, and scaffolding skills for Kotlin agents on the JVM
86
88%
Does it follow best practices?
Impact
86%
1.86xAverage score across 45 eval scenarios
Advisory
Suggest reviewing before use
Process steps in order. Do not skip ahead.
Worked Kotlin example excerpts (illustrative, with ... placeholders for tool
bodies and prompt-executor wiring; not a standalone buildable file):
skills/domain-model-subtask-pipeline/references/banking-example.mdThe pattern earns its complexity when the work has distinct phases that produce structurally different intermediate artifacts. Examples: issue triage (raw text → classified issue → fix proposal → verified fix); customer support (question → identified problem → applied resolution → verified resolution); code review (PR diff → categorized changes → review comments → applied edits).
singleRunStrategy()Skill(skill: "use-planner")Proceed immediately to Step 2.
The slicing axis is access, not feature. Group tools into separate ToolSet
classes by what they can do:
Communication tools — ask the user, send email, post comments, request approval
Read tools — query, list, search, get details (no side effects)
Write tools — mutate, create, delete, transfer (real consequences)
Constructor parameters are dependency injection. They are NOT visible to the LLM
userId, sessionId, DB connections, HTTP clients go through the constructor
The LLM only sees @Tool-annotated methods
Keeps authorization context out of the LLM-controlled surface
For tool registration mechanics, invoke Skill(skill: "add-tool"). See the
reference file for a worked three-ToolSet example.
Proceed immediately to Step 3.
Each subtask's input and output is a @Serializable Kotlin data class with
@LLMDescription on the class and on every property.
@LLMDescription describes the contract@LLMDescription describes each fieldFor top-level structured output (no subtask pipeline), invoke
Skill(skill: "add-structured-output"). See the reference file for worked
handoff classes.
Proceed immediately to Step 4.
subgraphWithTask<In, Out>Each subtask is a subgraphWithTask (or subgraphWithVerification, see
Step 5) typed end-to-end. Pass the subset of tools the subtask needs plus the
model that fits the subtask's complexity:
import ai.koog.agents.ext.agent.subgraphWithTask
import ai.koog.agents.ext.agent.subgraphWithVerification
import ai.koog.agents.ext.agent.CriticResult
val identifyProblem by subgraphWithTask<String, AccountIssueSummary>(
tools = communicationTools.asTools() + readTools.asTools(),
llmModel = OpenAIModels.Chat.GPT5_2,
) { input -> "Identify the problem:\n$input" }Match the model to the subtask:
subgraphWithTask, subgraphWithVerification, and CriticResult live in
package ai.koog.agents.ext.agent but ship inside the agents-core
artifact, which the koog-agents umbrella already pulls. No extra
dependency needed — the standalone ai.koog:agents-ext artifact is a
separate 1.0.0-beta module and is NOT required for these APIs.
If you type-annotate the returned strategy, the strategy type lives in
ai.koog.agents.core.agent.entity.AIAgentGraphStrategy (not bare
ai.koog.agents.core.agent).
For graph DSL mechanics (edges, node naming, the member-vs-extension
import table), invoke Skill(skill: "author-strategy").
Proceed immediately to Step 5.
subgraphWithVerificationsubgraphWithVerification<T> produces a CriticResult<T> with
.successful: Boolean, .feedback: String?, .input: T. Branch on it:
edge(verifySolution forwardTo nodeFinish onCondition { it.successful } transformed { it.input })
edge(verifySolution forwardTo adjustSolution onCondition { !it.successful } transformed { it.feedback.orEmpty() })
edge(adjustSolution forwardTo verifySolution)transformed { it.input } pulls the verified payload out of CriticResult<T>transformed { it.feedback.orEmpty() } coerces nullable feedback to non-null
for subgraphWithTask<String, _> inputThe adjust subgraph re-runs the action. Grant it read + write ToolSets.
Communication-only adjustment cannot apply the corrected fix.
Cap the loop. A stubborn critic / adjuster can ping-pong forever within
maxIterations.
AIAgentStorage (see Skill(skill: "manage-state"))adjustSolution to nodeFinish when attempts exceed a
sensible boundProceed immediately to Step 6.
Koog shares the message history across subtasks automatically, even when each
subtask uses a different model. You do NOT thread the history manually between
subgraphs. Tool calls from identifyProblem are visible to fixProblem's
LLM; fixProblem's actions are visible to verifySolution.
If the chain is long, history grows. Compress at deliberate boundaries (end of a phase, start of the next), not at every node:
llm.writeSession {
replaceHistoryWithTLDR()
}Pass a HistoryCompressionStrategy variant when the default TL;DR shape
doesn't fit. For the full set of variants, invoke
Skill(skill: "manage-state").
Proceed immediately to Step 7.
AIAgentPass the strategy explicitly as strategy =. It is not the default.
Register every tool at the agent level via toolRegistry
Each subgraph selects its subset via subgraphWithTask's tools = parameter
The agent's registry is one flat namespace. Don't register tools per-subgraph
Set maxIterations higher than the default 50. Pipelines chain LLM calls
and the default runs out fast
When per-subgraph llmModel values span providers (e.g., OpenAIModels.Chat.O3 for the verifier, AnthropicModels.Sonnet_4_5 for the deployer), the agent needs a MultiLLMPromptExecutor with one client per provider. simpleOpenAIExecutor / simpleAnthropicExecutor only know one provider each and will fail at runtime when a subgraph asks for the other:
import ai.koog.prompt.executor.clients.anthropic.AnthropicLLMClient
import ai.koog.prompt.executor.clients.openai.OpenAILLMClient
import ai.koog.prompt.executor.llms.MultiLLMPromptExecutor
val executor = MultiLLMPromptExecutor(
OpenAILLMClient(System.getenv("OPENAI_API_KEY")),
AnthropicLLMClient(System.getenv("ANTHROPIC_API_KEY")),
)Run ./gradlew build. The most common failure is a type mismatch on an edge
predicate. The chain only compiles when every subgraph's output type matches
the next subgraph's input type. Go back to Step 3 if types don't line up.
Full agent-construction snippet:
skills/domain-model-subtask-pipeline/references/banking-example.mdFinish here.
evals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
scenario-6
scenario-7
scenario-8
scenario-9
scenario-10
scenario-11
scenario-12
scenario-13
scenario-14
scenario-15
scenario-16
scenario-17
scenario-18
scenario-19
scenario-20
scenario-21
scenario-22
scenario-23
scenario-24
scenario-25
scenario-26
scenario-27
scenario-28
scenario-29
scenario-30
scenario-31
scenario-32
scenario-33
scenario-34
scenario-35
scenario-36
scenario-37
scenario-38
scenario-39
scenario-40
scenario-41
scenario-42
scenario-43
scenario-44
scenario-45
skills
add-observability
add-persistence
add-rag
add-structured-output
add-token-budgeting
add-tool
cache-llm-calls
define-prompt
domain-model-subtask-pipeline
references
enable-prompt-caching
handle-agent-events
manage-state
migrate-from-0-x
model-planner-subtasks
persist-chat-history
query-sql-from-agent
scaffold-agent
snapshot-and-restore
test-koog-agents
trace-agent-internals
use-attachments
use-functional-agent
use-llm-node-variants
use-planner
wire-a2a
wire-acp-server
wire-ktor-server
wire-mcp-server
wire-spring-boot