Common Library

Memory

Memory is what makes an agent more than a single request. Four kinds coexist, each with a different lifetime and store:

Kind Store Lifetime

Chat memory

MongoDB

The message history of one session.

Data memory

MongoDB

Typed objects with revision history, scoped to a session or an application.

Scoped memory

Elasticsearch

Text plus embedding, scoped by namespace and user — session, short-term and knowledge memory.

Memory Facade

Memory is what a Context carries — chat memory plus data memory for one session:

class Memory(sessionId: ID, chatMemory: ChatMemory, val dataMemory: DataMemory) {
    fun add(message: Message): Mono<Void>
    fun addAll(messages: List<Message>): Mono<Void>
    fun messages(): Mono<List<Message>>      // chat history without instruction messages
    fun getChatHistory(): Mono<List<Message>>
}

Chat Memory

interface ChatMemory {
    fun getChatHistory(sessionId: ID): Mono<List<Message>>
    fun addToChatHistory(sessionID: ID, messages: List<Message>): Mono<Void>
    fun deleteChatHistory(sessionID: ID): Mono<Void>
}

Messages are Spring AI Message instances (user, system, assistant, tool response) persisted as ChatMemoryUnit documents through the converters in MongoConfiguration. BlankChatMemory is the no-op implementation used when an agent has no memory.

Instruction Messages

InstructionMessage is a UserMessage subtype tagged with an InstructionType — INSTRUCTION, COMMENTARY or SUPPORTING_CONTEXT. It carries guidance injected into the conversation (retrieved knowledge, plan steps, behavioural instructions) without pretending to be something the user said. Memory.messages() filters them out, so the plain conversation can be read back without the injected material — which is what transcripts and evaluation use.

Data Memory

interface DataMemory {
    fun <D : Any> unit(type: Class<D>, scope: MemoryScope = MemoryScope.Default): Mono<DataMemoryUnit<D>>
    fun <D : Any> data(type: Class<D>, scope: MemoryScope = MemoryScope.Default): Mono<D>
    fun <D : Any> merge(data: D, scope: MemoryScope = MemoryScope.Default): Mono<D>
    fun <D : Any> delete(type: Class<D>, scope: MemoryScope = MemoryScope.Default): Mono<Void>
    fun deleteAll(scope: MemoryScope = MemoryScope.Default): Mono<Void>
}

Each write appends a DataMemoryUnit.Revision, so the history of a value is preserved rather than overwritten. MemoryScope is Application, Session or Default; BlankDataMemory is the no-op implementation.

Scoped Memory

ScopedMemory (in ai.promethist.component) is the Elasticsearch-backed semantic memory. One instance is created per namespace, each with its own index and optional embedding model:

  • writing a unit embeds its text (when an embedding model is configured) and indexes it;

  • reading supports vector similarity search with metadata filters, latest-value lookup and time-interval queries.

The namespaces the engine declares — session, short-term and knowledge memory — are wired in MemoryConfiguration, see Engine Application.

ScopedMemoryUnit

The indexed unit carries its scope alongside its content:

open class ScopedMemoryUnit(
    override val id: String,
    val created: ZonedDateTime,
    val turnId: String,
    val sessionId: String,
    val userId: String,
    val namespace: String,
    val agentRef: String,
    val agentInfo: AgentInfo?,
    val projectId: String,
    val tenantId: String,
    val text: String,
    open val data: List<Any>,
) : MemoryUnit, TextuallyRepresented

data is heterogeneous; data(type) and dataWithTypes pull out a typed payload. text is the representation that gets embedded and put into prompts.

TextuallyRepresented

TextuallyRepresented is the contract for anything that can be embedded or put into a prompt:

interface TextuallyRepresented {
    fun describe(): String    // human/prompt readable description
    fun promptify(): String   // the form injected into a prompt
}

Extracted data types and graph node payloads implement it, which is what lets memory retrieval return heterogeneous results that the model can still read.

Snapshots

MemoryState captures a user’s whole memory — data, short-term and session memory units plus graph nodes and relationships — so it can be exported and restored. The engine exposes this through the memory endpoints.