skip to content

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