17. March 2026 By Fabian Rump
Building AI agents with Koog – A cross-platform practical introduction with Kotlin and Compose Multiplatform
Artificial intelligence is no longer purely a backend issue that only takes place on huge server farms. More and more applications are emerging that work directly on the end device with large language models (LLMs) or orchestrate them in a targeted manner. But anyone who has ever tried to squeeze the creative output of an AI into rigid app logic knows the problem: a prompt is far from being a reliable service.
This is exactly where Koog comes in. JetBrains' Kotlin-based library promises to make AI agents structured, reproducible and cross-platform. In this blog post, I'll take you through a small proof of concept (PoC) and show you how Koog, Kotlin and Compose Multiplatform can be used to build a useful agent that delivers results your code can actually do something with.
Why Koog?
You may be wondering: Do we really need another framework? The answer is yes, if we're talking about stability. Koog takes a clear approach: Not every prompt is an agent.
A real agent in a productive application needs more than just a text field and a response. It consists of:
- A clear task (mission).
- A defined structure (workflow).
- Controlled output (type safety).
Instead of unstructured text responses (‘Here's your plan, good luck!’), Koog can be used to generate reproducible results that can be processed directly in the app. This is particularly helpful for applications that go beyond simple chat UIs. Since Koog is part of the Kotlin ecosystem, the definition of agents feels immediately familiar to us Android and Kotlin developers – type-safe and idiomatic.
The scenario: A goal decomposition agent
To make the theory tangible, let's look at a concrete example. Instead of just recognising simple tasks or appointments, our agent takes it a step further. It is a goal decomposition agent. Its task: it takes an abstract, large goal and breaks it down into feasible, structured chunks.
Imagine a user enters:
‘I want to run a half marathon in six months.’
A normal chatbot would probably spit out a long stream of text with tips on running shoes and nutrition. Our Koog agent, on the other hand, returns a clearly defined result object that contains the following:
- 1. A validated main goal.
- 2. Several logical sub-goals (milestones).
- 3. Concrete actions to get started.
- 4. Possible limitations or risks.
- 5. A recommended first step.
The key point here is that the result is not Markdown text that we have to laboriously parse, but a JSON structure that is directly translated into a Kotlin data class.
The power of structure: type safety meets AI
The entire app works with a clear structure. For our example, we define a data class that maps the desired result. This could look something like this (simplified):
@Serializable
data class GoalDecomposition(
val mainGoal: String,
val milestones: List<String>,
val actions: List<Action>,
val constraints: List<String>,
val firstStep: String
)
This structure is deliberately kept simple. It is suitable for direct display in the UI as well as for later processing – for example, to save the sub-goals as real to-dos in a database or to enter them in the calendar.
The agent itself is defined very leanly in Koog. It essentially requires three ingredients:
- The model: Which LLM should be used? (GPT-4o, Claude 3.5 or a local model via Ollama).
- The system prompt: The instruction on how the agent should behave.
- The expected output format: Here we specify our GoalDecomposition class.
internal class GoalDecompositionAgent(
apiKey: String)
{
private val service = AIAgentService(
promptExecutor = simpleOpenRouterExecutor(apiKey = apiKey),
llmModel = OpenRouterModels.GPT4oMini,
systemPrompt = ‘’"
You are a minimalist assistant.
Break down a goal into Goal, Subgoals, Actions, Constraints and FirstStep.
Respond **exclusively** with valid JSON in the following format:
{
‘goal’: ‘Goal’,
‘subgoals’: [“Subgoal1”, ‘Subgoal2’],
‘actions’: [‘Action1’, ‘Action2’],
‘constraints’: [‘Constraint1’, ‘Constraint2’],
“firstStep”: ‘The first step’
}
‘’".trimIndent()
)
suspend fun analyse(input: String): GoalDecomposition = withContext(Dispatchers.Default) {
val response = service.createAgentAndRun(input)
val parsed = response.toGoalDecompositionSafe()
parsed ?: throw IllegalStateException(‘Could not parse JSON. Response: $response’)
}
}
The most important point here is validation. Koog (often in combination with the ‘structured output’ features of modern model providers) ensures that the agent only delivers valid JSON that exactly matches our data class.
This strict separation keeps the app stable. The UI doesn't have to guess whether the AI is hallucinating or has forgotten formatting characters. It always receives a valid object or a clear error. That's the difference between a gimmick and a robust application.
One codebase, many platforms: Compose Multiplatform
The front end of our PoC is implemented with Compose Multiplatform. The motto ‘one screen, one code path, multiple platforms’ fits perfectly with Koog's platform-independent approach.
The screen shows:
- An input field for the goal.
- A loading status (while Koog works in the background).
- Structured cards for the individual result areas (goals, actions, risks).
This is where the advantage of type safety is particularly evident: the UI does not work with strings or unsafe maps, but directly with the GoalDecomposition data class. We can iterate through lists, check types and show or hide UI elements based on their status (actions.isEmpty()).
Status quo: Where is it running?
The project from the example is currently:
- Android-ready
- Desktop-ready (JVM)
- iOS (not yet active in the setup)
Since both Koog and Compose Multiplatform rely heavily on Kotlin Multiplatform (KMP), iOS integration is absolutely realistic and one of the next logical steps. However, it currently requires additional adjustments, particularly in the area of network configuration and the integration of iOS-specific LLM clients.
The project can be found at https://gitlab.com/fabian-rump/smartassistant.
A look under the hood: What else Koog can do
Our example only scratches the surface. Koog offers many more tools for complex scenarios that are exciting for enterprise applications:
- Tools & Functions: You can provide the agent with real Kotlin functions as ‘tools’. The agent can then decide whether to call a function (getWeather(city: String)) to enrich its response.
- Graph-based strategies: For complex processes, the agent's decision-making process can be modelled as a graph. This prevents the agent from getting caught in an endless loop and makes its behaviour more deterministic.
- Testing: Since the agent is encapsulated in Kotlin code, it can be integrated much better into unit and integration tests than a pure string prompt.
Conclusion
Koog impressively demonstrates how AI agents can be meaningfully integrated into everyday development beyond chat UIs. In combination with Kotlin and Compose Multiplatform, applications are created that are structured, type-safe, cross-platform and, above all, easy to maintain.
The goal decomposition agent presented here is just a simple example. Agents for learning plans, automated project planning, decision-making in a business context or even code reflection are also conceivable.
Anyone who wants to actively and robustly design AI rather than just consume it should definitely take a look at Koog – it brings order to the chaos of language models that we need for professional software development.