skip to content

Koog

You will learn JetBrains' Kotlin and JVM agent framework: agents driven by strategy graphs, tools and MCP integration, prompt executors, and the features and embedding support around them. Interviewers ask about Koog on JVM teams that want agents inside their existing stack instead of standing up a separate Python service.

part ofAI agent & RAG frameworksoverview, primer and where to startread it →
on this pageshow

explore

questions

17

In Koog, when do you need a custom strategy graph instead of a plain AIAgent?

level: juniorimportance: must knowfreq 72%

answer

  1. Two entry points, not one
  2. Constructor first, graph only when needed
  3. Default loop already runs tools
  4. Graph is for branching and custom steps
  5. Typed nodes and edges, checked at compile time

basics

~20 s

A plain Koog AIAgent already runs a built-in loop: send the prompt, execute any tool calls, return the final message. You pass a strategy when you need explicit control flow — branching, custom Kotlin steps, history compression, or subgraphs.

solid answer

~40 s

Koog's `AIAgent` constructor takes a `promptExecutor`, an `llmModel`, a `systemPrompt`, an optional `toolRegistry` and a `maxIterations` cap. Built that way it uses a default single-run behaviour: it sends the user input, and if the model asks for tools it runs them, feeds the results back, and keeps going until the model returns a plain assistant message, which `agent.run(input)` gives you as the result. That covers most "ask, maybe call tools, answer" cases. You pass `strategy = ...` when the control flow is your business rather than the model's: routing to different nodes depending on what came back, running deterministic Kotlin between LLM calls, compressing history part-way through, restricting tools per phase with subgraphs, or switching models mid-run. The tradeoff is that you then own every path to `nodeFinish`.

code

kotlin · 9 lines
kotlin
val agent = AIAgent(
    promptExecutor = simpleOpenAIExecutor(apiKey),
    llmModel = OpenAIModels.Chat.GPT4o,
    systemPrompt = "You are a helpful assistant.",
    toolRegistry = toolRegistry,
    maxIterations = 30,
)

val answer: String = agent.run("Summarise ticket PROJ-142")

go deeper

for a junior

Know that you can construct an AIAgent with a promptExecutor, a model, a system prompt and a tool registry, then call run(input) — and that this already handles tool calls without any graph.

for a middle

Be ready to name what a strategy graph adds over the default: branching on the model's response, deterministic Kotlin nodes, history compression, and per-phase tool sets, plus the fact that edges are type-checked at compile time.

for a senior

Show judgment about when the explicit graph earns its maintenance cost, and admit what it hands you: responsibility for every path to nodeFinish, and a run that dies on maxIterations when you get that wrong.

for a principal

Own the framing that control flow encoded in a compiled graph is auditable and testable while control flow encoded in prompt instructions is neither, and weigh that against the graph becoming a second source of truth teams must keep in sync with the prompt.

## Two ways to stand up a Koog agent Koog is JetBrains' Kotlin/JVM agent framework, so everything here is Kotlin code compiled with your application rather than a Python script. It gives you two entry points at very different levels of ceremony. The first is the `AIAgent` constructor on its own. You supply a `promptExecutor` (the object that actually talks to a model provider), an `llmModel`, a `systemPrompt`, optionally a `toolRegistry` holding the tools the model may call, and `maxIterations` as a safety cap. Then you call `agent.run(input)` — a suspending call that returns the agent's final answer. The second is to also pass `strategy = ...`, where the strategy is built with Koog's `strategy("name") { ... }` DSL: a directed graph of nodes and edges you declare yourself. ## What the default behaviour already does for you Without a strategy the agent is not "one LLM call and stop". It runs the standard tool loop: it sends the prompt; if the model responds with tool calls, the agent looks each tool up in the `toolRegistry`, executes it, sends the results back to the model, and repeats; when the model finally returns an ordinary assistant message, that message is the run's result. So tools work perfectly well with no strategy at all — a common misconception in interviews is that you must hand-write a graph to get tool calling. That default covers a surprising share of real work: a support-triage agent, a code-explainer, an agent with five tools that answers a question. If you cannot name a decision the framework is making wrongly for you, you do not need a graph. ## What a strategy graph buys A strategy is where the loop stops being implicit. Inside `strategy("name") { }` you declare nodes as delegated properties — `val callLLM by nodeLLMRequest()`, `val runTool by nodeExecuteTool()`, `val sendResult by nodeLLMSendToolResult()` — and connect them with `edge(a forwardTo b)`, optionally guarded by conditions such as `onToolCall { }` or `onAssistantMessage { }`. `nodeStart` and `nodeFinish` are provided as the graph's entry and exit. The things that genuinely require this: - **Branching on what came back.** Route to a validation node when the model returns structured output, and to a retry node otherwise. - **Deterministic Kotlin between model calls.** A custom `node("name") { input -> output }` runs ordinary code — hit a database, normalise a payload, call an existing service — as a first-class graph step rather than as a tool the model has to choose to call. - **History management.** Dropping a history-compression node in the middle of a long loop is something the default loop will not do for you. - **Phases with different tool sets.** Subgraphs let a research phase see search tools and a writing phase see none. - **Multi-model runs.** Cheap model for extraction, expensive model for synthesis, chosen by edges. ## What it costs Explicit control flow means explicit responsibility. Every node needs a reachable path to `nodeFinish`; if the exit condition is missing or shadowed by an earlier edge, the graph loops until `maxIterations` trips and the run fails instead of returning a half-answer. You also write and maintain more code, and the graph is a second place where behaviour lives alongside the prompt. ## The compile-time angle The reason Koog appeals to JVM teams is that the graph is typed. Each node has an input and an output type, and an edge only compiles if the upstream output feeds the downstream input; `transformed { }` on an edge adapts one to the other. A wiring mistake is a red squiggle in the IDE, not a runtime surprise in production — which is the usual selling point versus dynamically typed agent frameworks. ## How to answer this in an interview Say plainly that the default agent is not a toy: it already does prompt, tools, answer. Then name one or two concrete reasons you reached for a graph, and admit the cost — you now own termination. Candidates who claim every Koog agent needs a hand-written strategy are describing docs they skimmed, not code they shipped.

  • If the default agent already runs tools, what is the first symptom that tells you to move to a strategy graph?
    When you start smuggling control flow into the prompt — telling the model "first do X, then always validate, then stop" and hoping it complies. Instruction-shaped control flow is unreliable and unobservable. A strategy graph makes those steps edges the runtime enforces, so they either happen or fail loudly, and you can see which node ran.
  • What does maxIterations protect you from in either mode?
    It caps how many iterations the agent takes before Koog aborts the run. In the default loop it stops a model that keeps requesting tools forever; in a strategy graph it also catches a wiring bug where no edge ever reaches `nodeFinish`. It is a safety net against a non-terminating run, not a token or cost budget.
  • Why do JVM teams pick Koog over standing up a separate Python agent service?
    The agent is ordinary Kotlin in the same process as the rest of the system, so it reuses existing services, DI, config, logging and deployment rather than adding a second runtime, a second deploy pipeline and a network hop. The typed strategy DSL also means wiring errors surface at compile time.

saying these in an interview costs you the question

  • Thinks every Koog agent needs a hand-written strategy graph
  • Claims an AIAgent without a strategy cannot call tools
  • Describes the strategy as just a prompt template
  • Says the default agent does exactly one LLM call
  • Assumes graph wiring errors only appear at runtime

context

open as a page

How do you build a prompt with Koog's prompt("id") { } DSL, and what is the id for?

level: juniorimportance: must knowfreq 60%

basics

~20 s

Koog's prompt("id") { } builder returns an immutable Prompt made of typed messages added by system(), user() and assistant() calls. The id is a stable label for that prompt, used to identify it in logs, traces and tooling — not sent as content.

open as a page

In Koog, what does ToolRegistry do and what breaks if a tool is missing?

level: juniorimportance: must knowfreq 70%

basics

~20 s

ToolRegistry is the single list of tools a Koog agent may call. Koog sends each registered tool's name, description and parameter schema with the prompt, then resolves incoming calls back to Kotlin. An unregistered tool is invisible and unresolvable.

open as a page

In a Koog strategy graph, which edges close the LLM tool-calling loop?

level: middleimportance: must knowfreq 66%

basics

~10 s

Send nodeLLMRequest to nodeExecuteTool on onToolCall, nodeExecuteTool straight to nodeLLMSendToolResult, and nodeLLMSendToolResult back to nodeExecuteTool on onToolCall. Both LLM nodes also need an onAssistantMessage edge to nodeFinish, or the loop never exits.

open as a page

In Koog, what does a PromptExecutor do, and how does MultiLLMPromptExecutor differ?

level: middleimportance: must knowfreq 72%

basics

~20 s

A PromptExecutor is Koog's single suspending entry point for running a Prompt against an LLModel, hiding provider HTTP details behind an LLMClient. SingleLLMPromptExecutor wraps one client; MultiLLMPromptExecutor holds several and routes each call by the model's provider.

open as a page

In Koog, how do @Tool and @LLMDescription turn a Kotlin function into an LLM tool?

level: middleimportance: must knowfreq 75%

basics

~20 s

Put the functions in a class implementing ToolSet, mark each with @Tool, and describe the function and every parameter with @LLMDescription. asTools() reflects over the class and builds a tool descriptor per function, mapping Kotlin parameter types to the JSON schema the model is given.

open as a page

What does Koog's Spring Boot starter auto-configure, and how do you use it in a service?

level: seniorimportance: must knowfreq 55%

basics

~20 s

Koog's Spring Boot starter reads provider settings under ai.koog in your application config, builds an LLM client for each configured provider, and exposes a ready PromptExecutor bean you inject. It wires model access only — strategies, tools and session state stay yours.

open as a page

In Koog, what happens when two edges from the same node both match?

level: middleimportance: should knowfreq 40%

basics

~20 s

Koog takes the first matching edge in declaration order and ignores the rest. A broad or unconditional edge declared early therefore shadows every more specific edge below it, which silently makes those branches dead code.

open as a page

In a Koog strategy, what do nodeStart and nodeFinish define?

level: middleimportance: should knowfreq 48%

basics

~10 s

They are the graph's typed boundary. nodeStart's output is whatever you pass to agent.run, and nodeFinish's input is what the run returns. Every other node must connect between them with types the compiler accepts.

open as a page

In Koog, when do you install EventHandler versus the OpenTelemetry feature on an agent?

level: middleimportance: should knowfreq 46%

basics

~20 s

EventHandler gives you in-process Kotlin callbacks on agent lifecycle points — tool calls, model calls, errors — for custom logic like metrics or audit records. The OpenTelemetry feature emits spans through the OTel SDK so runs appear in your existing tracing backend. They compose; install both.

open as a page

When would you implement Koog's SimpleTool or Tool instead of annotating a function?

level: middleimportance: should knowfreq 45%

basics

~20 s

Subclass when reflection is not enough: you need to hand-write the ToolDescriptor, build tools at runtime rather than at compile time, hold dependencies or state in the tool object, or return a typed ToolResult instead of a string.

open as a page

What makes a Koog agent hit its maxIterations limit mid-run?

level: seniorimportance: should knowfreq 44%

basics

~20 s

Usually a graph that cannot reach nodeFinish — a missing or shadowed assistant-message exit edge — or a model stuck re-calling a tool that keeps failing. Koog aborts the run rather than returning a partial answer.

open as a page

What does Koog's Persistency feature checkpoint, and what does a rollback not undo?

level: seniorimportance: should knowfreq 42%

basics

~20 s

Installing Koog's Persistency feature snapshots an agent run's execution state — its message history and where execution stood — into a storage provider, so a run can resume or roll back. It restores agent state only: side effects tools already performed stay done.

open as a page

In Koog, how does McpToolRegistryProvider expose an external MCP server's tools?

level: seniorimportance: should knowfreq 55%

basics

~20 s

McpToolRegistryProvider connects to an MCP server over a transport, reads the tools that server advertises, and wraps each one as a Koog tool inside a ToolRegistry you merge with your own. The tool list is captured when you build the registry, not per call.

open as a page

When is embedding Koog agents in your existing JVM service better than a separate agent service?

level: principalimportance: should knowfreq 32%

basics

~20 s

Embed when the agent mostly orchestrates systems your service already owns: tools become typed Kotlin calls into existing code, with one deploy, auth and observability story. Split it out when the agent's scaling curve, deploy cadence or dependencies diverge from your API's.

open as a page

How do you decide which tools a Koog agent's ToolRegistry exposes as the set grows?

level: principalimportance: should knowfreq 32%

basics

~20 s

Treat the registry as a reviewed API surface, not a bucket. Every entry costs prompt budget on each request and widens what the model can reach, so scope registries per agent, filter imported MCP tools, and validate arguments inside the tool rather than trusting the schema.

open as a page

When would you split a Koog strategy into subgraphs instead of one graph?

level: principalimportance: nice to knowfreq 33%

basics

~20 s

Split when the run has distinct phases with different tool sets, different termination rules, or different owners. A subgraph is a typed unit with its own nodes, edges and boundary, so it composes and can be tested and reasoned about alone.

open as a page