Koog 1.0 idioms, gotchas, and scaffolding skills for Kotlin agents on the JVM
87
88%
Does it follow best practices?
Impact
87%
1.85xAverage score across 45 eval scenarios
Advisory
Suggest reviewing before use
Process steps in order. Do not skip ahead.
implementation("ai.koog:koog-ktor:1.0.0")The umbrella koog-agents does not include the Ktor integration — add it explicitly.
Proceed immediately to Step 2.
In the Application module, install the Koog plugin. The minimal install is just the prompt-executor wiring:
import ai.koog.ktor.Koog
import ai.koog.prompt.executor.llms.all.simpleOpenAIExecutor
import io.ktor.server.application.*
fun Application.module() {
install(Koog) {
promptExecutor(simpleOpenAIExecutor(System.getenv("OPENAI_API_KEY")))
}
}install(Koog) { ... } is the entry point. The plugin's companion implements Ktor's Plugin<ApplicationCallPipeline, KoogAgentsConfig, Koog> so it composes with other Ktor plugins (ContentNegotiation, Authentication, CallLogging) the usual way.
Steps 3 and 4 are conditional add-ons within the sequential flow:
Proceed immediately to Step 3.
Skip this step entirely if the user did not name MCP servers (no mcp { ... } block needed in the install). Proceed to Step 4 either way.
The MCP example below uses HttpClient(CIO). Add implementation("io.ktor:ktor-client-cio:<ktor-version>") to build.gradle.kts if the project does not already pull a Ktor client engine (server-only projects don't).
The Ktor plugin exposes a typed mcp { ... } block that registers MCP servers as part of the plugin configuration — no separate McpToolRegistryProvider plumbing in user code:
import io.ktor.client.HttpClient
import io.ktor.client.engine.cio.CIO
val httpClient = HttpClient(CIO)
install(Koog) {
mcp {
// streamable HTTP (1.0 primary transport)
streamableHttp(url = "https://mcp.github.example/v1", httpClient = httpClient)
// SSE fallback
sse(url = "https://legacy-mcp.example/sse")
// stdio (locally launched)
process(ProcessBuilder("npx", "-y", "@playwright/mcp@latest").start())
}
}Use Streamable HTTP unless the remote server only supports SSE. For the standalone (non-Ktor) form, invoke Skill(skill: "wire-mcp-server").
Proceed immediately to Step 4.
application.conf (Optional — skip unless user names HOCON/YAML config)Skip this step entirely if the user did not name HOCON or YAML external config. Proceed to Step 5 either way.
For deployments where agent shape should be configuration, not code, KoogAgentsConfig reads application.conf (HOCON) or application.yaml via loadAgentsConfig():
koog {
agents {
name = "github-assistant"
model.id = "gemini-2.5-flash-lite"
model.options.temperature = 1.0
system_prompt = """
You are a helpful GitHub assistant.
"""
tools = [
{
type = "MCP"
id = "GitHub"
options.url = "https://mcp.github.example/v1"
}
]
}
}The agent is then resolvable from the Ktor application call via a typed accessor — the plugin's call-attribute key is Koog.key. Read it inside route handlers when you need the configured agent.
Proceed immediately to Step 5.
Expose the agent on an HTTP route. A minimal POST endpoint:
routing {
post("/agent") {
val input = call.receiveText()
val agent = call.attributes[Koog.key].agent // resolution via plugin attribute key
val result = agent.run(input)
call.respondText(result)
}
}For streaming responses, use nodeLLMRequestStreaming inside a custom strategy (see use-llm-node-variants) and Ktor's respondBytesWriter { ... } or SSE/WebSocket transport.
Write the modified module and the dependency to disk with explicit Path: labels (same convention as scaffold-agent):
Path: src/main/kotlin/com/example/Application.kt — the file containing Application.module() (rename to the user's actual filename if different)Path: build.gradle.kts — appended ai.koog:koog-ktor:1.0.0 dependency lineCreate files if they don't exist. Do not respond with prose only. Finish 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