{"slug": "thursdays-with-koog-tools", "title": "Thursdays with Koog: Tools", "summary": "Koog's Knosh 0.3.0 offers a class-based approach for defining tools, where developers extend Tool or SimpleTool and use LLMDescription annotations to guide the LLM, as demonstrated by the CreateDirectoryTool example.", "body_md": "A coding agent usually is only as good as its tools and how well the LLM can use those tools. Which begs the question: how do tools work, anyway?\n\nThere are several steps for tools:\n\nFrom our standpoint as consumers of Koog, Koog handles most of the dirty work. We tell Koog what tools we offer, and Koog invokes those as needed based on LLM requests. Beyond that, the rest of the work is internal to the tools and tends to be specific to your particular Koog-using program. A coding agent will have different tools (doing different things) than will a song lyric generator, or a 3D printer model generator, or a customer service chatbot.\n\nIn Koog, there are a couple of high-level ways you can define tools.\n\nKnosh uses the [class-based approach](https://docs.koog.ai/tools/class-based-tools/). You can extend `Tool`\n\nor `SimpleTool`\n\n, the latter handling the common case where your return is a chunk of text to send back to the LLM. For example, here is the class declaration for [ CreateDirectoryTool in Knosh 0.3.0](https://codeberg.org/commonsguy/knosh/src/tag/0.3.0/lib/knosh-tools/src/main/kotlin/com/commonsware/knosh/tools/CreateDirectoryTool.kt):\n\n```\n/**\n * Tool that creates one or more directories, including all required parent directories. Each path is attempted\n * independently; failures are reported but do not stop the remaining creations.\n *\n * @param dispatcher the coroutine dispatcher used for file I/O\n * @param context the runtime context\n */\npublic class CreateDirectoryTool(private val dispatcher: CoroutineDispatcher, private val context: RuntimeContext) :\n  SimpleTool<CreateDirectoryTool.Args>(\n    argsType = typeToken<Args>(),\n    name = \"create-directory\",\n    description = DESCRIPTION,\n  ) {\n  ```\n\nThe `name` and `description` constructor parameters provide details to the LLM of what the tool is and under what circumstances it should be used. Here, you are not talking to a human user, but rather to the LLM itself. Hence, you don't need to be polite, and sometimes you do need to be a bit forceful:\n\n``` kotlin\nprivate val DESCRIPTION =\n  \"\"\"\n  You MUST use this tool when you need to create directories on the filesystem. You do not have access to a shell, Bash, or any command-line tools — do not attempt to create directories by any other means.\n\n  Usage:\n  - Parent directories are created automatically as needed (equivalent to `mkdir -p`).\n  - Paths must be absolute. Relative paths are resolved against the current\n    working directory.\n  - If a directory already exists at a given path, that path is treated as a\n    success (idempotent).\n  - If a directory cannot be created (e.g. due to permissions), the failure is\n    noted and the tool continues with the remaining paths. All failures are\n    reported.\n  \"\"\"\n    .trimIndent()\n```\n\n`argsType`\n\nindicates what sorts of arguments your tool expects to be supplied by the LLM when it tries to invoke your tool. This can be any `@Serializable`\n\ntype, and you use the `LLMDescription`\n\nannotation to provide descriptive information to the LLM of the role of each argument:\n\n```\n /**\n   * Arguments for [CreateDirectoryTool].\n   *\n   * @property paths the list of filesystem paths at which to create directories\n   */\n  @Serializable\n  @Poko\n  public class Args(\n    @property:LLMDescription(\n      \"REQUIRED. The list of filesystem paths at which to create directories. \" +\n        \"Paths must be absolute. Relative paths are resolved against the current working directory. \" +\n        \"Parent directories are created automatically.\"\n    )\n    public val paths: List<String>\n  ) : HasFilesystemPaths {\n    override fun getFilesystemPaths(): List<String> = paths\n  }\n```\n\nIn this case, `HasFilesystemPaths`\n\nis a Knosh interface, denoting argument types that contain filesystem paths and providing those paths on demand. This is tied to how Knosh secures its tools, which we will see in a future issue.\n\nYour business logic goes in an `override suspend fun execute(args: Args): String`\n\nfunction on your `SimpleTool`\n\nsubtype. Your job is to take those arguments, do something fun, and return a `String`\n\nthat will get passed back to the LLM. This response can have different content (e.g., success vs. failure) and even different formats (e.g., JSON for success, Markdown for failure).\n\nIn many cases, you can get away with using Koog's [annotation-based tools](https://docs.koog.ai/tools/annotation-based-tools/), where you can define a tool as a simple function by means of the `@Tool`\n\nannotation:\n\n```\n@Tool\n@LLMDescription(\"Processes input data\")\nfun processTool(\n    @LLMDescription(\"The input data to process\")\n    input: String,\n\n    @LLMDescription(\"Optional configuration parameters\")\n    config: String = \"\"\n): String {\n    // Function implementation\n    return \"Processed: $input with config: $config\"\n}\n```\n\n(the above sample is from the Koog documentation)\n\nThese functions go on some implementation of the `ToolSet`\n\ninterface:\n\n```\n@LLMDescription(\"Tools for getting weather information\")\nclass MyFirstToolSet : ToolSet {\n    @Tool\n    @LLMDescription(\"Get the current weather for a location\")\n    fun getWeather(\n        @LLMDescription(\"The city and state/country\")\n        location: String\n    ): String {\n        // In a real implementation, you would call a weather API\n        return \"The weather in $location is sunny and 72°F\"\n    }\n}\n```\n\n(the above sample is from the Koog documentation)\n\nTeaching Koog about your tools is a matter of combining them into a `ToolRegistry`\n\n, then passing that registry to your `AIAgent`\n\nvia a constructor parameter:\n\n```\n// Agent initialization\nval agent = AIAgent(\n    promptExecutor = simpleOpenAIExecutor(System.getenv(\"OPENAI_API_KEY\")),\n    systemPrompt = \"You are a helpful assistant with strong mathematical skills.\",\n    llmModel = OpenAIModels.Chat.GPT4o,\n    // Pass your tool registry to the agent\n    toolRegistry = ToolRegistry {\n        tools(toolOne, toolTwo, toolTime)\n    }\n)\n```\n\n(the above sample is based on the Koog documentation)\n\nHere, `toolOne`\n\n, `toolTwo`\n\n, and `toolTime`\n\nare instances of your `ToolSet`\n\nimplementation (for the annotation-based tools) or your `Tool`\n\nsubclass.\n\nAt this point, Koog takes over. Your tool's function (e.g., `execute()`\n\non a `SimpleTool`\n\n) gets invoked when the LLM sends down a properly-formatted tool call, and your function's response gets sent back to the LLM as part of the prompt.\n\nAll that is necessary. It is unlikely to be *sufficient*. You may need to decide at runtime which tools are relevant, based on the circumstances in which you are creating your `AIAgent`\n\n. You **must** take steps to ensure that your tools are not abused by the LLM, lest [the LLM run amok](https://arstechnica.com/security/2026/07/jfrog-tries-to-spin-openai-0-day-exploit-of-its-app-into-a-success-story/). In a future issue, I will explain how Knosh decides which tools to offer an agent and how it defends against LLM-based attacks.\n\nBut, next week, I want to talk a bit about metrics: how we can find out what the LLM is doing while it is chugging away on our prompt, and how we can find out details of the work, such as how many tokens were involved.", "url": "https://wpnews.pro/news/thursdays-with-koog-tools", "canonical_source": "https://pac.commonsware.com/archive/thursdays-with-koog-tools/", "published_at": "2026-07-30 13:00:00+00:00", "updated_at": "2026-08-03 07:30:23.143326+00:00", "lang": "en", "topics": ["developer-tools", "large-language-models"], "entities": ["Koog", "Knosh", "CreateDirectoryTool"], "alternates": {"html": "https://wpnews.pro/news/thursdays-with-koog-tools", "markdown": "https://wpnews.pro/news/thursdays-with-koog-tools.md", "text": "https://wpnews.pro/news/thursdays-with-koog-tools.txt", "jsonld": "https://wpnews.pro/news/thursdays-with-koog-tools.jsonld"}}