Documentation Index
Fetch the complete documentation index at: https://mintlify.com/JetBrains/koog/llms.txt
Use this file to discover all available pages before exploring further.
Graph workflows allow you to define complex agent behaviors as state machines with nodes and edges. This is more powerful than the simple singleRunStrategy() for multi-step processes.
Why Graph Workflows?
Graph-based strategies give you:
- Explicit control flow — Define exact paths through your workflow
- Conditional branching — Route based on results, tool calls, or custom logic
- Subgraphs — Reuse and compose workflows
- Debugging — Visualize and test graph structure
Basic Graph Strategy
Creating a Strategy
Use the strategy DSL to define your workflow:
import ai.koog.agents.core.dsl.builder.strategy
import ai.koog.agents.core.dsl.builder.forwardTo
import ai.koog.agents.core.dsl.extension.*
fun toneStrategy(name: String) = strategy<String, String>(name) {
// Define nodes
val nodeSendInput by nodeLLMRequest()
val nodeExecuteTool by nodeExecuteTool()
val nodeSendToolResult by nodeLLMSendToolResult()
val nodeCompressHistory by nodeLLMCompressHistory<ReceivedToolResult>()
// Define edges (workflow)
edge(nodeStart forwardTo nodeSendInput)
// If LLM responds with text, finish
edge(
nodeSendInput forwardTo nodeFinish
onAssistantMessage { true }
)
// If LLM calls a tool, execute it
edge(
nodeSendInput forwardTo nodeExecuteTool
onToolCall { true }
)
// Compress history if needed
edge(
nodeExecuteTool forwardTo nodeCompressHistory
onCondition { llm.readSession { prompt.messages.size > 100 } }
)
edge(nodeCompressHistory forwardTo nodeSendToolResult)
// Send tool result back to LLM
edge(
nodeExecuteTool forwardTo nodeSendToolResult
onCondition { llm.readSession { prompt.messages.size <= 100 } }
)
// Continue if LLM calls another tool
edge(
nodeSendToolResult forwardTo nodeExecuteTool
onToolCall { true }
)
// Finish if LLM responds with text
edge(
nodeSendToolResult forwardTo nodeFinish
onAssistantMessage { true }
)
}
Using the Strategy
val agent = AIAgent(
promptExecutor = executor,
strategy = toneStrategy("tone_analysis"),
agentConfig = AIAgentConfig(
prompt = prompt("tone-agent") {
system("You are a tone analysis agent...")
},
model = OpenAIModels.Chat.GPT4oMini
),
toolRegistry = toolRegistry
)
Built-in Nodes
Koog provides pre-built nodes for common operations:
LLM Nodes
// Send request to LLM
val nodeLLMRequest by nodeLLMRequest()
// Send request and stream response
val nodeStreaming by nodeLLMRequestStreamingAndSendResults()
// Request structured output
val classifyRequest by nodeLLMRequestStructured<UserRequestClassification>()
// Send tool result back to LLM
val nodeSendToolResult by nodeLLMSendToolResult()
// Compress conversation history
val nodeCompressHistory by nodeLLMCompressHistory<ReceivedToolResult>()
// Execute a single tool
val nodeExecuteTool by nodeExecuteTool()
// Execute multiple tools in parallel
val executeMultipleTools by nodeExecuteMultipleTools(parallelTools = true)
Custom Nodes
Create custom processing nodes:
val mapStringToRequests by node<String, List<Message.Request>> { input ->
listOf(Message.User(content = input, metaInfo = RequestMetaInfo.Empty))
}
val processData by node<InputType, OutputType> { input ->
// Your custom logic
val result = processInput(input)
result
}
Conditional Edges
Basic Conditions
// Condition on result
edge(
nodeExecuteTool forwardTo nodeCompressHistory
onCondition { llm.readSession { prompt.messages.size > 100 } }
)
// Condition on message type
edge(
nodeSendInput forwardTo nodeFinish
onAssistantMessage { true }
)
edge(
nodeSendInput forwardTo nodeExecuteTool
onToolCall { true }
)
Type-based Routing
// Route based on sealed interface type
edge(
classifyRequest forwardTo askMoreInfo
transformed { it.getOrThrow().data }
onIsInstance JokeRequestClassification.NeedsClarification::class
)
edge(
classifyRequest forwardTo generateJoke
transformed { it.getOrThrow().data }
onIsInstance JokeRequestClassification.Ready::class
)
edge(
nodeStreaming forwardTo executeMultipleTools
onMultipleToolCalls { true }
)
Streaming with Graphs
Create agents that stream responses while executing tools:
fun streamingWithToolsStrategy() = strategy("streaming_loop") {
val executeMultipleTools by nodeExecuteMultipleTools(parallelTools = true)
val nodeStreaming by nodeLLMRequestStreamingAndSendResults()
val mapStringToRequests by node<String, List<Message.Request>> { input ->
listOf(Message.User(content = input, metaInfo = RequestMetaInfo.Empty))
}
val applyRequestToSession by node<List<Message.Request>, List<Message.Request>> { input ->
llm.writeSession {
appendPrompt {
input.filterIsInstance<Message.User>()
.forEach { user(it.content) }
tool {
input.filterIsInstance<Message.Tool.Result>()
.forEach { result(it) }
}
}
input
}
}
val mapToolCallsToRequests by node<List<ReceivedToolResult>, List<Message.Request>> { input ->
input.map { it.toMessage() }
}
// Define flow
edge(nodeStart forwardTo mapStringToRequests)
edge(mapStringToRequests forwardTo applyRequestToSession)
edge(applyRequestToSession forwardTo nodeStreaming)
edge(nodeStreaming forwardTo executeMultipleTools onMultipleToolCalls { true })
edge(executeMultipleTools forwardTo mapToolCallsToRequests)
edge(mapToolCallsToRequests forwardTo applyRequestToSession)
edge(
nodeStreaming forwardTo nodeFinish onCondition {
it.filterIsInstance<Message.Tool.Call>().isEmpty()
}
)
}
Subgraphs
Reuse workflows as subgraphs within larger strategies:
import ai.koog.agents.ext.agent.subgraphWithTask
import ai.koog.agents.ext.agent.subgraphWithVerification
fun wizardStrategy(
generateTools: List<Tool<*, *>>,
verifyTools: List<Tool<*, *>>,
fixTools: List<Tool<*, *>>
) = strategy<String, String>("wizard-with-checkstyle") {
// Subgraph: Generate project structure
val generate by subgraphWithTask<Unit, String>(
tools = generateTools,
llmModel = OpenAIModels.Chat.GPT4o,
) { input ->
"""
You are an AI agent that creates files and folders.
Create all necessary files and folders for the project.
""".trimIndent()
}
// Subgraph: Fix issues
val fix by subgraphWithTask<CriticResult<String>, String>(
tools = fixTools,
llmModel = AnthropicModels.Opus_4_6,
) { verificationResult ->
"""
Fix the following problems:
${verificationResult.feedback}
""".trimIndent()
}
// Subgraph: Verify project
val verify by subgraphWithVerification(verifyTools) { input: String ->
"""
Verify the created project by running build commands.
You can only read files and run shell commands.
""".trimIndent()
}
// Connect subgraphs
edge(nodeStart forwardTo generate transformed { })
edge(generate forwardTo verify transformed { "Project generated" })
edge(verify forwardTo fix onCondition { !it.successful })
edge(verify forwardTo nodeFinish onCondition { it.successful } transformed { "Project correct" })
edge(fix forwardTo verify)
}
Accessing Graph Context
Within nodes, access the agent’s state:
val processInput by node<String, ProcessedData> { input ->
// Access original agent input
val originalInput = agentInput<A2AMessage>()
// Read/write LLM session
llm.writeSession {
appendPrompt {
user("Process: $input")
}
}
val history = llm.readSession { prompt.messages }
// Call custom logic
processData(input, history)
}
Transform data as it flows between nodes:
// Transform output before passing to next node
edge(
setupTaskContext forwardTo classifyRequest
onCondition { task -> task == null }
transformed { agentInput<A2AMessage>().content() }
)
// Extract nested data
edge(
classifyRequest forwardTo generateJoke
transformed { it.getOrThrow().data }
onIsInstance JokeRequestClassification.Ready::class
)
Shorthand Syntax
Simplify sequential edges:
// Instead of:
edge(nodeStart forwardTo nodeA)
edge(nodeA forwardTo nodeB)
// Use:
nodeStart then nodeA then nodeB
Testing Graph Structure
Validate your graph’s structure in tests:
import ai.koog.agents.testing.feature.withTesting
val agent = AIAgent(
promptExecutor = mockExecutor,
strategy = myStrategy(),
agentConfig = config,
toolRegistry = toolRegistry
) {
withTesting()
testGraph("verify_structure") {
val firstSubgraph = assertSubgraphByName<String, String>("first")
val secondSubgraph = assertSubgraphByName<String, String>("second")
assertEdges {
startNode() alwaysGoesTo firstSubgraph
firstSubgraph alwaysGoesTo secondSubgraph
}
verifySubgraph(firstSubgraph) {
val askLLM = assertNodeByName<String, Message.Response>("callLLM")
assertNodes {
askLLM withInput "Hello" outputs Message.Assistant("Hello!")
}
}
}
}
Best Practices
Clear Node Names
Use descriptive names for debugging:
val loadUserContext by node<String, UserContext> { ... }
val validatePermissions by node<UserContext, ValidationResult> { ... }
val executeAction by node<ValidationResult, ActionResult> { ... }
Error Handling
Add explicit error paths:
val handleError by node<Throwable, String> { error ->
"Error occurred: ${error.message}"
}
edge(
riskyNode forwardTo handleError
onCondition { it is Failure }
)
Separate Concerns
Keep nodes focused on single responsibilities:
// Good: Each node has one job
val fetchData by node<Request, Data> { ... }
val validateData by node<Data, ValidationResult> { ... }
val processData by node<Data, Result> { ... }
// Avoid: One node doing everything
val doEverything by node<Request, Result> { ... }
Next Steps
Complete Example
A full graph-based agent with conditional routing:
import ai.koog.agents.core.agent.AIAgent
import ai.koog.agents.core.dsl.builder.*
import ai.koog.agents.core.dsl.extension.*
val strategy = strategy<String, String>("tone_analyzer") {
val nodeSendInput by nodeLLMRequest()
val nodeExecuteTool by nodeExecuteTool()
val nodeSendToolResult by nodeLLMSendToolResult()
val nodeCompressHistory by nodeLLMCompressHistory<ReceivedToolResult>()
edge(nodeStart forwardTo nodeSendInput)
edge(
nodeSendInput forwardTo nodeFinish
onAssistantMessage { true }
)
edge(
nodeSendInput forwardTo nodeExecuteTool
onToolCall { true }
)
edge(
nodeExecuteTool forwardTo nodeCompressHistory
onCondition { llm.readSession { prompt.messages.size > 100 } }
)
edge(nodeCompressHistory forwardTo nodeSendToolResult)
edge(
nodeExecuteTool forwardTo nodeSendToolResult
onCondition { llm.readSession { prompt.messages.size <= 100 } }
)
edge(
nodeSendToolResult forwardTo nodeExecuteTool
onToolCall { true }
)
edge(
nodeSendToolResult forwardTo nodeFinish
onAssistantMessage { true }
)
}
val agent = AIAgent(
promptExecutor = executor,
strategy = strategy,
llmModel = OpenAIModels.Chat.GPT4oMini,
systemPrompt = "Analyze text tone using available tools.",
toolRegistry = toolRegistry
)