{"slug": "thursdays-with-koog-strategies", "title": "Thursdays with Koog: Strategies", "summary": "Koog's AIAgent delegates its work to an AIAgentStrategy interface, with the default singleRunStrategy() factory function implementing a one-shot graph pattern that calls the LLM, executes tools, and repeats until the LLM stops requesting tool calls. The strategy DSL defines nodes and edges, but nodes are synchronous suspend functions, making multi-turn agents awkward; developers can instead iteratively call run() on an AIAgent using singleRunStrategy() and manage the prompt themselves.", "body_md": "In [a past \"Thursdays with Koog\" issue](https://pac.commonsware.com/archive/thursdays-with-koog-tools/), we saw that Koog's `AIAgent`\n\nknows how to handle tool calls for us. We can provide a prompt and get the final response from the LLM, and any tool calls the LLM requests in between those are handled by the agent.\n\nIn truth, the `AIAgent`\n\nis *involved* in that logic, but the real work is handled by a strategy implementation.\n\nKoog has an `AIAgentStrategy`\n\ninterface with a bunch of sub-interfaces and implementations. In addition to a `name`\n\n, `AIAgentStrategy`\n\ndefines `execute()`\n\n, which `AIAgent`\n\nitself wraps via its `run()`\n\nfunction, with a bit of additional indirection. `execute()`\n\ndoes the work. So, in the end, the strategy is what orchestrates the work with the LLM, and since the strategy is defined by an interface, we can plug in other strategies as needed.\n\nThe default strategy is built by the `singleRunStrategy()`\n\nfactory function. [Its KDoc](https://api.koog.ai/agents/agents-core/ai.koog.agents.core.agent/single-run-strategy.html?query=fun%20singleRunStrategy(parallelTools:%20Boolean%20=%20false):%20AIAgentGraphStrategy%3CString,%20String%3E) shows the flow that Knosh uses:\n\n- Start the agent.\n- Call the LLM with the input.\n- Execute a tool based on the LLM's response.\n- Send the tool result back to the LLM.\n- Repeat until LLM indicates no further tool calls are needed or the agent finishes.\n\nBasically, `singleRunStrategy()`\n\nis your \"one-shot\" pattern.\n\nUnder the covers, `singleRunStrategy()`\n\nis a one-off implementation of a graph strategy:\n\n```\n@JvmOverloads\npublic fun singleRunStrategy(parallelTools: Boolean = false): AIAgentGraphStrategy<String, String> = strategy<String, String>(\"single_run\") {\n    val nodeCallLLM by nodeLLMRequest()\n    val nodeExecuteTool by nodeExecuteTools()\n    val nodeSendToolResult by nodeLLMSendToolResults()\n\n    edge(nodeStart forwardTo nodeCallLLM)\n    edge(nodeCallLLM forwardTo nodeExecuteTool onToolCalls { true })\n    edge(nodeCallLLM forwardTo nodeFinish onTextMessage { true })\n    edge(nodeExecuteTool forwardTo nodeSendToolResult)\n    edge(nodeSendToolResult forwardTo nodeFinish onTextMessage { true })\n    edge(nodeSendToolResult forwardTo nodeExecuteTool onToolCalls { true })\n}\n```\n\n(the above is from Koog 1.1.1)\n\nThis defines an object graph, with `edge()`\n\nconnecting two processing nodes. The interaction begins at `nodeStart`\n\nand flows through the graph, eventually terminating at `nodeFinish`\n\n. The `strategy()`\n\nbuilder offers a DSL for defining the graph, designed to make the code read almost like plain English:\n\nStrategies can get a *lot* more elaborate:\n\nThe bad news is that nodes are synchronous, insofar as they implement a simple `execute()`\n\nfunction (often defined by a DSL-supplied lambda expression). `execute()`\n\nis a `suspend fun`\n\n, so you can do I/O and stuff, but in the end, `execute()`\n\nneeds to return whatever the graph needs to continue.\n\nA side effect of this approach is that creating a multi-turn agent, with several rounds of user input, gets weird. Effectively, you build a graph where you have a node that takes the latest message from the LLM and *synchronously* returns the next input from the user. That node can leverage coroutines, such as posting to a `Flow`\n\nor `Channel`\n\nand observing a `Flow`\n\nor `Channel`\n\nto get the response, but it needs to be able to return the user's message, which your graph can then process. Your UI needs to be conducive to such an arrangement, and that might take a while to \"get your head wrapped around\".\n\nYou don't *have* to use custom graphs. Knosh doesn't. You can create a multi-turn agent just by iteratively calling `run()`\n\non your `AIAgent`\n\nset up to use `singleRunStrategy()`\n\n. Managing the overall prompt becomes your job: for your second turn, you need to determine how to combine your original prompt with the result from the LLM and (presumably) fresh input from the user, but without all of the tool responses (which were \"consumed\" in the process of giving you the LLM result). Personally, and off the cuff, I would not mind this and might prefer it to trying to twist my overall app to be visible just via a graph node. But, you have options.\n\nNext week's \"Thursdays with Koog\" will explore some other Koog \"knobs we can turn and switches we can flip\", such as the maximum number of iterations to use, the temperature, and more.", "url": "https://wpnews.pro/news/thursdays-with-koog-strategies", "canonical_source": "https://pac.commonsware.com/archive/thursdays-with-koog-strategies/", "published_at": "2026-08-13 13:00:00+00:00", "updated_at": "2026-08-13 13:11:02.148481+00:00", "lang": "en", "topics": ["artificial-intelligence", "ai-agents", "developer-tools"], "entities": ["Koog", "AIAgent", "AIAgentStrategy", "singleRunStrategy", "Knosh"], "alternates": {"html": "https://wpnews.pro/news/thursdays-with-koog-strategies", "markdown": "https://wpnews.pro/news/thursdays-with-koog-strategies.md", "text": "https://wpnews.pro/news/thursdays-with-koog-strategies.txt", "jsonld": "https://wpnews.pro/news/thursdays-with-koog-strategies.jsonld"}}