Scale Agent Memory Using Multiple Nodes
- how-to
Run several Agent Memory nodes that share a Couchbase bucket, so your deployment handles more client traffic and keeps processing memories when nodes fail.
By default, Agent Memory runs as a single node. Cluster mode lets you run several Agent Memory nodes that share 1 Couchbase bucket. Each node processes the memory blocks it ingests, and if a node stops, the surviving nodes take over any unfinished work. For more information about how nodes share work, recover from failures, and split rate limits, see Cluster Mode.
To create an Agent Memory cluster, complete the following steps:
|
Converting or Expanding a Deployment
|
Prerequisites
Before you start using Agent Memory’s cluster mode, confirm the following:
-
You have a server for each node, with Docker and Docker Compose installed. The servers share a private network with the load balancer.
-
Every node runs the same version of Agent Memory.
-
Your Couchbase cluster and bucket are prepared. For more information, see Prepare Your Couchbase Cluster.
-
You have a load balancer, such as nginx, that can terminate HTTPS, and a TLS certificate for your domain.
-
You know the requests-per-minute and tokens-per-minute limits of your model provider account.
Step 1: Set Up Nodes
| Set up each node that you need for your cluster, but do not start them until Step 3. |
To set up your nodes, continue from Step 2 of Deploy Agent Memory for Production, but make the following changes to the docker-compose.yml and .env files.
Update the docker-compose.yml File
Use the following docker-compose.yml file.
Its image value is the pre-built Agent Memory image that you load and tag in Step 2 of Deploy Agent Memory for Production.
It has no HTTPS proxy, and it publishes port 8080 only on the server’s private address.
Replace <PRIVATE_IP> with that address.
name: agentmemory
services:
agentmemory-server:
image: agentmemory-server:latest
container_name: agentmemory-server
ports:
- "<PRIVATE_IP>:8080:8080"
env_file:
- .env
environment:
- AGENTMEMORY_SERVER_HOST=0.0.0.0
- AGENTMEMORY_SERVER_PORT=8080
volumes:
- agentmemory-logs:/app/logs
- agentmemory-prometheus:/prometheus-data
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8080/health').read()"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
restart: unless-stopped
volumes:
agentmemory-logs:
agentmemory-prometheus:
Update the .env File
In each node’s .env file, enable cluster mode and set the rate limits for the whole cluster, not a per-node share:
AGENTMEMORY_CLUSTER_MODE=true
AGENTMEMORY_MAX_REQUESTS_PER_MINUTE=<REQUESTS_PER_MINUTE>
AGENTMEMORY_MAX_TOKENS_PER_MINUTE=<TOKENS_PER_MINUTE>
Set these limits to about 90-95% of your model provider’s limits.
This leaves headroom for brief over-counting when nodes join or leave, and for other usage on the same provider account.
Use the same .env values on every node for the Couchbase connection, bucket, model providers, rate limits, and cluster mode settings.
For more information, see cluster mode and Rate Limiting and Throughput.
Step 2: Configure the Load Balancer
A cluster has a load balancer in front of its nodes, and every node connects to the same Couchbase bucket. Clients connect only to the load balancer, and each node runs on its own server without its own HTTPS proxy.
To use Agent Memory’s cluster mode, you must configure your load balancer to:
-
Terminate HTTPS for your domain.
-
Forward requests to port
8080on each node’s private address. -
Stop routing to a node that fails, and send its requests to the other nodes.
Agent Memory does not distribute ingestion or rebalance work between nodes, so distribute requests evenly, for example with round-robin routing. An uneven load balancer leaves some nodes idle while others build a queue.
The following example configures nginx.
Replace <NODE_1_IP>, <NODE_2_IP>, and <NODE_3_IP> with each node’s private address, <YOUR_DOMAIN> with your domain, and <CERTIFICATE_PATH> and <PRIVATE_KEY_PATH> with the paths to your TLS certificate and private key.
upstream agentmemory {
server <NODE_1_IP>:8080 max_fails=2 fail_timeout=10s;
server <NODE_2_IP>:8080 max_fails=2 fail_timeout=10s;
server <NODE_3_IP>:8080 max_fails=2 fail_timeout=10s;
}
server {
listen 443 ssl;
server_name <YOUR_DOMAIN>;
ssl_certificate <CERTIFICATE_PATH>;
ssl_certificate_key <PRIVATE_KEY_PATH>;
location / {
proxy_pass http://agentmemory;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_next_upstream error timeout http_502 http_503 http_504;
proxy_read_timeout 300s;
}
}
With this configuration, nginx stops sending requests to a node after 2 failed attempts within 10 seconds, and tries that node again after 10 seconds.
The proxy_next_upstream directive retries a failed request on another node.
By default, nginx does not resend a request that creates data, such as a POST that adds a memory, after it sends the request to a node.
The proxy_read_timeout value allows time for synchronous memory processing, which waits for the model provider.
If your load balancer supports active health checks, point them at each node’s /health endpoint.
/health only confirms that the server process responds.
It does not check the Couchbase connection or the model providers.
Step 3: Start the Cluster
-
New Deployment
-
Convert Existing Deployment
Use these steps if you’re creating a new Agent Memory deployment that uses cluster mode:
-
Start each node by running the following command in its project directory.
docker compose up -d -
Point your domain at the load balancer.
| Do not start any new nodes until you finish draining the backlog. A node that starts without cluster mode re-queues every unfinished block, including blocks the existing node is processing. A node that starts with cluster mode does not count the existing node, so together they exceed your rate limits. |
Use the following steps if you’re converting an existing Agent Memory deployment to use cluster mode:
-
Drain the extraction backlog of your existing node.
Blocks ingested before you enable cluster mode are not owned by any node in the cluster. If they’re still waiting for extraction, cluster mode never recovers them.
-
Stop sending new writes to Agent Memory, for example by pausing your clients.
-
Keep the existing node running in single-node mode until both
sizeandprocessingin thequeueobject are0:curl -s https://$YOUR_DOMAIN/health/async-batch-processor-stats | jq '.queue'If you have enabled authentication, add
-H "Authorization: Bearer $TOKEN"with a token that carries theams_rorole or higher. For more information about getting a token, see Configure Authentication for Self-Managed Agent Memory. -
Confirm that no unprocessed blocks remain in Couchbase. Run the following SQL++ query in the Capella Query Workbench, the Couchbase Server Query Workbench, or the cbq shell, with credentials that can read the bucket:
SELECT COUNT(*) AS pending FROM `<BUCKET_NAME>`.agentmemory.memory WHERE status != 'ready' AND IFMISSINGORNULL(fail_count, 0) < 3;If you changed AGENTMEMORY_MAX_FAIL_COUNTfrom its default of3, use your value instead.The query returns
0when the backlog is drained. If the query returns a number greater than0while the queue is empty, restart the node withdocker restart agentmemory-serverto re-queue the remaining blocks. Wait untilsizeandprocessingin thequeueobject are0again, then rerun the query.docker restartkeeps the node’s current configuration, so the node stays reachable at your domain.
-
-
Point your domain at the load balancer.
-
Restart the existing node and start each new node using the following commands in each node’s project directory:
docker compose down --remove-orphans && docker compose up -dThe
--remove-orphansoption removes containers that are no longer in thedocker-compose.ymlfile, such as a Traefik or nginx proxy. If you used Cloudflare Tunnel, also stop thecloudflaredtunnel on the server. -
Verify the cluster deployment, then resume your clients.
Step 4: Verify the Cluster Deployment
To verify that a cluster is operating, wait 30 seconds after the last node starts, then run the following command on each node’s host.
If your container is not named agentmemory-server, replace it with your container name.
docker exec agentmemory-server python -c "import urllib.request; print(urllib.request.urlopen('http://localhost:8080/metrics').read().decode())" | grep '^agentmemory_cluster_live_node_count'
The output shows the number of nodes you started, for example agentmemory_cluster_live_node_count 3.0 for a 3-node cluster.
If this reports a lower number than you’re expecting, see Troubleshooting.
Add Nodes to a Cluster
You can add a node to a running cluster without restarting the other nodes.
-
Set up the node, as described in Step 1: Set Up Nodes, with the same
.envvalues as the other nodes, includingAGENTMEMORY_CLUSTER_MODE=true. -
Start the node by running
docker compose up -din its project directory. -
Add the node’s private address to your load balancer, and reload the load balancer’s configuration.
-
If you scrape metrics with an external Prometheus server, add the node to its scrape configuration.
-
Verify the cluster. For more information, see Step 4: Verify the Cluster Deployment.
Monitor a Cluster
Each node’s embedded Prometheus instance collects metrics only from that node. To view the whole cluster in a single place, use an external Prometheus server.
-
Add the
/metricsendpoint of every node to your Prometheus server’s scrape configuration. Use each node’s private address, and run Prometheus on the same private network.scrape_configs: - job_name: agentmemory metrics_path: /metrics static_configs: - targets: - <NODE_1_IP>:8080 - <NODE_2_IP>:8080 - <NODE_3_IP>:8080 -
Confirm that Prometheus reaches every node.
curl -s "http://$PROMETHEUS_HOST:9090/api/v1/targets?state=active" | jq -r '.data.activeTargets[] | "\(.labels.instance) \(.health)"'Each node appears with the health
up. -
To tell nodes apart in queries, join a metric to
agentmemory_node_infoon theinstancelabel. For example, the following query shows the live node count that each node reports, labeled with its node ID:agentmemory_cluster_live_node_count * on(instance) group_left(node_id) agentmemory_node_info
Agent Memory exposes metrics for heartbeats, dead-node recovery, and each node’s rate limit share. Use them to build dashboards and alerts for the cluster.
For all of the cluster mode metrics that are available, expand the following section:
Show All Cluster Mode Metrics
| Metric | Type | Description |
|---|---|---|
|
Gauge |
Number of live nodes as seen by this node. |
|
Gauge |
This node’s share of |
|
Gauge |
Carries this node’s ID in the |
|
Counter |
Heartbeat attempts, labeled by |
|
Counter |
Times this node found that a peer had claimed it and shut itself down. |
|
Counter |
Dead-node recovery sweeps this node has run. |
|
Counter |
Dead nodes found across all sweeps. |
|
Counter |
Attempts to claim a dead node, labeled by |
|
Counter |
Blocks this node took over from dead nodes. |
|
Counter |
Recovery passes over this node’s own unfinished blocks, labeled by |
|
Counter |
Blocks re-queued by those recovery passes. |
|
Counter |
Blocks this node recovered at startup. |
In cluster mode, agentmemory_health_check_status{component="couchbase"} reflects heartbeat success.
agentmemory_async_batch_process_outcomes_total reports result="lease_expired" when a node discards an extraction result after it loses contact with Couchbase.
Collect Diagnostics from a Cluster
To collect support diagnostics from every node in a single archive, use agentmemory-collect:
-
On a chosen node’s host, run
agentmemory-collectand pass the node URLs with--nodes.docker exec agentmemory-server agentmemory-collect \ --customer $CUSTOMER_NAME \ --ticket-id $TICKET_ID \ --nodes http://$NODE_1_IP:8080,http://$NODE_2_IP:8080,http://$NODE_3_IP:8080The command uploads the archive to Couchbase Support, so you need an open support ticket. Replace the following placeholders.
-
$CUSTOMER_NAME: Your company name, using up to 50 letters, numbers, underscores, periods, or hyphens. -
$TICKET_ID: Your Couchbase Support ticket number, up to 7 digits. -
$NODE_1_IP,$NODE_2_IP, and$NODE_3_IP: Each node’s private address.If you have enabled authentication, add
--auth-tokenwith a token that each node accepts. If you have also enabled authorization, the token must carry theams_adminrole.
-
-
Check the command output for errors.
The archive leaves out any node that the command cannot reach, and the output shows
Collected <URL>: errorfor that node.
For more information, see Collecting Support Diagnostics.
Troubleshooting
- If a node reports fewer live nodes than you started
-
Confirm that every node registered with the cluster by running the following command in each node’s project directory.
docker compose logs agentmemory-server | grep "Node document written"This log entry requires
LOG_LEVELset toINFOorDEBUG.INFOis the default. A node that does not show this log entry did not register. Check itsAGENTMEMORY_CLUSTER_MODEvalue and its Couchbase connection settings.