koog Documentation

Koog is a JVM (Java and Kotlin) framework for building predictable, fault-tolerant and enterprise-ready AI agents across all platforms – from backend...

RAW Doc

Docker & Self-Hosted Deployment Verified OCI

Deploy koog on any server, homelab, or cloud container platform.

1-Click Terminal Command
bash (docker run)
docker run -d --name koog -p 4317:4317 --restart always jaegertracing/all-in-one:1.39
Configured Services & Ports
⚙️ jaeger-all-in-one
image: jaegertracing/all-in-one:1.39
🔌 Ports: 4317:4317, 16686:16686
docker-compose.yml
yaml (docker-compose.yml)
services:
  jaeger-all-in-one:
    image: jaegertracing/all-in-one:1.39
    container_name: jaeger-all-in-one
    environment:
      - COLLECTOR_OTLP_ENABLED=true
    ports:
      - "4317:4317"
      - "16686:16686"

1. Project Overview & Quickstart (JetBrains/koog)

File: README.md

Koog






Build status:



Useful links:

Overview

Koog is a Kotlin-based framework designed to build and run AI agents entirely in idiomatic Kotlin and Java API. It lets you create agents that can interact with tools, handle complex workflows, and communicate with users.

Key features

Key features of Koog include:

  • Multiplatform development: Deploy agents across JVM, JS, WasmJS, Android, and iOS targets using Kotlin Multiplatform.
  • Reliability and fault-tolerance: Handle failures with built-in retries and restore the agent state at specific points during execution with the agent persistence feature.
  • Intelligent history compression: Optimize token usage while maintaining context in long-running conversations using advanced built-in history compression techniques.
  • Enterprise-ready integrations: Utilize integration with popular JVM frameworks such as Spring Boot and Ktor to embed Koog into your applications.
  • Observability with OpenTelemetry exporters: Monitor and debug applications with built-in support for popular observability providers (W&B Weave, Langfuse).
  • LLM switching and seamless history adaptation: Switch to a different LLM at any point without losing the existing conversation history, or reroute between multiple LLM providers.
  • Integration with JVM and Kotlin applications: Build AI agents with an idiomatic, type-safe Kotlin DSL designed specifically for JVM and Kotlin developers.
  • Model Context Protocol integration: Use Model Context Protocol (MCP) tools in AI agents.
  • Agent Client Protocol integration: Build ACP-compliant agents that can communicate with standardized client applications using the Agent Client Protocol (ACP).
  • Knowledge retrieval and memory: Retain and retrieve knowledge across conversations using vector embeddings and RAG.
  • Powerful Streaming API: Process responses in real-time with streaming support and parallel tool calls.
  • Modular feature system: Customize agent capabilities through a composable architecture.
  • Flexible graph workflows: Design complex agent behaviors using intuitive graph-based workflows.
  • Custom tool creation: Enhance your agents with tools that access external systems and APIs.
  • Comprehensive tracing: Debug and monitor agent execution with detailed, configurable tracing.

Available LLM providers and platforms

The LLM providers and platforms whose LLMs you can use to power your agent capabilities:

  • Google
  • OpenAI
  • Anthropic
  • DeepSeek
  • OpenRouter
  • Ollama
  • Bedrock

Quickstart example

To help you get started with AI agents, here is a quick example:

kotlin
fun main() = runBlocking {
    // Before you run the example, assign a corresponding API key as an environment variable.
    val apiKey = System.getenv("OPENAI_API_KEY") // or Anthropic, Google, OpenRouter, etc.

    val agent = AIAgent(
        promptExecutor = MultiLLMPromptExecutor(OpenAILLMClient(apiKey)), // or Anthropic, Google, OpenRouter, etc.
        systemPrompt = "You are a helpful assistant. Answer user questions concisely.",
        llmModel = OpenAIModels.Chat.GPT4o
    )

    val result = agent.run("Hello! How can you help me?")
    println(result)
}

Using in your projects

Supported targets

Currently, the framework supports the JVM, JS, WasmJS and iOS targets.

Requirements

  • JDK 17 or higher is required to use the framework on JVM.
  • Kotlin 2.3.10 or higher should be set explicitly in existing projects. Please check the libs.versions.toml to know more about Kotlin dependencies (currently it uses kotlinx-coroutines 1.10.2, kotlinx-serialization 1.10.0 and kotlinx-datetime 0.7.1)

Gradle (Kotlin DSL)

  1. Add dependencies to the build.gradle.kts file:

    text
    dependencies {
        implementation("ai.koog:koog-agents:1.1.1")
        implementation("ai.koog:koog-agents-additions:1.1.1-beta")
    }
  2. Make sure that you have mavenCentral() in the list of repositories.

Gradle (Groovy)

  1. Add dependencies to the build.gradle file:

    text
    dependencies {
        implementation 'ai.koog:koog-agents:1.1.1'
        implementation 'ai.koog:koog-agents-additions:1.1.1-beta'
    }
  2. Make sure that you have mavenCentral() in the list of repositories.

Maven

  1. Add dependencies to the pom.xml file:

    text
    <dependency>
        <groupId>ai.koog</groupId>
        <artifactId>koog-agents-jvm</artifactId>
        <version>1.1.1</version>
    </dependency>
    <dependency>
        <groupId>ai.koog</groupId>
        <artifactId>koog-agents-additions-jvm</artifactId>
        <version>1.1.1-beta</version>
    </dependency>
  2. Make sure that you have mavenCentral in the list of repositories.

Versioning

Koog framework is stable and follows semantic versioning. See VERSIONING.md for details.

Contributing

Read the Contributing Guidelines.

Code of Conduct

This project and the corresponding community are governed by the JetBrains Open Source and Community Code of Conduct. Please make sure you read it.

License

Koog is licensed under the Apache 2.0 License.

Support

Please feel free to ask any questions in our official Slack
channel
and to
use Koog official YouTrack project
for filing feature requests and bug reports.


File: agents/agents-features/agents-features-snapshot/README.md

AgentCheckpoint

AgentCheckpoint is a feature that provides checkpoint functionality for AI agents in the Koog Agents framework. It allows saving and restoring the state of an agent at specific points during execution, enabling powerful capabilities such as:

  • Resuming agent execution from a specific point
  • Rolling back to previous states
  • Persisting agent state across sessions

Core Concepts

Checkpoints

A checkpoint captures the complete state of an agent at a specific point in its execution, including:

  • Message history (all interactions between user, system, assistant, and tools)
  • Current node being executed
  • Input data for the current node
  • Timestamp of creation

Checkpoints are identified by unique IDs and are associated with a specific agent.

Storage Providers

AgentCheckpoint uses storage providers to save and retrieve checkpoints. The framework includes several built-in providers:

  • InMemoryAgentCheckpointStorageProvider: Stores checkpoints in memory (lost when the application restarts)
  • FileAgentCheckpointStorageProvider: Persists checkpoints to the file system
  • NoAgentCheckpointStorageProvider: A no-op implementation (default) that doesn't store checkpoints

You can also implement custom storage providers by implementing the AgentCheckpointStorageProvider interface.

Continuous Persistence

The feature can be configured to automatically create checkpoints after each node execution, ensuring that the agent's state is continuously persisted and can be recovered at any point.

Installation

To use the AgentCheckpoint feature, you need to add it to your agent's configuration:

kotlin
val agent = aiAgent {
    // ... other configuration
    
    features {
        install(AgentCheckpoint) {
            // Configure the feature
            snapshotProvider(InMemoryAgentCheckpointStorageProvider())
            continuouslyPersistent() // Optional: create checkpoints after each node
        }
    }
}

Configuration Options

The AgentCheckpoint feature has two main configuration options:

Storage Provider

Set the storage provider that will be used to save and retrieve checkpoints:

kotlin
install(AgentCheckpoint) {
    snapshotProvider(InMemoryAgentCheckpointStorageProvider())
}

Available built-in providers:

  • InMemoryAgentCheckpointStorageProvider: In-memory storage (non-persistent)
  • FileAgentCheckpointStorageProvider: File-based storage
  • NoAgentCheckpointStorageProvider: No-op provider (default)

Continuous Persistence

Enable automatic checkpoint creation after each node execution:

kotlin
install(AgentCheckpoint) {
    continuouslyPersistent()
}

When enabled, the agent will automatically create a checkpoint after each node is executed, allowing for fine-grained recovery.

Basic Usage

Creating a Checkpoint

Create a checkpoint at a specific point in your agent's execution:

kotlin
suspend fun example(context: AIAgentContextBase) {
    // Create a checkpoint with the current state
    val checkpoint = context.checkpoint().createCheckpoint(
        agentId = context.id,
        agentContext = context,
        nodeId = "current-node-id",
        lastInput = inputData
    )
    
    // The checkpoint ID can be stored for later use
    val checkpointId = checkpoint.checkpointId
}

Restoring from a Checkpoint

Restore an agent's state from a specific checkpoint:

kotlin
suspend fun example(context: AIAgentContextBase, checkpointId: String) {
    // Roll back to a specific checkpoint
    context.checkpoint().rollbackToCheckpoint(checkpointId, context)
    
    // Or roll back to the latest checkpoint
    context.checkpoint().rollbackToLatestCheckpoint(context)
}

Using Extension Functions

The feature provides convenient extension functions for working with checkpoints:

kotlin
suspend fun example(context: AIAgentContextBase) {
    // Access the checkpoint feature
    val checkpointFeature = context.checkpoint()
    
    // Or perform an action with the checkpoint feature
    context.withCheckpoints(context) { ctx ->
        // 'this' is the checkpoint feature
        createCheckpoint(ctx.id, ctx, "node-id", inputData)
    }
}

Advanced Usage

Custom Storage Providers

You can implement custom storage providers by implementing the AgentCheckpointStorageProvider interface:

kotlin
class MyCustomStorageProvider : AgentCheckpointStorageProvider {
    override suspend fun getCheckpoints(agentId: String): List<AgentCheckpointData> {
        // Implementation
    }
    
    override suspend fun saveCheckpoint(agentId: String, agentCheckpointData: AgentCheckpointData) {
        // Implementation
    }
    
    override suspend fun getLatestCheckpoint(agentId: String): AgentCheckpointData? {
        // Implementation
    }
}

Then use your custom provider in the feature configuration:

kotlin
install(AgentCheckpoint) {
    snapshotProvider(MyCustomStorageProvider())
}

Setting Execution Points

For advanced control, you can directly set the execution point of an agent:

kotlin
fun example(context: AIAgentContextBase) {
    context.checkpoint().setExecutionPoint(
        agentContext = context,
        nodeId = "target-node-id",
        messageHistory = customMessageHistory,
        input = customInput
    )
}

This allows for more fine-grained control over the agent's state beyond just restoring from checkpoints.

Requirements

The AgentCheckpoint feature requires that all nodes in your agent's strategy have unique names. This is enforced when the feature is installed:

kotlin
require(ctx.strategy.metadata.uniqueNames) { 
    "Checkpoint feature requires unique node names in the strategy metadata" 
}

Make sure to set uniqueNames = true in your strategy metadata when using this feature.


File: agents/agents-features/agents-features-opentelemetry/README.md

OpenTelemetry support

This guide provides comprehensive instructions on how to use OpenTelemetry with the Koog agentic framework for tracing and monitoring your AI agents.

Overview

OpenTelemetry is an observability framework that provides tools for generating, collecting, and exporting telemetry data (metrics, logs, and traces) from your applications. The Koog OpenTelemetry feature allows you to instrument your AI agents to collect telemetry data, which can help you:

  • Monitor agent performance and behavior
  • Debug issues in complex agent workflows
  • Visualize the execution flow of your agents
  • Track LLM calls and tool usage
  • Count tokens (input, output, and total) for LLM calls to monitor usage and costs
  • Analyze agent behavior patterns

Key OpenTelemetry concepts

  • Spans: Spans represent individual units of work or operations within a distributed trace. They indicate the beginning and end of a specific activity in an application, such as an agent execution, a function call, an LLM call, or a tool call.
  • Exporters: Exporters are components responsible for sending the collected telemetry data (spans, metrics, logs) to various backends or destinations.
  • Collectors: An OpenTelemetry Collector receives, processes, and exports telemetry data. It acts as an intermediary between your applications and your observability backend.

The OpenTelemetry feature in Koog automatically creates spans for various agent events, including:

  • Agent creation and invocation
  • Strategy execution
  • Node execution
  • LLM calls (inference)
  • Tool calls

Installation

To use OpenTelemetry with Koog, add the OpenTelemetry feature to your agent:

kotlin
val agent = AIAgent(
    executor = simpleOpenAIExecutor(apiKey),
    llmModel = OpenAIModels.Chat.GPT4o,
    systemPrompt = "You are a helpful assistant.",
    installFeatures = {
        install(OpenTelemetry) {
            // Configuration options here
        }
    }
)

Configuration

Basic configuration

Here is an example of installing the OpenTelemetry feature with a basic set of configuration items:

kotlin
import ai.koog.agents.features.opentelemetry.feature.OpenTelemetry
import io.opentelemetry.exporter.logging.LoggingSpanExporter

install(OpenTelemetry) {
    setServiceInfo("my-agent-service", "1.0.0")    // Set your service configuration
    addSpanExporter(LoggingSpanExporter.create())  // Java SDK exporter is bridged automatically on JVM
}

The example includes the following configuration methods:

Name Data type Required Description
setServiceInfo Unit No Sets the information about the service being instrumented, including service name and version.
addSpanExporter Unit No Registers an exporter wrapped in a [batchSpanProcessor][batchSpanProcessor] (OTel-recommended).

Please see below the full list of available configuration properties:

Name Data type Default value Description
serviceName String ai.koog The name of the service being instrumented.
serviceVersion String 0.0.0 The version of the service being instrumented.
isVerbose Boolean false Whether to enable verbose logging for debugging OpenTelemetry configuration.
isShutdownOnAgentClose Boolean false Whether the SDK is shut down when the owning agent closes.
tracer Tracer Kotlin SDK [Tracer][Tracer] used for creating spans.

Configuration API:

Name Arguments Description
setServiceInfo serviceName: String, serviceVersion: String Sets the service information including name and version.
addSpanExporter exporter: SpanExporter (Kotlin SDK or Java SDK on JVM) Registers an exporter behind a batchSpanProcessor. On JVM the method is overloaded to accept Java-SDK exporters directly (bridged via the compat layer).
addSpanProcessor factory: TraceExportConfigDsl.() -> SpanProcessor Registers a custom span processor (e.g., simple/composite/custom-batched) via the SDK's DSL.
addResourceAttributes attributes: Map<String, Any> Adds resource attributes to provide additional context about the service. Supported value types: String, Long, Double, Boolean.
setVerbose verbose: Boolean Enables or disables verbose logging for debugging OpenTelemetry configuration.
setSdk openTelemetry: OpenTelemetry Injects a pre-built Kotlin OpenTelemetry SDK instance, bypassing internal configuration.
setShutdownOnAgentClose shutdownOnAgentClose: Boolean Toggles whether the SDK is shut down when the owning agent closes.

JVM-only members on [OpenTelemetryConfig][OpenTelemetryConfig] cover metrics (addMetricExporter, addMetricFilter, meter) — the Kotlin SDK 0.3.0 ships no metrics module, so metrics are sent through the Java SDK.

Advanced configuration

For more advanced configuration, you can customize resource attributes:

kotlin
install(OpenTelemetry) {
    setServiceInfo("my-agent-service", "1.0.0")    // Set your service configuration
    addSpanExporter(LoggingSpanExporter.create())  // Add Logging exporter

    // Add resource attributes (Map<String, Any>; supports String, Long, Double, Boolean)
    addResourceAttributes(mapOf(
        "deployment.environment" to "production",
        "custom.attribute" to "custom-value"
    ))
}

Custom span processors

addSpanExporter always wraps in batchSpanProcessor. For full control over the processor — custom batching parameters, simple per-span export for tests, or a composite processor over several inner processors — use addSpanProcessor and the SDK's DSL-scoped factory functions:

kotlin
import io.opentelemetry.kotlin.tracing.export.batchSpanProcessor
import io.opentelemetry.kotlin.tracing.export.simpleSpanProcessor
import io.opentelemetry.kotlin.tracing.export.stdoutSpanExporter

install(OpenTelemetry) {
    // Stdout exporter wrapped in a simple (synchronous) processor — useful for local debugging.
    addSpanProcessor { simpleSpanProcessor(stdoutSpanExporter()) }

    // Custom batch parameters (default: 5s schedule delay, 512 max batch size).
    addSpanProcessor { batchSpanProcessor(myExporter, scheduleDelayMs = 1000) }
}

Sdk configuration

If you already have an initialized [OpenTelemetry][OpenTelemetry] SDK instance — for example, configured elsewhere in your application or provided by a framework — you can pass it directly using setSdk. When using setSdk any other configuration methods like addSpanExporter, addSpanProcessor, addResourceAttributes are ignored, since the SDK is assumed to be fully configured.

kotlin
import io.opentelemetry.kotlin.OpenTelemetry
import io.opentelemetry.kotlin.createOpenTelemetry

val sdk: OpenTelemetry = createOpenTelemetry { /* … */ } // Your preconfigured SDK

install(OpenTelemetry) {
    setSdk(sdk) // Use your existing OpenTelemetry instance
}

Resource attributes

Resource attributes represent additional information about a process producing telemetry. This information can be
included in an OpenTelemetry configuration in Koog using the addResourceAttributes() method that takes a
Map<String, Any>. Supported value types are String, Long, Double, and Boolean.

The following default resource attributes are automatically added:

  • service.name: The name of the service being instrumented. Set to the value of serviceName.
  • service.version: The version of the service being instrumented. Set to the value of serviceVersion.
  • service.instance.time: The timestamp when the service instance was created.
  • os.type: The operating system type.
  • os.version: The operating system version.
  • os.arch: The operating system architecture.

The OpenTelemetry feature automatically adds various attributes to spans following the OpenTelemetry Semantic Convention for GenAI:

Common Attributes

  • gen_ai.operation.name: the type of operation being performed (e.g., "create_agent", "invoke_agent", "chat", "execute_tool")
  • gen_ai.system: the LLM provider being used
  • gen_ai.agent.id: the ID of the agent
  • gen_ai.agent.name: the name of the agent (when available)
  • gen_ai.conversation.id: the ID of the conversation/run
  • gen_ai.request.model: the LLM model being used
  • gen_ai.request.temperature: the temperature parameter for the LLM request
  • error.type: the type of error that occurred (when applicable)

Custom Attributes

Some custom attributes specific to Koog are also added:

  • koog.agent.strategy.name: the name of the agent strategy
  • koog.node.id: the name of the node being executed

Span Types

The OpenTelemetry feature creates different types of spans for various operations. Each span type has a distinct purpose, parent–child relationship, and set of attributes and events.

  1. Create Agent Span

    • Purpose: Long‑lived span representing the lifecycle of an agent instance (created once per agent ID).
    • Emitted: When an agent is first started. Closed when the agent is closed.
    • Parent: none. Children: Invoke Agent Span (one per run).
    • Useful for: Grouping all agent runs and capturing static agent configuration.
    • Key attributes:
      • gen_ai.operation.name = create_agent
      • gen_ai.agent.id
      • gen_ai.request.model
  2. Invoke Agent Span

    • Purpose: One concrete execution (run) of an agent.
    • Emitted: At the start of a run. Closed on successful finish or error.
    • Parent: Create Agent Span. Children: Strategy Span instances, and, indirectly, node, tool, and LLM spans within strategies.
    • Useful for: Measuring run‑level timing, status, and grouping of all strategy, node, tool, and LLM activity for a run.
    • Key attributes:
      • gen_ai.operation.name = invoke_agent
      • gen_ai.agent.id
      • gen_ai.conversation.id
      • gen_ai.system (LLM provider)
      • gen_ai.response.finish_reasons (derived from the final response in the session prompt: Stop for Assistant/Reasoning, ToolCalls for Tool.Call)
  3. Strategy Span

    • Purpose: Execution of a strategy within an agent run.
    • Emitted: At the start of strategy execution. Closed when the strategy completes or errors.
    • Parent: Invoke Agent Span. Children: Node Execute Span instances.
    • Useful for: Tracking strategy-level execution, grouping all node executions within a strategy, and measuring strategy performance.
    • Key attributes:
      • gen_ai.conversation.id
      • koog.strategy.name
      • koog.event.id
    • Note: This is a custom span type specific to Koog, not defined in the OpenTelemetry Semantic Conventions.
  4. Node Execute Span

    • Purpose: Execution of a single node in the agent strategy.
    • Emitted: Immediately before a node runs. Closed after the node completes or errors.
    • Parent: Strategy Span. Children: Inference Spans and Execute Tool Span instances created by the node.
    • Useful for: Understanding strategy flow and attributing LLM or tool work to a specific node.
    • Key attributes:
      • gen_ai.conversation.id
      • koog.node.id
  5. Inference Span

    • Purpose: A single LLM call (prompt execution).
    • Emitted: Before the LLM is invoked. Closed after responses are received.
    • Parent: Node Execute Span.
    • Events added:
      • system, user, and assistant messages from the prompt
      • tool choice and tool result messages
      • moderation response (when present)
    • Useful for: Tracking model usage, prompt parameters, messages, token usage, and finish reasons.
    • Key attributes:
      • gen_ai.operation.name = chat
      • gen_ai.system (LLM provider)
      • gen_ai.conversation.id
      • gen_ai.request.model
      • gen_ai.request.temperature
      • gen_ai.request.max_tokens (when available)
      • gen_ai.usage.input_tokens (when available)
      • gen_ai.usage.output_tokens (when available)
      • gen_ai.usage.total_tokens (when available)
      • gen_ai.response.finish_reasons (Stop, ToolCalls, etc.)
      • error.type (on LLM call failure)
      • gen_ai.response.metadata (when ResponseMetaInfo.metadata is present - serialized JSON containing free-form provider or application metadata)
  6. Execute Tool Span

    • Purpose: Execution of a tool or function call triggered by the agent or LLM.
    • Emitted: When a tool is called. Closed after the tool returns a result or fails with an error during validation or execution.
    • Parent: Node Execute Span.
    • Useful for: Auditing tool usage, inputs and outputs, and failure causes.
    • Key attributes:
      • gen_ai.tool.name
      • gen_ai.tool.description
      • gen_ai.tool.arguments (when available)
      • gen_ai.tool.call_id (when available)
      • gen_ai.tool.output (when available)
      • error.type (on failure or validation error)

Exporters

Exporters send collected telemetry data to an OpenTelemetry Collector or other types of destinations or backend implementations.

OTLP Exporter example

The OTLP (OpenTelemetry Protocol) exporter sends telemetry data to an OpenTelemetry Collector. This is useful for integrating with systems like Jaeger, Zipkin, or Prometheus.

To add an OpenTelemetry Exporter, use the addSpanExporter function to a custom exporter:

kotlin
install(OpenTelemetry) {
    // The default endpoint is http://localhost:4317
    addSpanExporter(
        OtlpGrpcSpanExporter.builder()
            .setEndpoint("http://localhost:4317")
            .build()
    )
}

Logging Exporter example

A logging exporter that outputs trace information to the console is included by default. This type of export is useful
for development and debugging purposes.

kotlin
install(OpenTelemetry) {
    // The logging exporter is added by default
    addSpanExporter(LoggingSpanExporter.create())
}

Integration with Jaeger

Jaeger is a popular distributed tracing system that works with OpenTelemetry. To use Jaeger with Koog:

  1. Start a Jaeger container:
yaml
# docker-compose.yaml
services:
  jaeger-all-in-one:
    image: jaegertracing/all-in-one:1.39
    container_name: jaeger-all-in-one
    environment:
      - COLLECTOR_OTLP_ENABLED=true
    ports:
      - "4317:4317"  # OTLP gRPC port
      - "16686:16686"  # Jaeger UI port
  1. Configure your agent to use the OTLP exporter:
kotlin
install(OpenTelemetry) {
    addSpanExporter(
        OtlpGrpcSpanExporter.builder()
            .setEndpoint("http://localhost:4317")
            .build()
    )
}
  1. Access the Jaeger UI at http://localhost:16686 to view your traces.

Examples

This section includes two examples of implementing OpenTelemetry support in your agent workflow. The basic example
includes quick OpenTelemetry setup with minimal configuration, while the advanced example shows how you can create a
more elaborate and customized OpenTelemetry configuration.

Basic example

kotlin
suspend fun main() {
    val apiKey = "your-api-key"
    val agent = AIAgent(
        executor = simpleGoogleAIExecutor(apiKey),
        llmModel = GoogleModels.Gemini2_0Flash,
        systemPrompt = "You are a code assistant. Provide concise code examples.",
        installFeatures = {
            install(OpenTelemetry) {
                setServiceInfo("my-otlp-agent", "1.0.0")
                addSpanExporter(LoggingSpanExporter.create())
            }
        }
    )

    val result = agent.run("Create python function that prints hello world")
    println(result)
    
    // Wait for telemetry data to be exported
    TimeUnit.SECONDS.sleep(10)
}

Advanced example with custom exporters and attributes

kotlin
suspend fun main() {
    val apiKey = "your-api-key"
    val agent = AIAgent(
        executor = simpleOpenAIExecutor(apiKey),
        llmModel = OpenAIModels.Chat.GPT4o,
        systemPrompt = "You are a helpful assistant.",
        installFeatures = {
            install(OpenTelemetry) {
                // Configure service info
                setServiceInfo("advanced-agent", "2.0.0")

                // Add resource attributes (Map<String, Any>)
                addResourceAttributes(mapOf(
                    "deployment.environment" to "production",
                    "custom.attribute" to "custom-value"
                ))

                // Add exporters (Java SDK exporter bridged via the JVM addSpanExporter)
                addSpanExporter(LoggingSpanExporter.create())
            }
        }
    )

    val result = agent.run("Tell me about OpenTelemetry")
    println(result)

    // Wait for telemetry data to be exported
    TimeUnit.SECONDS.sleep(10)
}

For full sampling control or other SDK-level options, build your own [OpenTelemetry][OpenTelemetry] instance with [createOpenTelemetry { … }][createOpenTelemetry] and inject it via setSdk.

Datadog

Datadog supports direct OTLP intake for LLM Observability. To send traces to Datadog:

kotlin
import ai.koog.agents.features.opentelemetry.integration.datadog.addDatadogExporter

install(OpenTelemetry) {
    addDatadogExporter()
}

The exporter reads the DD_API_KEY environment variable by default. You can also pass it explicitly:

kotlin
install(OpenTelemetry) {
    addDatadogExporter(
        datadogApiKey = "your-api-key",
        url = "datadoghq.eu",                    // defaults to datadoghq.com
        resourceAttributes = mapOf(
            "env" to "production",
            "service.name" to "my-agent",
        ),
    )
}

To send traces to Datadog and another backend at the same time, register both via addSpanExporter (Datadog's helper handles its own registration internally; the second exporter goes through the public API):

kotlin
install(OpenTelemetry) {
    addDatadogExporter()
    addSpanExporter(
        OtlpHttpSpanExporter.builder()
            .setEndpoint("http://localhost:4318/v1/traces")
            .build()
    )
}

Troubleshooting

Common Issues

  1. No traces appearing in Jaeger UI

    • Ensure the Jaeger container is running and the OTLP port (4317) is accessible
    • Check that the OTLP exporter is configured with the correct endpoint
    • Make sure to wait a few seconds after agent execution for traces to be exported
  2. Missing spans or incomplete traces

    • Verify that the agent execution completes successfully
    • Ensure that you're not closing the application too quickly after agent execution
    • Add a delay (e.g., TimeUnit.SECONDS.sleep(10)) after agent execution to allow time for spans to be exported
  3. Excessive number of spans

    • Build a custom Kotlin OTel SDK with a sampler (e.g., parentBased(root = traceIdRatioBased(0.1))) and inject it via setSdk(...).

File: docs/docs/agents/planner-agents/goap-agents.md


status: beta

GOAP agents

--8<-- "versioning-snippets.md:beta"

GOAP is an algorithmic planning approach that uses A* search find optimal action sequences
that satisfy the goal conditions while minimizing the total cost.
Unlike LLM-based planners that use an LLM to generate plans,
a GOAP agent algorithmically discovers action sequences based on predefined goals and actions.

GOAP planners work with three main concepts:

  • State: Represents the current state of the world.
  • Actions: Define what can be done, including preconditions, effects (beliefs), costs, and execution logic.
  • Goals: Define target conditions, heuristic costs, and value functions.

??? note "Prerequisites"

text
--8<-- "quickstart-snippets.md:prerequisites"

--8<-- "quickstart-snippets.md:dependencies"

--8<-- "quickstart-snippets.md:api-key"

Examples on this page assume that you have set the `OPENAI_API_KEY` environment variable.

In Koog, you define a GOAP agent using a DSL by declaratively specifying the goals and actions.

To create a GOAP agent, you need to:

  1. Define the state as a data class with properties representing various aspects specific to your goal.
  2. Create a GOAPPlanner instance using the goap() function.
    1. Define actions with preconditions and beliefs using the action() function.
    2. Define goals with completion conditions using the goal() function.
  3. Wrap the planner with AIAgentPlannerStrategy and pass it to the PlannerAIAgent constructor.

!!! note

text
The planner selects individual actions and their sequence.
Each action includes a precondition that must hold true for the action to be executed
and a belief that defines the predicted outcome.
For more information about beliefs, see [State beliefs compared to actual execution](#state-beliefs-compared-to-actual-execution).

In the following example, GOAP handles high-level planning for creating an article
(outline → draft → review → publish),
while the LLM performs the actual content generation within each action.

=== "Kotlin"

text
```

/* Detailed source-code truncated for AI context efficiency. */

text
=== "Java"

    
    
    ```
/* Detailed source-code truncated for AI context efficiency. */

Custom cost functions

As A* search uses cost as a factor in finding the optimal sequence of actions,
you can define custom cost functions for actions and goals to guide the planner:

=== "Kotlin"

text
```kotlin
action(
    name = "Expensive operation",
    precondition = { true },
    belief = { state -> state.copy(operationDone = true) },
    cost = { state ->
        // Dynamic cost based on state
        if (state.hasOptimization) 1.0 else 10.0
    }
) { ctx, state ->
    // Execute action
    state.copy(operationDone = true)
}
```

=== "Java"

text
```java
.action("Expensive operation", builder -> builder
    .precondition(state -> true)
    .belief(state -> state.copy(true))
    .cost(state -> {
        // Dynamic cost based on state
        return state.hasOptimization ? 1.0 : 10.0;
    })
    .execute((context, state) -> {
        // Execute action
        return state.copy(true);
    })
)
```

State beliefs compared to actual execution

GOAP distinguishes between the concepts of beliefs (optimistic predictions) and actual execution:

  • Belief: What the planner thinks will happen, used for planning.
  • Execution: What actually happens, used for real state updates.

This allows the planner to make plans based on expected outcomes while handling actual results properly:

=== "Kotlin"

text
```kotlin
action(
    name = "Attempt complex task",
    precondition = { state -> !state.taskComplete },
    belief = { state ->
        // Optimistic belief: task will succeed
        state.copy(taskComplete = true)
    },
    cost = { 5.0 }
) { ctx, state ->
    // Actual execution might fail or have different results
    val success = performComplexTask()
    state.copy(
        taskComplete = success,
        attempts = state.attempts + 1
    )
}
```

=== "Java"

text
```java
.action("Attempt complex task", builder -> builder
    .precondition(state -> !state.taskComplete)
    .belief(state -> {
        // Optimistic belief: task will succeed
        return state.copy(true, state.attempts);
    })
    .cost(state -> 5.0)
    .execute((context, state) -> {
        // Actual execution might fail or have different results
        boolean success = performComplexTask();
        return state.copy(success, state.attempts + 1);
    })
)
```

File: docs/docs/agents/planner-agents/index.md


status: beta

Planner agents

--8<-- "versioning-snippets.md:beta"

Planner agents are AI agents that plan and execute multistep tasks through iterative planning cycles.
They continuously build or update plans, execute steps, and check completion criteria against the current state.

Planner agents are suitable for complex tasks
that require breaking down a high-level goal into smaller, actionable steps
and adapting the plan based on the results of each step.

While with graph-based agents you define all the nodes and edges,
for planner agents, you only define the actions (nodes) with typed inputs and outputs.
The planner creates reasonable edges that would be suitable to achieve the desired state,
and it can also update the optimal path between steps.
This allows for a more dynamic approach which can be more powerful
but less controllable compared to graph-based agents.

Planner agents operate through an iterative planning cycle:

  1. The planner creates or updates a plan based on the current state.
  2. The planner executes a single step from the plan, updating the state.
  3. The planner determines whether the plan is completed according to the current state.
    • If the plan is completed, the cycle ends.
    • If the plan is not completed, the cycle repeats from the first step.
mermaid
graph LR
  A[Create or update plan] --> B["Execute step and update state"]
  B --> C["Check completion"]
  C -->|Completed| D[[Done]]
  C -->|"Not completed"| A

Koog provides two types of planner agents:


File: docs/docs/agents/planner-agents/llm-based-planners.md


status: beta

LLM-based planners

--8<-- "versioning-snippets.md:beta"

LLM-based planners use LLMs to generate and evaluate plans.
They operate on a string-based state and execute steps through LLM requests.
String-based state means that the agent state is a single string.
At every step, the agent accepts an initial state string and returns the final state string as the result.

??? note "Prerequisites"

text
--8<-- "quickstart-snippets.md:prerequisites"

--8<-- "quickstart-snippets.md:dependencies"

--8<-- "quickstart-snippets.md:api-key"

Examples on this page assume that you have set the `OPENAI_API_KEY` environment variable.

Koog provides two simple planners:

  • SimpleLLMPlanner
    generates a plan only once at the very beginning and then follows the plan until it is completed.
    To include replanning, extend SimpleLLMPlanner and override the assessPlan method,
    indicating when the agent should replan.
  • SimpleLLMWithCriticPlanner
    implements the assessPlan method that uses an LLM to check the validity of the plan via an LLM request
    and assess whether the agent should replan.

The following example shows how to create a simple planner agent using SimpleLLMPlanner:

=== "Kotlin"

text
```kotlin
// Create the planner
val planner = SimpleLLMPlanner()

// Wrap it in a planner strategy
val strategy = AIAgentPlannerStrategy(
    name = "simple-planner",
    planner = planner
)

// Configure the agent
val agentConfig = AIAgentConfig(
    prompt = prompt("planner") {
        system("You are a helpful planning assistant.")
    },
    model = OpenAIModels.Chat.GPT4o,
    maxAgentIterations = 50
)

// Create the planner agent
val agent = PlannerAIAgent(
    promptExecutor = simpleOpenAIExecutor(System.getenv("OPENAI_API_KEY")),
    strategy = strategy,
    agentConfig = agentConfig
)

suspend fun main() {
    // Run the agent with a task
    val result = agent.run("Create a plan to organize a team meeting")
    println(result)
}
```

=== "Java"

text
```java
// Create the planner strategy with LLM-based planner
AIAgentPlannerStrategy<String, String> strategy =
    Planners.llmBased("simple-planner")
        .build();

// Create the OpenAI executor
var promptExecutor = PromptExecutor.builder()
    .openAI("OPENAI_API_KEY")
    .build();

// Create the planner agent using AIAgent builder
AIAgent<String, String> agent = AIAgent.builder()
    .plannerStrategy(strategy)
    .promptExecutor(promptExecutor)
    .llmModel(OpenAIModels.Chat.GPT4o)
    .systemPrompt("You are a helpful planning assistant.")
    .maxIterations(50)
    .build();

// Run the agent with a task
String result = agent.run("Create a plan to organize a team meeting");
System.out.println(result);
```

Next steps


File: docs/docs/agents/basic-agents.md

Basic agents

A basic agent uses a predefined strategy with a simple execution flow that works for most common use cases.
It accepts a string input (a question, request, or task description) and sends this input to the configured LLM.
The LLM may decide to call provided tools.
The agent will execute the tools and send the results back to the LLM.
This repeats until the LLM does not request any more tool calls and returns a string response.
The agent then outputs this response.

In Graph-based agents,
you can see how to re-create the predefined strategy graph used by basic agents.

??? note "Prerequisites"

text
--8<-- "quickstart-snippets.md:prerequisites"

--8<-- "quickstart-snippets.md:dependencies"

--8<-- "quickstart-snippets.md:api-key"

Examples on this page assume that you have set the `OPENAI_API_KEY` environment variable.

Create a minimal agent

To create the most basic agent, instantiate AIAgent
and provide a prompt executor with a language model:

=== "Kotlin"

text
```kotlin
val agent = AIAgent(
    promptExecutor = simpleOpenAIExecutor(System.getenv("OPENAI_API_KEY")),
    llmModel = OpenAIModels.Chat.GPT4o
)
```

This agent will expect a string as input and return a string as output.
To run the agent, use the `run()` function with some user input:

```kotlin
fun main() = runBlocking {
    val result = agent.run("Hello! How can you help me?")
    println(result)
}
```

=== "Java"

text
```java
AIAgent<String, String> agent = AIAgent.builder()
    .promptExecutor(simpleOpenAIExecutor(System.getenv("OPENAI_API_KEY")))
    .llmModel(OpenAIModels.Chat.GPT4o)
    .build();
```

This agent expects a string as input and returns a string as output.
To run the agent, use the `run()` method with some user input:

```java
String result = agent.run("Hello! How can you help me?");
System.out.println(result);
```

The agent will return a generic answer, such as:

text
I can assist with a wide range of topics and tasks. Here are some examples:

1. **Answering questions**: I can provide information on various subjects, from science and history to entertainment and culture.
2. **Generating text**: I can help with writing tasks, such as suggesting alternative phrases, providing definitions, or even creating entire articles or stories.
3. **Translation**: I can translate text from one language to another, including popular languages such as Spanish, French, German, Chinese, and many more.
4. **Conversation**: I can engage in natural-sounding conversations, using context and understanding to respond to questions and statements.
5. **Brainstorming**: I can help generate ideas for creative projects, such as writing stories, composing music, or coming up with business ideas.
6. **Learning**: I can help with language learning, explaining grammar rules, vocabulary, and pronunciation.
7. **Calculations**: I can perform mathematical calculations, including basic arithmetic, algebra, and more advanced math concepts.

What's on your mind? Do you have a specific question, topic, or task you'd like to tackle?

Add a system prompt

Provide a system message to define the agent's role
as well as the purpose, context, and instructions related to the task.

=== "Kotlin"

text
```kotlin
val agent = AIAgent(
    promptExecutor = simpleOpenAIExecutor(System.getenv("YOUR_API_KEY")),
    systemPrompt = "You are an expert in internet memes. Be helpful, friendly, and answer user questions concisely, showing your knowledge of memes.",
    llmModel = OpenAIModels.Chat.GPT4o
)
```

=== "Java"

text
```java
AIAgent<String, String> agent = AIAgent.builder()
    .promptExecutor(simpleOpenAIExecutor(System.getenv("OPENAI_API_KEY")))
    .systemPrompt("You are an expert in internet memes. Be helpful, friendly, and answer user questions concisely, showing your knowledge of memes.")
    .llmModel(OpenAIModels.Chat.GPT4o)
    .build();
```

The instructions in the system prompt will guide the agent's response:

text
I'm here to help you navigate the wild world of internet memes!

What's on your mind? Are you trying to understand a specific meme, need help finding a popular joke, or perhaps want some recommendations for trending memes? Let me know, and I'll do my best to provide you with some LOLs!

Configure LLM output

You can provide some LLM parameters directly to the agent constructor
(Kotlin) or via the builder methods (Java) to customize the behavior of the LLM.
For example, use the temperature parameter to adjust the randomness of the generated responses:

=== "Kotlin"

text
```kotlin
val agent = AIAgent(
    promptExecutor = simpleOpenAIExecutor(System.getenv("YOUR_API_KEY")),
    systemPrompt = "You are an expert in internet memes. Be helpful, friendly, and answer user questions concisely, showing your knowledge of memes.",
    llmModel = OpenAIModels.Chat.GPT4o,
    temperature = 0.7
)
```

=== "Java"

text
```java
AIAgent<String, String> agent = AIAgent.builder()
    .promptExecutor(simpleOpenAIExecutor(System.getenv("OPENAI_API_KEY")))
    .systemPrompt("You are an expert in internet memes. Be helpful, friendly, and answer user questions concisely, showing your knowledge of memes.")
    .llmModel(OpenAIModels.Chat.GPT4o)
    .temperature(0.7)
    .build();
```

Here are some response examples with different temperature values:

=== "0.4"

text
```text
I'm here to help you navigate the wild world of internet memes! Whether you're looking for explanations, examples, or just want to share a meme with someone, I'm your go-to expert. What's on your mind? Got a specific meme in mind that's got you curious? Or maybe you need some meme-related advice? Fire away!
```

=== "0.7"

text
```text
I'm here to help you navigate the wild world of internet memes!

What's on your mind? Need help understanding a specific meme, finding a popular joke or trend, or maybe even creating your own meme? Let's get this meme party started!
```

=== "1.0"

text
```text
I'd be happy to help you navigate the wild world of internet memes!

Whether you're looking for explanations of classic memes, suggestions for new ones to try out, or just want to discuss your favorite meme culture trends, I'm here to assist. What's on your mind?

Do you have a specific question about memes (e.g., "What does this meme mean?"), or are you looking for some meme-related recommendations (e.g., "Can you recommend a funny meme to share with friends?"). Let me know how I can help!
```

Add tools

Agents can use tools to perform specific tasks.

First, create a tool by annotating a function (Kotlin) or method (Java) with the @Tool annotation:

=== "Kotlin"

text
```kotlin
@Tool
@LLMDescription("Ask the user a question by sending it to stdout and return the answer from stdin")
fun askUser(
    @LLMDescription("Question from the agent")
    question: String
): String {
    println(question)
    return readln()
}
```

Then, use the [`ToolRegistry`](https://api.koog.ai/agents/agents-tools/ai.koog.agents.core.tools/-tool-registry/index.html) to make this tool available to the agent:

```kotlin
val agent = AIAgent(
    promptExecutor = simpleOpenAIExecutor(System.getenv("YOUR_API_KEY")),
    systemPrompt = "You are an expert in internet memes. Be helpful, friendly, and answer user questions concisely, showing your knowledge of memes.",
    llmModel = OpenAIModels.Chat.GPT4o,
    temperature = 0.7,
    toolRegistry = ToolRegistry {
        tool(::askUser)
    }
)
```


In the example, `askUser` is a tool that helps the agent maintain a conversation with the user via printing and reading from the console.
If the agent decides to ask the user a question,
it can call this tool that writes to `stdout` via `println()` and reads from `stdin` via `readln()`.

=== "Java"

text
```java
// Create a ToolSet class
class UserConversationTools implements ToolSet {
    @Tool
    @LLMDescription("Ask the user a question by sending it to stdout and return the answer from stdin")
    public String askUser(
        @LLMDescription("Question from the agent")
        String question
    ) {
        System.out.println(question);
        Scanner scanner = new Scanner(System.in);
        return scanner.nextLine();
    }
}
```

Then, use the [`ToolRegistry`](https://api.koog.ai/agents/agents-tools/ai.koog.agents.core.tools/-tool-registry/index.html) to make this tool available to the agent:

```java
UserConversationTools askUser = new UserConversationTools();

ToolRegistry toolRegistry = ToolRegistry.builder()
        .tools(askUser)
        .build();

AIAgent<String, String> agent = AIAgent.builder()
    .promptExecutor(simpleOpenAIExecutor(System.getenv("OPENAI_API_KEY")))
    .systemPrompt("You are an expert in internet memes. Be helpful, friendly, and answer user questions concisely, showing your knowledge of memes.")
    .llmModel(OpenAIModels.Chat.GPT4o)
    .temperature(0.7)
    .toolRegistry(toolRegistry)
    .build();
```


In the example, `askUser` is a tool that helps the agent maintain a conversation with the user via printing and reading from the console.

Here is an example interaction with the agent:

text
Agent: Which meme would you like me to explain? Please choose from: Grumpy Cat, Success Kid, or Doge.

User: Explain Doge

Agent:

**Doge**

Doge is a popular internet meme that originated on the imageboard website 4chan in 2013. The meme features a picture of a Shiba Inu dog with a caption written in a playful, exaggerated style.

The typical format of a Doge meme includes:

* A photo of a Shiba Inu dog
* A caption written in all capital letters, using a intentionally simplistic and childlike tone
* Exaggerated or made-up words or phrases, often used to convey a humorous or nonsensical idea

Examples of Doge memes might include:

* "Such wow. Such happy."
* "I had fun today!"
* "Wow, I am good at napping."

The meme is known for its lighthearted and playful tone, and is often used to express excitement, happiness, or silliness. The meme has since become a cultural phenomenon, with countless variations and parodies emerging online.

Adjust agent iterations

To avoid infinite loops, Koog allows any agent to take a limited number of steps (50 by default).
Use the maxIterations parameter to either increase this limit if you expect the agent to require more steps
(such as tool calls and LLM requests) or decrease it for agents that require only a few steps.
For example, a simple agent described here is not likely to require more than 10 steps:

=== "Kotlin"

text
```kotlin
val agent = AIAgent(
    promptExecutor = simpleOpenAIExecutor(System.getenv("YOUR_API_KEY")),
    systemPrompt = "You are an expert in internet memes. Be helpful, friendly, and answer user questions concisely, showing your knowledge of memes.",
    llmModel = OpenAIModels.Chat.GPT4o,
    temperature = 0.7,
    toolRegistry = ToolRegistry {
        tool(::askUser)
    },
    maxIterations = 10
)
```

=== "Java"

text
```java
// Create a ToolSet class
class UserConversationTools implements ToolSet {
    @Tool
    @LLMDescription("Ask the user a question by sending it to stdout and return the answer from stdin")
    public String askUser(
        @LLMDescription("Question from the agent")
        String question
    ) {
        System.out.println(question);
        Scanner scanner = new Scanner(System.in);
        return scanner.nextLine();
    }
}

// In main method:
UserConversationTools askUser = new UserConversationTools();

ToolRegistry toolRegistry = ToolRegistry.builder()
        .tools(askUser)
        .build();

AIAgent<String, String> agent = AIAgent.builder()
    .promptExecutor(simpleOpenAIExecutor(System.getenv("OPENAI_API_KEY")))
    .systemPrompt("You are an expert in internet memes. Be helpful, friendly, and answer user questions concisely, showing your knowledge of memes.")
    .llmModel(OpenAIModels.Chat.GPT4o)
    .temperature(0.7)
    .toolRegistry(toolRegistry)
    .maxIterations(10)
    .build();
```

!!! tip

text
Instead of passing the model, temperature, max iterations, and other parameters directly to the Kotlin constructor 
or Java builder, you can also define and pass them as a separate configuration object.
For more information, see [Agent configuration](index.md#agent-configuration).

Handle events during agent runtime

To assist with testing and debugging, as well as making hooks for chained agent interactions,
Koog provides the EventHandler feature.

=== "Kotlin"

text
Call the `handleEvents()` function inside the agent constructor lambda to install the feature and register event handlers:


```kotlin
val agent = AIAgent(
    promptExecutor = simpleOpenAIExecutor(System.getenv("YOUR_API_KEY")),
    systemPrompt = "You are an expert in internet memes. Be helpful, friendly, and answer user questions concisely, showing your knowledge of memes.",
    llmModel = OpenAIModels.Chat.GPT4o,
    temperature = 0.7,
    toolRegistry = ToolRegistry {
        tool(::askUser)
    },
    maxIterations = 10
){
    handleEvents {
        // Handle tool calls
        onToolCallStarting { eventContext ->
            println("Tool called: ${eventContext.toolName} with args ${eventContext.toolArgs}")
        }
    }
}
```

=== "Java"
Use the .install() method on the agent builder to register event handlers with EventHandler.Feature:

text
```java
// Create a ToolSet class
class UserConversationTools implements ToolSet {
    @Tool
    @LLMDescription("Ask the user a question by sending it to stdout and return the answer from stdin")
    public String askUser(
        @LLMDescription("Question from the agent")
        String question
    ) {
        System.out.println(question);
        Scanner scanner = new Scanner(System.in);
        return scanner.nextLine();
    }
}

// In main method:
UserConversationTools askUser = new UserConversationTools();

ToolRegistry toolRegistry = ToolRegistry.builder()
        .tools(askUser)
        .build();

AIAgent<String, String> agent = AIAgent.builder()
    .promptExecutor(simpleOpenAIExecutor(System.getenv("OPENAI_API_KEY")))
    .systemPrompt("You are an expert in internet memes. Be helpful, friendly, and answer user questions concisely, showing your knowledge of memes.")
    .llmModel(OpenAIModels.Chat.GPT4o)
    .temperature(0.7)
    .toolRegistry(toolRegistry)
    .maxIterations(10)
    .install(EventHandler.Feature, config -> {
        config.onToolCallStarting(eventContext -> {
            System.out.println("Tool called: " + eventContext.getToolName() +
                " with args " + eventContext.getToolArgs());
        });
    })
    .build();
```

The agent will now output something similar to the following when it calls the askUser tool:

text
Tool called: askUser with args {"question":"Which meme would you like me to explain?"}

For more information about Koog agent features, see Features.

Next steps


File: docs/docs/agents/functional-agents.md

Functional agents

With functional agents, you implement the logic as a function that handles user input,
interacts with LLMs, calls tools if necessary, and produces the final output.
Compared to graph-based agents,
this usually means faster prototyping with the following downsides:

  • Not easy to visualize
  • No state persistence

??? note "Prerequisites"

text
--8<-- "quickstart-snippets.md:prerequisites"

--8<-- "quickstart-snippets.md:dependencies"

--8<-- "quickstart-snippets.md:api-key"

Examples on this page assume that you are running Llama 3.2 locally via Ollama.

This page describes how to implement a functional strategy to quickly prototype some custom logic for your agent.

Create a minimal functional agent

To create a minimal functional agent, use the same AIAgent interface as for a basic agent and pass an
instance of AIAgentFunctionalStrategy to it.
You can define a functional strategy that expects an input and returns an output, makes one LLM call, then returns the
content of the assistant message from the response.

In Kotlin, the most convenient way is to use the functionalStrategy {...} DSL method. In Java, you can use the functionalStrategy method on the AIAgent builder.

=== "Kotlin"

text
```kotlin
val strategy = functionalStrategy<String, String> { input ->
    val response = requestLLM(input)
    response.parts.filterIsInstance<MessagePart.Text>().joinToString("\n") { it.text }
}

val mathAgent = AIAgent(
    promptExecutor = simpleOllamaAIExecutor(),
    llmModel = OllamaModels.Meta.LLAMA_3_2,
    strategy = strategy
)

fun main() = runBlocking {
    val result = mathAgent.run("What is 12 × 9?")
    println(result)
}
```

=== "Java"

text
```java
AIAgent<String, String> mathAgent = AIAgent.builder()
    .promptExecutor(SimpleLLMExecutorsKt.simpleOllamaAIExecutor("http://localhost:11434"))
    .llmModel(OllamaModels.Meta.LLAMA_3_2)
    .functionalStrategy("mathStrategy", (AIAgentFunctionalContext context, String input) -> {
        Message.Response response = context.requestLLM(input);
        if (response instanceof Message.Assistant) {
            return ((Message.Assistant) response).getContent();
        }
        return "";
    })
    .build();

String result = mathAgent.run("What is 12 × 9?");
System.out.println(result);
```

The agent can produce the following output:

text
The answer to 12 × 9 is 108.

Make sequential LLM calls

You can extend the previous strategy to make multiple sequential LLM calls:

=== "Kotlin"

text
```kotlin
fun Message.Assistant.text(): String =
    parts.filterIsInstance<MessagePart.Text>().joinToString("\n") { it.text }

val strategy = functionalStrategy<String, String> { input ->
    // The first LLM call produces an initial draft based on the user input
    val draft = requestLLM("Draft: $input").text()
    // The second LLM call improves the initial draft
    val improved = requestLLM("Improve and clarify.").text()
    // The final LLM call formats the improved text and returns the result
    requestLLM("Format the result as bold.").text()
}
```

=== "Java"

text
```java
AIAgent<String, String> mathAgent = AIAgent.builder()
    .promptExecutor(simpleOllamaAIExecutor("http://localhost:11434"))
    .systemPrompt("You are a precise math assistant.")
    .llmModel(OllamaModels.Meta.LLAMA_3_2)
    .functionalStrategy((AIAgentFunctionalContext context, String input) -> {
        // The first LLM call produces an initial draft based on the user input
        Message.Response draftResponse = context.requestLLM("Draft: " + input);
        String draft = "";
        if (draftResponse instanceof Message.Assistant) {
            draft = ((Message.Assistant) draftResponse).getContent();
        }

        // The second LLM call improves the initial draft
        Message.Response improvedResponse = context.requestLLM("Improve and clarify.");
        String improved = "";
        if (improvedResponse instanceof Message.Assistant) {
            improved = ((Message.Assistant) improvedResponse).getContent();
        }

        // The final LLM call formats the improved text and returns the result
        Message.Response finalResponse = context.requestLLM("Format the result as bold.");
        if (finalResponse instanceof Message.Assistant) {
            return ((Message.Assistant) finalResponse).getContent();
        }
        return "";
    })
    .build();
```

The agent can produce the following output:

text
To calculate the product of 12 and 9, we multiply these two numbers together.

12 × 9 = **108**

Add tools

In many cases, a functional agent needs to complete specific tasks,
such as reading and writing data, calling APIs, or performing other deterministic operations.
In Koog, you expose such capabilities as tools and let the LLM decide when to call them.

Here is what you need to do:

  1. Create an annotation-based tool.
  2. Add it to a tool registry and pass the registry to the agent.
  3. Make sure the agent strategy can identify tool calls in LLM responses, execute the requested tools,
    send their results back to the LLM, and repeat the process until there are no tool calls remaining.

=== "Kotlin"

text
```kotlin
@LLMDescription("Tools for performing math operations")
class MathTools : ToolSet {
    @Tool
    @LLMDescription("Multiplies two numbers and returns the result")
    fun multiply(a: Int, b: Int): Int {
        // This is not necessary, but it helps to see the tool call in the console output
        println("Multiplying $a and $b...")
        return a * b
    }
}

val toolRegistry = ToolRegistry {
    tool(MathTools()::multiply)
}

val strategy = functionalStrategy<String, String> { input ->
    // Send the user input to the LLM
    var response = requestLLM(input)

    // Only loop while the LLM requests tools
    var toolCalls = response.parts.filterIsInstance<MessagePart.Tool.Call>()
    while (toolCalls.isNotEmpty()) {
        // Execute the tools and return the results
        val results = executeTools(toolCalls)
        // Send the tool results back to the LLM. The LLM may call more tools or return a final output
        response = sendToolResults(results)
        toolCalls = response.parts.filterIsInstance<MessagePart.Tool.Call>()
    }

    // When no tool calls remain, extract and return the assistant message content from the response
    response.parts.filterIsInstance<MessagePart.Text>().joinToString("\n") { it.text }
}

val mathAgentWithTools = AIAgent(
    promptExecutor = simpleOllamaAIExecutor(),
    llmModel = OllamaModels.Meta.LLAMA_3_2,
    toolRegistry = toolRegistry,
    strategy = strategy
)

fun main() = runBlocking {
    val result = mathAgentWithTools.run("Multiply 3 by 4, then multiply the result by 5.")
    println(result)
}
```

=== "Java"

text
```

/* Detailed source-code truncated for AI context efficiency. */

text
The agent can produce the following output:

```text
Multiplying 3 and 4...
Multiplying 12 and 5...
The result of multiplying 3 by 4 is 12. Multiplying 12 by 5 gives us a final answer of 60.

Next steps


File: docs/docs/agents/graph-based-agents.md

Graph-based agents

With graph-based agents, you model the behavior as an explicit state machine:
nodes of a graph strategy represent actions (LLM calls, tool execution)
and edges represent data flow between nodes.

The main advantages of graph-based agents are:

  • Easy to visualize
  • State persistence
  • Composable architecture

??? note "Prerequisites"

text
--8<-- "quickstart-snippets.md:prerequisites"

--8<-- "quickstart-snippets.md:dependencies"

--8<-- "quickstart-snippets.md:api-key"

Examples on this page assume that you are running Llama 3.2 locally via Ollama.

This page describes how to re-create the strategy graph used by basic agents.
It sends a request to an LLM and then either outputs the response (if the LLM responded with an assistant message)
or executes a tool (if the LLM requested a tool call).
In case of a tool call, the agent sends the tool result to the LLM
and then either outputs the response or executes a tool.

Here is an illustration of the strategy graph:

mermaid
---
config:
  flowchart:
    defaultRenderer: "elk"
---
graph TB
    subgraph nodeStart
        Input
    end
    
    subgraph nodeFinish
        Output
    end
    
    subgraph nodeSendInput
        llmRequest(Request LLM)
    end
    
    subgraph nodeExecuteTool
        executeTool(Execute tool call)
    end
    
    subgraph nodeSendToolResult
        sendToolResult(Request LLM)
    end
    
    Input --String--> llmRequest
    llmRequest --Message.Assistant--> onToolCalls{{onToolCalls}}
    llmRequest --Message.Assistant--> onTextMessage{{onTextMessage}}
    onTextMessage --String--> Output
    onToolCalls --ToolCalls--> executeTool --ReceivedToolResults--> sendToolResult
    sendToolResult --Message.Assistant--> onToolCalls
    sendToolResult --Message.Assistant--> onTextMessage

Build a strategy graph

In Koog, you implement a strategy using AIAgentGraphStrategyBuilder.
Just like every node has an input and output type,
the strategy as a whole also defines some input and output type.
This example assumes that the input and output types are strings,
which means the agent implementing this strategy will expect a string and return a string.

To create a strategy, use the strategy() function with two generics as the input and output types,
provide a unique identifier for the strategy, and define the nodes and edges.

=== "Kotlin"

text
```kotlin
val calculatorAgentStrategy = strategy<String, String>("Simple calculator") {
    val nodeSendInput by nodeLLMRequest()
    val nodeExecuteTool by nodeExecuteTools()
    val nodeSendToolResult by nodeLLMSendToolResults()
    
    edge(nodeStart forwardTo nodeSendInput)
    edge(nodeSendInput forwardTo nodeFinish onTextMessage { true })
    edge(nodeSendInput forwardTo nodeExecuteTool onToolCalls { true })
    edge(nodeExecuteTool forwardTo nodeSendToolResult)
    edge(nodeSendToolResult forwardTo nodeFinish onTextMessage { true })
    edge(nodeSendToolResult forwardTo nodeExecuteTool onToolCalls { true })
}
```

=== "Java"

text
```java
var calculatorAgentStrategy = AIAgentGraphStrategy.builder("Simple calculator")
    .withInput(String.class)
    .withOutput(String.class);

var nodeSendInput = AIAgentNode.llmRequest("nodeSendInput");
var nodeExecuteTool = AIAgentNode.executeTools("nodeExecuteTool");
var nodeSendToolResult = AIAgentNode.llmSendToolResults("nodeSendToolResult");

calculatorAgentStrategy.edge(AIAgentEdge.builder()
    .from(calculatorAgentStrategy.nodeStart)
    .to(nodeSendInput)
    .build());
calculatorAgentStrategy.edge(AIAgentEdge.builder()
    .from(nodeSendInput)
    .to(calculatorAgentStrategy.nodeFinish)
    .onTextMessage()
    .build());
calculatorAgentStrategy.edge(AIAgentEdge.builder()
    .from(nodeSendInput)
    .to(nodeExecuteTool)
    .onToolCalls()
    .build());
calculatorAgentStrategy.edge(nodeExecuteTool, nodeSendToolResult);
calculatorAgentStrategy.edge(AIAgentEdge.builder()
    .from(nodeSendToolResult)
    .to(calculatorAgentStrategy.nodeFinish)
    .onTextMessage()
    .build());
calculatorAgentStrategy.edge(AIAgentEdge.builder()
    .from(nodeSendToolResult)
    .to(nodeExecuteTool)
    .onToolCalls()
    .build());
```

This example uses only predefined nodes,
but you can also create custom nodes.

Every strategy graph must have a path from nodeStart to nodeFinish connected by edges.
Edges can have conditions to determine when to follow a particular edge.
Edges can also transform the output of the previous node before passing it to the next one.
This is necessary to connect nodes that have non-matching output and input types.

In the previous example, onToolCalls { true } means that the edge will follow
only if the previous node returned an assistant message containing at least one tool call (MessagePart.Tool.Call).

When using onTextMessage { true }, the edge will follow
only if the previous node returned an assistant message containing text parts (MessagePart.Text).
This function also extracts and joins the text content of those parts,
effectively transforming Message.Assistant to String, because nodeFinish expects a string.

!!! tip

text
Instead of `onTextMessage { true }`, you can do the following:



```kotlin
onMessageParts(MessagePart.Text::class) transformed { it.joinToString("\n") { part -> part.text } }
```


Or:



```kotlin
onCondition { it is Message.Assistant } transformed { (it as Message.Assistant).parts.filterIsInstance<MessagePart.Text>().joinToString("\n") { part -> part.text } }
```

Create and run the agent

Let's create an agent instance with this strategy and run it:

=== "Kotlin"

text
```kotlin
val calculatorAgentStrategy = strategy<String, String>("Simple calculator") {
    val nodeSendInput by nodeLLMRequest()
    val nodeExecuteTool by nodeExecuteTools()
    val nodeSendToolResult by nodeLLMSendToolResults()

    edge(nodeStart forwardTo nodeSendInput)
    edge(nodeSendInput forwardTo nodeFinish onTextMessage { true })
    edge(nodeSendInput forwardTo nodeExecuteTool onToolCalls { true })
    edge(nodeExecuteTool forwardTo nodeSendToolResult)
    edge(nodeSendToolResult forwardTo nodeFinish onTextMessage { true })
    edge(nodeSendToolResult forwardTo nodeExecuteTool onToolCalls { true })
}

val mathAgent = AIAgent(
    promptExecutor = simpleOllamaAIExecutor(),
    llmModel = OllamaModels.Meta.LLAMA_3_2,
    strategy = calculatorAgentStrategy
)

fun main() = runBlocking {
    val result = mathAgent.run("Multiply 3 by 4, then multiply the result by 5, then add 10, then add 123.")
    println(result)
}
```

=== "Java"

text
```java
var calculatorAgentStrategy = AIAgentGraphStrategy.builder("Simple calculator")
    .withInput(String.class)
    .withOutput(String.class);

var nodeSendInput = AIAgentNode.llmRequest("nodeSendInput");
var nodeExecuteTool = AIAgentNode.executeTools("nodeExecuteTool");
var nodeSendToolResult = AIAgentNode.llmSendToolResults("nodeSendToolResult");

calculatorAgentStrategy.edge(AIAgentEdge.builder()
    .from(calculatorAgentStrategy.nodeStart)
    .to(nodeSendInput)
    .build());
calculatorAgentStrategy.edge(AIAgentEdge.builder()
    .from(nodeSendInput)
    .to(calculatorAgentStrategy.nodeFinish)
    .onTextMessage()
    .build());
calculatorAgentStrategy.edge(AIAgentEdge.builder()
    .from(nodeSendInput)
    .to(nodeExecuteTool)
    .onToolCalls()
    .build());
calculatorAgentStrategy.edge(nodeExecuteTool, nodeSendToolResult);
calculatorAgentStrategy.edge(AIAgentEdge.builder()
    .from(nodeSendToolResult)
    .to(calculatorAgentStrategy.nodeFinish)
    .onTextMessage()
    .build());
calculatorAgentStrategy.edge(AIAgentEdge.builder()
    .from(nodeSendToolResult)
    .to(nodeExecuteTool)
    .onToolCalls()
    .build());

var promptExecutor = PromptExecutor.builder()
    .ollama("http://localhost:11434")
    .build();

AIAgent<String, String> mathAgent = AIAgent.builder()
    .promptExecutor(promptExecutor)
    .llmModel(OllamaModels.Meta.LLAMA_3_2)
    .graphStrategy(calculatorAgentStrategy.build())
    .build();

    String result = mathAgent.run("Multiply 3 by 4, then multiply the result by 5, then add 10, then add 123.", null);
    System.out.println(result);
```

When you run this agent, it will respond with something like this:

text
To calculate this, I'll follow the order of operations:

1. Multiply 3 by 4: 3 * 4 = 12
2. Multiply the result by 5: 12 * 5 = 60
3. Add 10: 60 + 10 = 70
4. Add 123: 70 + 123 = 193

The final answer is 193.

However, since this agent doesn't have any tools, the LLM never returns a tool call
and simply generates the whole answer.
This is what effectively happens:

mermaid
---
config:
  flowchart:
    defaultRenderer: "elk"
---
graph LR
    subgraph nodeStart
        Input
    end
    
    subgraph nodeFinish
        Output
    end
    
    subgraph nodeSendInput
        llmRequest(Request LLM)
    end
    
    Input --String--> llmRequest --Message.Assistant--> onTextMessage{{onTextMessage}} --String--> Output

Even though it is correct in this case, the answer will depend on the arithmetic abilities of the underlying LLM.
To make sure the calculations are correct, we should provide the agent with math tools.
Then the LLM will be able to decide to call tools that perform the calculations deterministically.

Add tools

Define tools for performing math operations and add them to a ToolRegistry:

=== "Kotlin"

text
```kotlin
@LLMDescription("Tools for performing math operations")
class MathTools : ToolSet {
    @Tool
    @LLMDescription("Adds two numbers and returns the result")
    fun add(a: Int, b: Int): Int {
        // This is not necessary, but it helps to see the tool call in the console output
        println("Adding $a and $b...")
        return a + b
    }
    @Tool
    @LLMDescription("Multiplies two numbers and returns the result")
    fun multiply(a: Int, b: Int): Int {
        // This is not necessary, but it helps to see the tool call in the console output
        println("Multiplying $a and $b...")
        return a * b
    }
}

val toolRegistry = ToolRegistry {
    tools(MathTools())
}
```

=== "Java"

text
```java
@LLMDescription("Tools for performing math operations")
public static class MathTools implements ToolSet {
    @Tool
    @LLMDescription("Adds two numbers and returns the result")
    public int add(int a, int b) {
        // This is not necessary, but it helps to see the tool call in the console output
        System.out.println("Adding " + a + " and " + b + "...");
        return a + b;
    }

    @Tool
    @LLMDescription("Multiplies two numbers and returns the result")
    public int multiply(int a, int b) {
        // This is not necessary, but it helps to see the tool call in the console output
        System.out.println("Multiplying " + a + " and " + b + "...");
        return a * b;
    }
}
public static void main(String[] args) {
    ToolRegistry toolRegistry = ToolRegistry.builder()
        .tools(new MathTools())
        .build();
}
```

Add the tool registry to the agent configuration:

=== "Kotlin"

text
```kotlin
val mathAgent = AIAgent(
    promptExecutor = simpleOllamaAIExecutor(),
    llmModel = OllamaModels.Meta.LLAMA_3_2,
    strategy = calculatorAgentStrategy,
    toolRegistry = toolRegistry
)

fun main() = runBlocking {
    val result = mathAgent.run("Multiply 3 by 4, then multiply the result by 5, then add 10, then add 123.")
    println(result)
}
```

=== "Java"

text
```java
AIAgent<String, String> mathAgent = AIAgent.builder()
    .promptExecutor(promptExecutor)
    .llmModel(OllamaModels.Meta.LLAMA_3_2)
    .graphStrategy(calculatorAgentStrategy.build())
    .toolRegistry(toolRegistry)
    .build();

String result = mathAgent.run("Multiply 3 by 4, then multiply the result by 5, then add 10, then add 123.", null);
System.out.println(result);
```

When you run the agent now, it will respond with something like this:

text
Multiplying 3 and 4...
The output from the first operation was multiplied by 5:
5 * 12 = 60

Then, 10 was added to the result:
60 + 10 = 70

Finally, 123 was added to the result:
70 + 123 = 193

According to this output, the agent correctly performed the calculations, but it only called the multiply tool once
instead of calling the corresponding tool for every operation.
We can help the agent by describing its role and providing instructions for using appropriate tools in the system prompt.

Provide a system prompt

A system prompt defines the agent's role and instructions for performing tasks.
In our example, it is important to describe how the agent should process complex multistep calculations:

=== "Kotlin"

text
```kotlin
val mathAgent = AIAgent(
    promptExecutor = simpleOllamaAIExecutor(),
    llmModel = OllamaModels.Meta.LLAMA_3_2,
    systemPrompt = """
                You are a simple calculator assistant.
                You can add and multiply two numbers using the 'add' and 'multiply' tools.
                When the user provides input, extract the numbers and operations they requested.
                Use the appropriate tool for the first operation, then the next one, and so on, until you calculate the result.
                Always respond with a clear, friendly message showing the calculation and result.
                """.trimIndent(),
    toolRegistry = toolRegistry,
    strategy = calculatorAgentStrategy
)

fun main() = runBlocking {
    val result = mathAgent.run("Multiply 3 by 4, then multiply the result by 5, then add 10, then add 123.")
    println(result)
}
```

=== "Java"

text
```java
AIAgent<String, String> mathAgent = AIAgent.builder()
    .promptExecutor(promptExecutor)
    .llmModel(OllamaModels.Meta.LLAMA_3_2)
    .systemPrompt("You are a simple calculator assistant. You can add and multiply two numbers using the 'add' and 'multiply' tools. When the user provides input, extract the numbers and operations they requested. Use the appropriate tool for the first operation, then the next one, and so on, until you calculate the result. Always respond with a clear, friendly message showing the calculation and result.")
    .graphStrategy(calculatorAgentStrategy.build())
    .toolRegistry(toolRegistry)
    .build();

String result = mathAgent.run("Multiply 3 by 4, then multiply the result by 5, then add 10, then add 123.", null);
System.out.println(result);
```

When you run the agent now, it will respond with something like this:

text
Multiplying 3 and 4...
Multiplying 12 and 5...
Adding 60 and 10...
Adding 70 and 123...
The final result is: 193

As you can see, the agent now correctly calls the appropriate tool for each operation,
ensuring that it performs the calculations deterministically instead of risking a hallucinated result.

Next steps

2. Official Technical Reference & Guides (JetBrains/koog-docs)

File: README.md

Koog documentation






Welcome to the repository containing documentation of the Koog framework, published on https://docs.koog.ai

Overview

Koog is a Kotlin-based framework designed to build and run AI agents entirely in idiomatic Kotlin. It lets you create agents that can interact with tools, handle complex workflows, and communicate with users.

Contributing

Read the Contributing Guidelines.

Code of Conduct

This project and the corresponding community are governed by the JetBrains Open Source and Community Code of Conduct. Please make sure you read it.

License

Koog is licensed under the Apache 2.0 License.

Support

Please feel free to ask any questions in our official Slack channel (link)


File: docs/index.md

Overview

Koog is a Kotlin-based framework designed to build and run AI agents entirely in idiomatic Kotlin.
It lets you create agents that can interact with tools, handle complex workflows, and communicate with users.

The framework supports the following types of agents:

  • Single-run agents with minimal configuration that process a single input and provide a response.
    An agent of this type operates within a single cycle of tool-calling to complete its task and provide a response.
  • Complex workflow agents with advanced capabilities that support custom strategies and configurations.

Key features

Key features of Koog include:

  • Pure Kotlin implementation: Build AI agents entirely in natural and idiomatic Kotlin.
  • MCP integration: Connect to Model Control Protocol for enhanced model management.
  • Embedding capabilities: Use vector embeddings for semantic search and knowledge retrieval.
  • Custom tool creation: Extend your agents with tools that access external systems and APIs.
  • Ready-to-use components: Speed up development with pre-built solutions for common AI engineering challenges.
  • Intelligent history compression: Optimize token usage while maintaining conversation context using various pre-built strategies.
  • Powerful Streaming API: Process responses in real-time with streaming support and parallel tool calls.
  • Persistent agent memory: Enable knowledge retention across sessions and even different agents.
  • Comprehensive tracing: Debug and monitor agent execution with detailed and configurable tracing.
  • Flexible graph workflows: Design complex agent behaviors using intuitive graph-based workflows.
  • Modular feature system: Customize agent capabilities through a composable architecture.
  • Scalable architecture: Handle workloads from simple chatbots to enterprise applications.
  • Multiplatform: Run agents on JVM, JS, WasmJS targets with Kotlin Multiplatform.

Available LLM providers and platforms

The LLM providers and platforms whose LLMs you can use to power your agent capabilities:

  • Google
  • OpenAI
  • Anthropic
  • OpenRouter
  • Ollama

Installation

To use Koog, you need to include all necessary dependencies in your build configuration.

Gradle

Gradle (Kotlin DSL)

  1. Add dependencies to the build.gradle.kts file:

    text
    dependencies {
        implementation("ai.koog:koog-agents:LATEST_VERSION")
    }
  2. Make sure that you have mavenCentral() in the list of repositories.

Gradle (Groovy)

  1. Add dependencies to the build.gradle file:

    text
    dependencies {
        implementation 'ai.koog:koog-agents:LATEST_VERSION'
    }
  2. Make sure that you have mavenCentral() in the list of repositories.

Maven

  1. Add dependencies to the pom.xml file:

    text
    <dependency>
        <groupId>ai.koog</groupId>
        <artifactId>koog-agents</artifactId>
        <version>LATEST_VERSION</version>
    </dependency>
  2. Make sure that you have mavenCentral in the list of repositories.

Quickstart example

To help you get started with AI agents, here is a quick example of a single-run agent:

!!! note
Before you run the example, assign a corresponding API key as an environment variable. For details, see Getting started.

kotlin
fun main() {
    runBlocking {
        val apiKey = System.getenv("OPENAI_API_KEY") // or Anthropic, Google, OpenRouter, etc.

        val agent = AIAgent(
            executor = simpleOpenAIExecutor(apiKey), // or Anthropic, Google, OpenRouter, etc.
            systemPrompt = "You are a helpful assistant. Answer user questions concisely.",
            llmModel = OpenAIModels.Chat.GPT4o
        )

        val result = agent.run("Hello! How can you help me?")
        println(result)
    }
}

For more details, see Getting started.