GOAP agents
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.
Prerequisites
Ensure your environment and project meet the following requirements:
- JDK 17+
- Kotlin 2.2.0+
- Gradle 8.0+ or Maven 3.8+
Add the Koog package as a dependency:
Get an API key from an LLM provider or run a local LLM via Ollama. For more information, see Quickstart.
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:
- Define the state as a data class with properties representing various aspects specific to your goal.
- Create a GOAPPlanner instance using the goap() function.
- Wrap the planner with AIAgentPlannerStrategy and pass it to the PlannerAIAgent constructor.
Note
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.
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.
// Define a state for content creation
data class ContentState(
val topic: String,
val hasOutline: Boolean = false,
val outline: String = "",
val hasDraft: Boolean = false,
val draft: String = "",
val hasReview: Boolean = false,
val isPublished: Boolean = false
)
// Create GOAP planner with LLM-powered actions
val planner = goap<ContentState>(
stateType = typeOf<ContentState>()
) {
// Define actions with preconditions and beliefs
action(
name = "Create outline",
precondition = { state -> !state.hasOutline },
belief = { state -> state.copy(hasOutline = true, outline = "Outline") },
cost = { 1.0 }
) { ctx, state ->
// Use LLM to create the outline
val response = ctx.llm.writeSession {
appendPrompt {
user("Create a detailed outline for an article about: ${state.topic}")
}
requestLLM()
}
state.copy(hasOutline = true, outline = response.content)
}
action(
name = "Write draft",
precondition = { state -> state.hasOutline && !state.hasDraft },
belief = { state -> state.copy(hasDraft = true, draft = "Draft") },
cost = { 2.0 }
) { ctx, state ->
// Use LLM to write the draft
val response = ctx.llm.writeSession {
appendPrompt {
user("Write an article based on this outline:\n${state.outline}")
}
requestLLM()
}
state.copy(hasDraft = true, draft = response.content)
}
action(
name = "Review content",
precondition = { state -> state.hasDraft && !state.hasReview },
belief = { state -> state.copy(hasReview = true) },
cost = { 1.0 }
) { ctx, state ->
// Use LLM to review the draft
val response = ctx.llm.writeSession {
appendPrompt {
user("Review this article and suggest improvements:\n${state.draft}")
}
requestLLM()
}
println("Review feedback: ${response.content}")
state.copy(hasReview = true)
}
action(
name = "Publish",
precondition = { state -> state.hasReview && !state.isPublished },
belief = { state -> state.copy(isPublished = true) },
cost = { 1.0 }
) { ctx, state ->
println("Publishing article...")
state.copy(isPublished = true)
}
// Define the goal with a completion condition
goal(
name = "Published article",
description = "Complete and publish the article",
condition = { state -> state.isPublished }
)
}
// Create and run the agent
val agentConfig = AIAgentConfig(
prompt = prompt("writer") {
system("You are a professional content writer.")
},
model = OpenAIModels.Chat.GPT4o,
maxAgentIterations = 20
)
val agent = PlannerAIAgent(
promptExecutor = simpleOpenAIExecutor(System.getenv("OPENAI_API_KEY")),
strategy = AIAgentPlannerStrategy(
name = "grouper",
planner = planner
),
agentConfig = agentConfig
)
suspend fun main() {
val result = agent.run(ContentState(topic = "The Future of AI in Software Development"))
println("Final state: $result")
}
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:
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)
}
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:
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
)
}