Memory
Feature overview
The AgentMemory feature is a component of the Koog framework that lets AI agents store, retrieve, and use information across conversations.
Purpose
The AgentMemory Feature addresses the challenge of maintaining context in AI agent interactions by:
- Storing important facts extracted from conversations.
- Organizing information by concepts, subjects, and scopes.
- Retrieving relevant information when needed in future interactions.
- Enabling personalization based on user preferences and history.
Architecture
The AgentMemory feature is built on a hierarchical structure. The elements of the structure are listed and explained in the sections below.
Facts
Facts are individual pieces of information stored in the memory. Facts represent actual stored information. There are two types of facts:
- SingleFact: a single value associated with a concept. For example, an IDE user's current preferred theme:
- MultipleFacts: multiple values associated with a concept. For example, all languages that a user knows:
Concepts
Concepts are categories of information with associated metadata.
- Keyword: unique identifier for the concept.
- Description: detailed explanation of what the concept represents.
- FactType: whether the concept stores single or multiple facts (
FactType.SINGLE
orFactType.MULTIPLE
).
Subjects
Subjects are entities that facts can be associated with.
Common examples of subjects include:
- User: Personal preferences and settings
- Environment: Information related to the environment of the application
There is a predefined MemorySubject.Everything
that you may use as a default subject for all facts.
In addition, you can define your own custom memory subjects by extending the MemorySubject
abstract class:
object MemorySubjects {
/**
* Information specific to the local machine environment
* Examples: Installed tools, SDKs, OS configuration, available commands
*/
@Serializable
data object Machine : MemorySubject() {
override val name: String = "machine"
override val promptDescription: String =
"Technical environment (installed tools, package managers, packages, SDKs, OS, etc.)"
override val priorityLevel: Int = 1
}
}
Scopes
Memory scopes are contexts in which facts are relevant:
- Agent: specific to an agent.
- Feature: specific to a feature.
- Product: specific to a product.
- CrossProduct: relevant across multiple products.
Configuration and initialization
The feature integrates with the agent pipeline through the AgentMemory
class, which provides methods for saving and
loading facts, and can be installed as a feature in the agent configuration.
Configuration
The AgentMemory.Config
class is the configuration class for the AgentMemory feature.
class Config : FeatureConfig() {
var memoryProvider: AgentMemoryProvider = NoMemory
var scopesProfile: MemoryScopesProfile = MemoryScopesProfile()
var agentName: String
var featureName: String
var organizationName: String
var productName: String
}
Installation
To install the AgentMemory feature in an agent, follow the pattern provided in the code sample below.
val agent = AIAgent(...) {
install(AgentMemory) {
memoryProvider = memoryProvider
agentName = "your-agent-name"
featureName = "your-feature-name"
organizationName = "your-organization-name"
productName = "your-product-name"
}
}
Examples and quickstarts
Basic usage
The following code snippets demonstrate the basic setup of a memory storage and how facts are saved to and loaded from the memory.
-
Set up memory storage
-
Store a fact in the memory
- Retrieve the fact
// Get the stored information try { val greeting = memoryProvider.load( concept = Concept("greeting", "User's name", FactType.SINGLE), subject = MemorySubjects.User ) println("Retrieved: $greeting") } catch (e: MemoryNotFoundException) { println("Information not found. First time here?") } catch (e: Exception) { println("Error accessing memory: ${e.message}") }
Using memory nodes
The AgentMemory feature provides the following predefined memory nodes that can be used in agent strategies:
- nodeLoadAllFactsFromMemory: loads all facts about the subject from the memory for a given concept.
- nodeLoadFromMemory: loads specific facts from the memory for a given concept.
- nodeSaveToMemory: saves a fact to the memory.
- nodeSaveToMemoryAutoDetectFacts: automatically detects and extracts facts from the chat history and saves them to the memory. Uses the LLM to identify concepts.
Here is an example of how nodes can be implemented in an agent strategy:
val strategy = strategy("example-agent") {
// Node to automatically detect and save facts
val detectFacts by nodeSaveToMemoryAutoDetectFacts<Unit>(
subjects = listOf(MemorySubjects.User, MemorySubjects.Project)
)
// Node to load specific facts
val loadPreferences by node<Unit, Unit> {
withMemory {
loadFactsToAgent(
concept = Concept("user-preference", "User's preferred programming language", FactType.SINGLE),
subjects = listOf(MemorySubjects.User)
)
}
}
// Connect nodes in the strategy
edge(nodeStart forwardTo detectFacts)
edge(detectFacts forwardTo loadPreferences)
edge(loadPreferences forwardTo nodeFinish)
}
Making memory secure
You can use encryption to make sure that sensitive information is protected inside an encrypted storage used by the memory provider.
// Simple encrypted storage setup
val secureStorage = EncryptedStorage(
fs = JVMFileSystemProvider.ReadWrite,
encryption = Aes256GCMEncryption("your-secret-key")
)
Example: Remembering user preferences
Here is an example of how AgentMemory is used in a real-world scenario to remember a user's preference, specifically the user's favorite programming language.
memoryProvider.save(
fact = SingleFact(
concept = Concept("preferred-language", "What programming language is preferred by the user?", FactType.SINGLE),
value = "Kotlin",
timestamp = DefaultTimeProvider.getCurrentTimestamp()
),
subject = MemorySubjects.User
)
Advanced usage
Custom nodes with memory
You can also use the memory from the withMemory
clause inside any node. The ready-to-use loadFactsToAgent
and saveFactsFromHistory
higher level abstractions save facts to the history, load facts from it, and update the LLM chat:
val loadProjectInfo by node<Unit, Unit> {
withMemory {
loadFactsToAgent(Concept("project-structure", ...))
}
}
val saveProjectInfo by node<Unit, Unit> {
withMemory {
saveFactsFromHistory(Concept("project-structure", ...))
}
}
Automatic fact detection
You can also ask the LLM to detect all the facts from the agent's history using the nodeSaveToMemoryAutoDetectFacts
method:
val saveAutoDetect by nodeSaveToMemoryAutoDetectFacts<Unit>(
subjects = listOf(MemorySubjects.User, MemorySubjects.Project)
)
Best practices
-
Start Simple
- Begin with basic storage without encryption
- Use single facts before moving to multiple facts
-
Organize Well
- Use clear concept names
- Add helpful descriptions
- Keep related information under the same subject
-
Handle Errors
For more details on error handling, see Error handling and edge cases.try { memoryProvider.save(fact, subject) } catch (e: Exception) { println("Oops! Couldn't save: ${e.message}") }
Error handling and edge cases
The AgentMemory feature includes several mechanisms to handle edge cases:
-
NoMemory provider: a default implementation that doesn't store anything, used when no memory provider is specified.
-
Subject specificity handling: when loading facts, the feature prioritizes facts from more specific subjects based on their defined
priorityLevel
. -
Scope filtering: facts can be filtered by scope to ensure only relevant information is loaded.
-
Timestamp tracking: facts are stored with timestamps to track when they were created.
-
Fact type handling: the feature supports both single facts and multiple facts, with appropriate handling for each type.
API documentation
For a complete API reference related to the AgentMemory feature, see the reference documentation for the agents-features-memory module.
API documentation for specific packages:
- ai.koog.agents.local.memory.feature: includes the
AgentMemory
class and the core implementation of the AI agents memory feature. - ai.koog.agents.local.memory.feature.nodes: includes predefined memory-related nodes that can be used in subgraphs.
- ai.koog.agents.local.memory.config: provides definitions of memory scopes used for memory operations.
- ai.koog.agents.local.memory.model: includes definitions of the core data structures and interfaces that enable agents to store, organize, and retrieve information across different contexts and time periods.
- ai.koog.agents.local.memory.feature.history: provides the history compression strategy for retrieving and incorporating factual knowledge about specific concepts from past session activity or stored memory.
- ai.koog.agents.local.memory.providers: provides the core interface that defines the fundamental operation for storing and retrieving knowledge in a structured, context-aware manner and its implementations.
- ai.koog.agents.local.memory.storage: provides the core interface and specific implementations for file operations across different platforms and storage backends.
FAQ and troubleshooting
How do I implement a custom memory provider?
To implement a custom memory provider, create a class that implements the AgentMemoryProvider
interface:
class MyCustomMemoryProvider : AgentMemoryProvider {
override suspend fun save(fact: Fact, subject: MemorySubject, scope: MemoryScope) {
// Implementation for saving facts
}
override suspend fun load(concept: Concept, subject: MemorySubject, scope: MemoryScope): List<Fact> {
// Implementation for loading facts by concept
}
override suspend fun loadAll(subject: MemorySubject, scope: MemoryScope): List<Fact> {
// Implementation for loading all facts
}
override suspend fun loadByDescription(
description: String,
subject: MemorySubject,
scope: MemoryScope
): List<Fact> {
// Implementation for loading facts by description
}
}
How are facts prioritized when loading from multiple subjects?
Facts are prioritized based on subject specificity. When loading facts, if the same concept has facts from multiple subjects, the fact from the most specific subject will be used.
Can I store multiple values for the same concept?
Yes, by using the MultipleFacts
type. When defining a concept, set its factType
to FactType.MULTIPLE
:
val concept = Concept(
keyword = "user-skills",
description = "Programming languages the user is skilled in",
factType = FactType.MULTIPLE
)
This lets you store multiple values for the concept, which is retrieved as a list.