Skip to content
Docs

MongoDB Context Persistence

In-memory conversation history is lost when a process restarts, which means agents lose context between deployments, scaling events, or crashes. MongoDB persistence solves this by storing conversation messages durably, enabling agents to maintain long-term context across sessions and process lifecycles.

Choose MongoDB for memory persistence when your organization already operates MongoDB infrastructure, when you need flexible schema for varied message metadata, or when you want managed cloud persistence via MongoDB Atlas.

The mongodb store implements the memory.MessageStore interface, storing each message as an individual BSON document with a monotonically increasing sequence number. This design supports:

  • Persistent conversation history across process restarts
  • Multi-session context management via separate collections or key prefixes
  • Scalable storage for high-volume applications
  • Integration with MongoDB Atlas for managed cloud deployments
  • Go 1.26 or later
  • A Beluga AI project initialized with go mod init
  • A MongoDB instance (local, Docker, or MongoDB Atlas)
  • The MongoDB Go driver v2

Install the MongoDB memory store:

Terminal window
go get github.com/lookatitude/beluga-ai/v2/memory/stores/mongodb

Start a local MongoDB instance if you do not already have one:

Terminal window
# Using Docker
docker run -d --name mongodb -p 27017:27017 mongo:7
# Or install and run directly
mongod --dbpath /data/db

Connect to MongoDB and create a message store:

package main
import (
"context"
"fmt"
"log"
"github.com/lookatitude/beluga-ai/v2/memory/stores/mongodb"
"github.com/lookatitude/beluga-ai/v2/schema"
"go.mongodb.org/mongo-driver/v2/mongo"
"go.mongodb.org/mongo-driver/v2/mongo/options"
)
func main() {
ctx := context.Background()
// Connect to MongoDB.
client, err := mongo.Connect(options.Client().ApplyURI("mongodb://localhost:27017"))
if err != nil {
log.Fatalf("Failed to connect to MongoDB: %v", err)
}
defer func() {
if err := client.Disconnect(ctx); err != nil {
log.Printf("Disconnect error: %v", err)
}
}()
// Create the message store backed by a MongoDB collection.
store, err := mongodb.New(mongodb.Config{
Collection: client.Database("beluga_ai").Collection("messages"),
})
if err != nil {
log.Fatalf("Failed to create store: %v", err)
}
// Append messages to the store.
err = store.Append(ctx, schema.NewHumanMessage("Hello, how are you?"))
if err != nil {
log.Fatalf("Append failed: %v", err)
}
err = store.Append(ctx, schema.NewAIMessage("I'm doing well, thank you!"))
if err != nil {
log.Fatalf("Append failed: %v", err)
}
// Retrieve the full history in chronological order.
msgs, err := store.All(ctx)
if err != nil {
log.Fatalf("All failed: %v", err)
}
fmt.Printf("Retrieved %d messages\n", len(msgs))
}

The MongoDB store implements memory.MessageStore, so it plugs into Beluga’s CompositeMemory as the backing store of the recall tier:

import (
"github.com/lookatitude/beluga-ai/v2/memory"
"github.com/lookatitude/beluga-ai/v2/memory/stores/mongodb"
)
// Create a MongoDB-backed recall store.
recallStore, err := mongodb.New(mongodb.Config{
Collection: client.Database("beluga_ai").Collection("recall"),
})
if err != nil {
log.Fatalf("Failed to create recall store: %v", err)
}
// Build a CompositeMemory with MongoDB as the recall tier.
mem := memory.NewComposite(
memory.WithRecall(memory.NewRecall(recallStore)),
)
// Save a conversation turn.
err = mem.Save(ctx,
schema.NewHumanMessage("What is the weather today?"),
schema.NewAIMessage("I don't have access to weather data."),
)
if err != nil {
log.Fatalf("Save failed: %v", err)
}
// Load relevant context.
msgs, err := mem.Load(ctx, "weather")
if err != nil {
log.Fatalf("Load failed: %v", err)
}

The MongoDB store supports case-insensitive substring search across stored messages. Matching happens in the application after loading the collection, so it is suited to modest history sizes:

results, err := store.Search(ctx, "weather", 5)
if err != nil {
log.Fatalf("Search failed: %v", err)
}
for _, msg := range results {
fmt.Printf("[%s] %v\n", msg.GetRole(), msg.GetContent())
}

Remove all messages from the store:

err = store.Clear(ctx)
if err != nil {
log.Fatalf("Clear failed: %v", err)
}

Use separate MongoDB collections for different conversation sessions:

func storeForSession(client *mongo.Client, sessionID string) (*mongodb.MessageStore, error) {
coll := client.Database("beluga_ai").Collection("session_" + sessionID)
return mongodb.New(mongodb.Config{
Collection: coll,
})
}

Create indexes on the MongoDB collection to optimize query performance:

import "go.mongodb.org/mongo-driver/v2/bson"
coll := client.Database("beluga_ai").Collection("messages")
// Index on sequence number for chronological ordering.
_, err := coll.Indexes().CreateOne(ctx, mongo.IndexModel{
Keys: bson.D{{Key: "seq", Value: 1}},
})
if err != nil {
log.Printf("Index creation warning: %v", err)
}
// Index on timestamp for time-range queries.
_, err = coll.Indexes().CreateOne(ctx, mongo.IndexModel{
Keys: bson.D{{Key: "timestamp", Value: -1}},
})
if err != nil {
log.Printf("Index creation warning: %v", err)
}

Configure MongoDB TTL indexes to automatically remove old messages:

_, err := coll.Indexes().CreateOne(ctx, mongo.IndexModel{
Keys: bson.D{{Key: "timestamp", Value: 1}},
Options: options.Index().SetExpireAfterSeconds(86400 * 30), // 30 days
})

Add OpenTelemetry tracing to monitor MongoDB operations:

import (
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
)
tracer := otel.Tracer("beluga.memory.mongodb")
ctx, span := tracer.Start(ctx, "mongodb.append",
trace.WithAttributes(attribute.String("session_id", sessionID)),
)
defer span.End()
err := store.Append(ctx, msg)
if err != nil {
span.RecordError(err)
return err
}

Configure the MongoDB client for production connection pooling:

clientOpts := options.Client().
ApplyURI("mongodb://localhost:27017").
SetMaxPoolSize(50).
SetMinPoolSize(5).
SetMaxConnIdleTime(30 * time.Second)
client, err := mongo.Connect(clientOpts)
OptionDescriptionDefaultRequired
CollectionMongoDB collection to store messages in—Yes

Connection-level settings (URI, pool size, timeouts) are configured on the mongo.Client before passing the collection to the store.

The Config.Collection field is nil. Ensure you pass a valid *mongo.Collection (or any type implementing the mongodb.Collection interface) when creating the store.

MongoDB is not running or the URI is incorrect. Verify:

Terminal window
# Test connectivity
mongosh "mongodb://localhost:27017"

Search and All read the whole collection in seq order and filter in Go, so an index on seq helps ordering but search cost still grows with collection size. Keep per-session collections small (see Session Isolation) or archive old messages with a TTL index.