Build a chat agent with memory
This guide shows you how to create a conversational chat agent that remembers previous messages across multiple interactions using the ChatMemory feature.
Prerequisites
Ensure your environment and project meet the following requirements:
- JDK 17+
- Kotlin 2.2.0+
- Gradle 8.0+ or Maven 3.8+
Install Koog and Memory feature
Set up an API key
Get an API key from an LLM provider or run a local LLM via Ollama. For more information, see Quickstart.
What you will build
A command-line chat agent that:
- Accepts user input in a loop
- Sends each message to an LLM
- Remembers the full conversation history across
agent.run()calls - Uses a sliding window to limit context size
Without ChatMemory, each call to agent.run() starts a fresh conversation — the agent has no
knowledge of what was said before. ChatMemory solves this by automatically loading previous
messages before each run and storing the updated history afterward.
Create a chat agent
suspend fun main() {
val sessionId = "my-conversation"
val toolRegistry = ToolRegistry {
// register your tools here
}
simpleOpenAIExecutor(System.getenv("OPENAI_API_KEY")).use { executor ->
val agent = AIAgent(
promptExecutor = executor,
llmModel = OpenAIModels.Chat.GPT5_2,
systemPrompt = "You are a helpful assistant.",
toolRegistry = toolRegistry,
) {
install(ChatMemory) {
windowSize(20) // keep only the last 20 messages
}
}
while (true) {
print("You: ")
val input = readln().trim()
if (input == "/bye") break
if (input.isEmpty()) continue
val reply = agent.run(input, sessionId)
println("Assistant: $reply\n")
}
}
}
public class ExampleChatAgentOpenAI {
public static void main(String[] args) {
String sessionId = "my-conversation";
ToolRegistry toolRegistry = ToolRegistry.builder()
// register your tools here
.build();
try (var executor = simpleOpenAIExecutor(System.getenv("OPENAI_API_KEY"))) {
AIAgent<String, String> agent = AIAgent.builder()
.promptExecutor(executor)
.llmModel(OpenAIModels.Chat.GPT5_2)
.systemPrompt("You are a helpful assistant.")
.toolRegistry(toolRegistry)
.install(ChatMemory.Feature, config -> {
config.windowSize(20); // keep only the last 20 messages
})
.build();
Scanner scanner = new Scanner(System.in);
while (true) {
System.out.print("You: ");
String input = scanner.nextLine().trim();
if (input.equals("/bye")) break;
if (input.isEmpty()) continue;
String reply = agent.run(input, sessionId);
System.out.println("Assistant: " + reply + "\n");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
suspend fun main() {
val sessionId = "my-conversation"
val toolRegistry = ToolRegistry {
// register your tools here
}
simpleAnthropicExecutor(System.getenv("ANTHROPIC_API_KEY")).use { executor ->
val agent = AIAgent(
promptExecutor = executor,
llmModel = AnthropicModels.Sonnet_4_5,
systemPrompt = "You are a helpful assistant.",
toolRegistry = toolRegistry,
) {
install(ChatMemory) {
windowSize(20)
}
}
while (true) {
print("You: ")
val input = readln().trim()
if (input == "/bye") break
if (input.isEmpty()) continue
val reply = agent.run(input, sessionId)
println("Assistant: $reply\n")
}
}
}
public class ExampleChatAgentAnthropic {
public static void main(String[] args) {
String sessionId = "my-conversation";
ToolRegistry toolRegistry = ToolRegistry.builder()
// register your tools here
.build();
try (var executor = simpleAnthropicExecutor(System.getenv("ANTHROPIC_API_KEY"))) {
AIAgent<String, String> agent = AIAgent.builder()
.promptExecutor(executor)
.llmModel(AnthropicModels.Sonnet_4_5)
.systemPrompt("You are a helpful assistant.")
.toolRegistry(toolRegistry)
.install(ChatMemory.Feature, config -> {
config.windowSize(20);
})
.build();
Scanner scanner = new Scanner(System.in);
while (true) {
System.out.print("You: ");
String input = scanner.nextLine().trim();
if (input.equals("/bye")) break;
if (input.isEmpty()) continue;
String reply = agent.run(input, sessionId);
System.out.println("Assistant: " + reply + "\n");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
suspend fun main() {
val sessionId = "my-conversation"
val toolRegistry = ToolRegistry {
// register your tools here
}
simpleGoogleAIExecutor(System.getenv("GOOGLE_API_KEY")).use { executor ->
val agent = AIAgent(
promptExecutor = executor,
llmModel = GoogleModels.Gemini2_5Pro,
systemPrompt = "You are a helpful assistant.",
toolRegistry = toolRegistry,
) {
install(ChatMemory) {
windowSize(20)
}
}
while (true) {
print("You: ")
val input = readln().trim()
if (input == "/bye") break
if (input.isEmpty()) continue
val reply = agent.run(input, sessionId)
println("Assistant: $reply\n")
}
}
}
public class ExampleChatAgentGoogle {
public static void main(String[] args) {
String sessionId = "my-conversation";
ToolRegistry toolRegistry = ToolRegistry.builder()
// register your tools here
.build();
try (var executor = simpleGoogleAIExecutor(System.getenv("GOOGLE_API_KEY"))) {
AIAgent<String, String> agent = AIAgent.builder()
.promptExecutor(executor)
.llmModel(GoogleModels.Gemini2_5Pro)
.systemPrompt("You are a helpful assistant.")
.toolRegistry(toolRegistry)
.install(ChatMemory.Feature, config -> {
config.windowSize(20);
})
.build();
Scanner scanner = new Scanner(System.in);
while (true) {
System.out.print("You: ");
String input = scanner.nextLine().trim();
if (input.equals("/bye")) break;
if (input.isEmpty()) continue;
String reply = agent.run(input, sessionId);
System.out.println("Assistant: " + reply + "\n");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
suspend fun main() {
val sessionId = "my-conversation"
val toolRegistry = ToolRegistry {
// register your tools here
}
simpleOllamaAIExecutor().use { executor ->
val agent = AIAgent(
promptExecutor = executor,
llmModel = OllamaModels.Meta.LLAMA_3_2,
systemPrompt = "You are a helpful assistant.",
toolRegistry = toolRegistry,
) {
install(ChatMemory) {
windowSize(20)
}
}
while (true) {
print("You: ")
val input = readln().trim()
if (input == "/bye") break
if (input.isEmpty()) continue
val reply = agent.run(input, sessionId)
println("Assistant: $reply\n")
}
}
}
public class ExampleChatAgentOllama {
public static void main(String[] args) {
String sessionId = "my-conversation";
ToolRegistry toolRegistry = ToolRegistry.builder()
// register your tools here
.build();
try (var executor = simpleOllamaAIExecutor("http://localhost:11434")) {
AIAgent<String, String> agent = AIAgent.builder()
.promptExecutor(executor)
.llmModel(OllamaModels.Meta.LLAMA_3_2)
.systemPrompt("You are a helpful assistant.")
.toolRegistry(toolRegistry)
.install(ChatMemory.Feature, config -> {
config.windowSize(20);
})
.build();
Scanner scanner = new Scanner(System.in);
while (true) {
System.out.print("You: ");
String input = scanner.nextLine().trim();
if (input.equals("/bye")) break;
if (input.isEmpty()) continue;
String reply = agent.run(input, sessionId);
System.out.println("Assistant: " + reply + "\n");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
How it works
The example above has three key parts:
1. Install ChatMemory
ChatMemory is installed as a feature inside the agent builder block:
The windowSize(20) preprocessor ensures that the conversation context
stays bounded — only the 20 most recent messages are kept. Without this, prompt size
grows unboundedly as the conversation gets longer.
2. Use a consistent session ID
The second argument to agent.run() is the session ID:
ChatMemory uses this ID to load and store the conversation. All calls with the same session ID share the same history. Different session IDs produce fully isolated conversations.
3. The chat loop
Each iteration of the while loop:
- Reads user input
- Calls
agent.run(input, sessionId)— ChatMemory automatically loads the previous history before the LLM sees the prompt - Prints the response
- ChatMemory automatically stores the updated history (including the new user message and assistant response)
Example session
You: My name is Alice.
Assistant: Nice to meet you, Alice! How can I help you today?
You: What's my favorite color? It's blue.
Assistant: Got it — your favorite color is blue!
You: What's my name?
Assistant: Your name is Alice!
The agent correctly answers "Your name is Alice!" because ChatMemory loaded the earlier exchanges before processing the third message.
Next steps
- Learn about preprocessors to filter and transform conversation history
- Implement a custom history provider for persistent storage
- See a backend use case with Spring Boot for managing chat sessions over HTTP
- Understand the difference between ChatMemory and Persistence for crash recovery scenarios
- Explore Chat Memory for the full feature reference