Batch storage patterns — when and how to store memories in bulk


MemoClaw charges $0.005 per individual store. The batch endpoint stores up to 100 memories for a flat $0.04. That’s $0.04 vs $0.50 for 100 memories — 92% cheaper.

But batch isn’t always the right call. Here’s the cost math, practical patterns for batching, and how to structure payloads so recall actually works after bulk ingestion.

The cost math

Approach1 memory10 memories50 memories100 memories
Individual stores$0.005$0.05$0.25$0.50
Batch store$0.04$0.04$0.04$0.04
Savings-$0.035$0.01$0.21$0.46

Breakeven is 8 memories. Below 8, individual stores are cheaper. Above 8, batch wins. Since the price is flat regardless of count, fill batches as close to 100 as you can.

When to use batch

Session dumps

At the end of a long conversation, your agent has context worth preserving. Instead of storing each insight one by one, collect them and batch-store.

memoclaw store-batch \
  '{"content": "User decided to migrate from MongoDB to PostgreSQL", "importance": 0.9, "memory_type": "decision", "metadata": {"tags": ["database", "migration"]}}' \
  '{"content": "Migration deadline is March 30, 2026", "importance": 0.8, "memory_type": "project", "metadata": {"tags": ["deadline", "migration"]}}' \
  '{"content": "User wants to keep the MongoDB read replica during transition", "importance": 0.7, "memory_type": "decision", "metadata": {"tags": ["database", "migration"]}}' \
  '{"content": "Chose Drizzle ORM over Prisma for the new PostgreSQL layer", "importance": 0.8, "memory_type": "decision", "metadata": {"tags": ["orm", "migration"]}}' \
  --namespace project-acme

Four decisions from one session, stored in a single request for $0.04.

Migration from markdown files

If you’re moving from MEMORY.md or daily note files to MemoClaw, the migrate command handles this automatically. But if you want more control over how memories are structured, batch store is the way.

Say you have a MEMORY.md like this:

## Preferences
- Prefers dark mode
- Uses vim keybindings
- Wants responses under 500 words

## Project stack
- Next.js 14 with App Router
- PostgreSQL 16 + pgvector
- Deployed on Vercel + Railway

You could run memoclaw migrate ($0.01 per file). Or parse it yourself and batch-store with proper types and importance scores:

memoclaw store-batch \
  '{"content": "Prefers dark mode", "importance": 0.7, "memory_type": "preference"}' \
  '{"content": "Uses vim keybindings in all editors", "importance": 0.7, "memory_type": "preference"}' \
  '{"content": "Wants agent responses under 500 words", "importance": 0.8, "memory_type": "preference"}' \
  '{"content": "Project stack: Next.js 14 with App Router", "importance": 0.9, "memory_type": "project"}' \
  '{"content": "Database: PostgreSQL 16 with pgvector extension", "importance": 0.9, "memory_type": "project"}' \
  '{"content": "Deployed on Vercel (frontend) and Railway (API + DB)", "importance": 0.8, "memory_type": "project"}'

The manual approach gives you control over memory_type, importance, and tags, which directly affects how well recall works later.

Importing notes from external tools

Pulling memories from Notion, Obsidian, or another tool? Parse them into MemoClaw-shaped objects and batch-store.

curl -X POST https://api.memoclaw.com/v1/store/batch \
  -H "Content-Type: application/json" \
  -d '{
    "memories": [
      {
        "content": "Team standup is at 10am UTC-3, Monday through Friday",
        "importance": 0.7,
        "memory_type": "preference",
        "metadata": {"tags": ["team", "schedule"], "source": "notion"}
      },
      {
        "content": "Sprint reviews happen every other Friday at 3pm",
        "importance": 0.6,
        "memory_type": "project",
        "metadata": {"tags": ["team", "schedule"], "source": "notion"}
      },
      {
        "content": "Use conventional commits: feat/fix/chore/docs prefix required",
        "importance": 0.9,
        "memory_type": "correction",
        "metadata": {"tags": ["git", "conventions"], "source": "notion"}
      }
    ]
  }'

Response:

{
  "ids": ["a1b2c3d4-...", "e5f6a7b8-...", "c9d0e1f2-..."],
  "stored": true,
  "count": 3,
  "deduplicated_count": 0,
  "tokens_used": 45
}

The deduplicated_count field is worth noting. MemoClaw detects near-duplicates during batch storage. If you accidentally include a memory that’s already stored, it merges instead of creating a duplicate.

When NOT to use batch

Real-time context

If your agent needs to store something and recall it in the same conversation turn, use individual stores. Batch requests process as a unit — no streaming partial results.

# Store immediately, recall immediately
memoclaw store "User just changed their deploy target to Fly.io" --importance 0.9
memoclaw recall "What's the current deployment setup?"

With batch, you’d wait for the entire batch to finish before the new memory is recallable.

Small volumes (under 8 memories)

Below breakeven, individual stores cost less. Three memories from a short session: $0.015 individual vs $0.04 batch.

Mixed namespaces

All memories in a batch share the same namespace. If you need to store across different namespaces, you’ll need separate batch calls or individual stores.

# These need separate calls
memoclaw store "Frontend preference: Tailwind CSS" --namespace frontend-agent
memoclaw store "API convention: REST, no GraphQL" --namespace backend-agent

Structuring batch payloads for good recall

Batch storing is easy. Making those memories useful during recall takes more thought.

One fact per memory

Don’t cram multiple facts into a single memory. Each memory gets one embedding vector. Combine unrelated facts and the embedding becomes a blurry average that matches nothing well.

Bad:

{
  "content": "User likes dark mode, deploys to Vercel, and has a dog named Max"
}

Good:

[
  {"content": "User prefers dark mode in all editors and UIs"},
  {"content": "Deployment target: Vercel for frontend applications"},
  {"content": "User has a dog named Max"}
]

Three separate memories, three focused embeddings, three precise recall matches.

Set appropriate memory types

The memory_type field controls decay rate. Use it intentionally:

  • correction — Slowest decay. Facts that override previous information.
  • preference — Slow decay. User preferences that persist long-term.
  • decision — Medium decay. Project decisions that may change.
  • project — Medium decay. Technical context about current work.
  • observation — Fast decay. Contextual notes from a single session.
  • general — Default decay. When nothing else fits.

In a batch, mix types based on what each memory actually is:

memoclaw store-batch \
  '{"content": "API endpoint changed from /v1/users to /v2/users", "memory_type": "correction", "importance": 1.0}' \
  '{"content": "User wants all API responses paginated", "memory_type": "preference", "importance": 0.8}' \
  '{"content": "Discussed switching to GraphQL but decided against it", "memory_type": "decision", "importance": 0.7}' \
  '{"content": "User seemed frustrated with the current auth flow", "memory_type": "observation", "importance": 0.5}'

Use tags for filtering

Tags let you scope recall results without relying on semantic similarity alone:

memoclaw recall "database setup" --tags migration

This filters to only memories tagged migration before ranking by similarity. When batch-storing, add tags consistently:

{
  "memories": [
    {
      "content": "PostgreSQL 16 on Neon serverless",
      "metadata": {"tags": ["database", "infrastructure"]},
      "importance": 0.9
    },
    {
      "content": "pgvector extension for semantic search",
      "metadata": {"tags": ["database", "search"]},
      "importance": 0.8
    },
    {
      "content": "Connection pooling via Neon's built-in pooler",
      "metadata": {"tags": ["database", "infrastructure"]},
      "importance": 0.7
    }
  ]
}

Scale importance deliberately

Importance affects recall ranking. In a batch, don’t set everything to 1.0 — that defeats the purpose.

A practical scale:

  • 1.0 — Critical corrections, identity facts
  • 0.8-0.9 — Strong preferences, key decisions
  • 0.6-0.7 — Useful context, project details
  • 0.4-0.5 — Nice-to-have, background info
  • 0.1-0.3 — Ephemeral, low-priority observations

Automating batch storage

Cron-based session archiving

If your agent generates daily summaries, automate batch storage with a cron job:

#!/bin/bash
# archive-sessions.sh — run daily via cron

SESSIONS_DIR=~/.openclaw/workspace/memory
YESTERDAY=$(date -d "yesterday" +%Y-%m-%d)

# Check if yesterday's memory file exists
if [ -f "$SESSIONS_DIR/$YESTERDAY.md" ]; then
  memoclaw migrate "$SESSIONS_DIR/$YESTERDAY.md" --namespace daily-notes
fi

This uses migrate ($0.01) rather than manual batch parsing, which is simpler for markdown files.

Programmatic batch via API

For more control, build the batch payload programmatically:

cat <<EOF | curl -X POST https://api.memoclaw.com/v1/store/batch \
  -H "Content-Type: application/json" \
  -d @-
{
  "memories": [
    {"content": "Sprint 14 shipped auth v2 and user profiles", "importance": 0.7, "memory_type": "project"},
    {"content": "Performance regression in /api/users — fixed by adding index", "importance": 0.8, "memory_type": "correction"},
    {"content": "Next sprint focus: billing integration with Stripe", "importance": 0.6, "memory_type": "project"}
  ]
}
EOF

Quick reference

ScenarioMethodCost
1-7 memories, immediate recall neededIndividual store$0.005 each
8+ memories, same namespaceBatch store$0.04 flat
Importing markdown filesmemoclaw migrate$0.01/file
Real-time conversation contextIndividual store$0.005 each
End-of-session dumpBatch store$0.04 flat
Cross-namespace storageIndividual stores$0.005 each

What’s next