Multi-Memory Agent Guide

Memory segmentation strategies and the X-Fortemi-Memory header for agents.

Multi-Memory Agent Guide

AI Agent Quick Reference for Fortemi Multi-Memory Architecture

Quick Reference

Memory = isolated PostgreSQL schema. Select via X-Fortemi-Memory header or MCP select_memory.
Default = public schema. Omitting header = default memory (archive_public).
All CRUD/tag/collection/template/version/attachment operations are memory-scoped.
Search is memory-scoped by default. Use federated search to query across multiple memories.
Max memories configurable via MAX_MEMORIES env var (default: 10, scales with hardware — see configuration docs).

When to Create a New Memory (Decision Matrix)

ScenarioUse Memory?Instead UseNotes
Separate client data with legal isolation requirementsYES-Hard schema-level isolation
Separate work vs personalYES-If privacy/legal boundary required
Separate by project (>1000 notes per project)YES-If projects are truly independent
Multi-tenant SaaS deploymentYES-One memory per tenant
Separate by topic within same projectNOTags or CollectionsMemories too heavyweight
Temporary grouping of notesNOCollectionsMemories are permanent structures
Filter search results by categoryNOStrict tag filteringSearch is memory-scoped already
Archive old project dataYES-Clone to archive memory
Test/staging environment isolationYES-Separate memories for test data
Separate by time period (quarterly)MAYBETags if <5000 notes/quarterUse rolling archives for >10k notes
Different embedding models per domainNOEmbedding SetsEach embedding set has its own model config; no need for separate memories
Team knowledge base (single team)NOSingle memory + tagsUnless >50k notes

Segmentation Strategies

Strategy A: By Client/Tenant (Multi-Tenant SaaS)

Pattern: One memory per client (`client-acme`, `client-globex`)

Best for: Multi-tenant deployments, legal data isolation, SaaS platforms

Tradeoffs:

  • Cross-memory search requires federated search (`search_memories_federated`)
  • Perfect data isolation (schema-level)
  • Simplifies backup/restore per client
  • Overhead: ~1MB per empty memory, scales with data

Naming convention: `client-{slug}` or `tenant-{id}`

When to use: Always use for SaaS/multi-tenant scenarios where data isolation is legally or contractually required.

Strategy B: By Project (Independent Work Streams)

Pattern: One memory per major project (`project-alpha`, `project-beta`)

Best for: Teams working on distinct, unrelated projects with minimal knowledge overlap

Tradeoffs:

  • Cross-project knowledge discovery requires explicit effort
  • Simplifies project archival (delete entire memory)
  • Search limited to single project context

When to merge: If projects share >30% of the same concepts/tags, use single memory with project tags instead.

Naming convention: `project-{name}` or `proj-{slug}`

When to use: Projects with >1000 notes each, minimal shared concepts, or need for independent lifecycle management.

Strategy C: By Knowledge Domain (Content Type)

Pattern: Separate domains (`code-docs`, `research-papers`, `meeting-notes`)

Best for: Different content types with different search patterns, embedding models, or retention policies

Tradeoffs:

  • Same topic across domains requires federated search
  • Risk: Domains often overlap, prefer tags within single memory
  • Domain-specific embedding models can be achieved with embedding sets within a single memory

Naming convention: `domain-{type}` or `kb-{category}`

When to use: Only when content types have fundamentally different characteristics (drastically different note sizes, separate retention policies). Note: different embedding models do NOT require separate memories — use embedding sets instead.

Warning: Over-segmentation by domain reduces cross-domain discovery. Prefer single memory with document type metadata.

Strategy D: By Time Period (Rolling Archives)

Pattern: Rolling archives (`archive-2025-q4`, `archive-2026-q1`, plus `active` for current work)

Best for: Compliance, data retention, performance (smaller active set), historical archival

Tradeoffs:

  • Historical search requires federated query across periods (not yet supported)
  • Excellent for compliance (immutable historical archives)
  • Reduces active search space for performance

Operations: Clone current memory → archive at period boundary, then prune active memory.

Naming convention: `archive-{YYYY}-q{N}` or `archive-{YYYY}-{MM}`

When to use: Regulatory compliance requirements, >50k notes with clear temporal boundaries, or need to reduce active search space.

Pattern: Everything in default memory, use tags/collections for organization

Best for: Individual users, small teams, <50,000 notes, no regulatory isolation needs

Tradeoffs:

  • No data isolation (soft boundaries via tags only)
  • Larger search space (but still performant <50k notes)
  • Simplest operational model

When to use: Default strategy unless you have specific requirements driving multi-memory adoption.

Recommended when: No regulatory/legal isolation requirements, team size <20, or total notes <50,000.

Tradeoffs Table

FactorSingle MemoryMultiple Memories
Search speedScales with total notesScales with per-memory notes (smaller scope = faster)
Search scopeAll notes searchablePer-memory by default; use federated search for cross-memory queries
Data isolationTags only (soft)Schema-level (hard, PostgreSQL enforced)
Storage overheadBaseline+~1MB per memory (all per-memory tables + indexes cloned per schema)
Backup granularityAll or nothingPer-memory backup/restore possible
Cross-referencingLinks work anywhereLinks memory-scoped only (no cross-memory links)
Migration complexityNoneExport/import required between memories
Max recommended50,000 notes50,000 notes per memory
Operational overheadAutomaticAutomatic (maintenance runs across all schemas)
Embedding configsPer embedding set (multiple models in one memory)Per embedding set per memory
Tag isolationShared tag namespacesIndependent tag/collection namespaces per memory
Template sharingGlobal templatesTemplates memory-scoped (duplicate across memories)

API Patterns for Agents

HTTP API (cURL examples)

# Create memory
curl -X POST http://localhost:3000/api/v1/memories \
  -H "Content-Type: application/json" \
  -d '{"name": "project-alpha", "description": "Alpha project knowledge base"}'

# List all memories
curl http://localhost:3000/api/v1/memories

# Get memory stats
curl http://localhost:3000/api/v1/archives/project-alpha/stats

# Create note in specific memory
curl -X POST http://localhost:3000/api/v1/notes \
  -H "Content-Type: application/json" \
  -H "X-Fortemi-Memory: project-alpha" \
  -d '{"title": "Note", "content": "Content", "metadata": {}}'

# Search in specific memory
curl http://localhost:3000/api/v1/search?q=query \
  -H "X-Fortemi-Memory: project-alpha"

# Federated search across multiple memories
curl -X POST http://localhost:3000/api/v1/search/federated \
  -H "Content-Type: application/json" \
  -d '{"query": "search terms", "memories": ["project-alpha", "project-beta"]}'

# Clone memory (backup/snapshot)
curl -X POST http://localhost:3000/api/v1/archives/project-alpha/clone \
  -H "Content-Type: application/json" \
  -d '{"new_name": "project-alpha-backup-2026-02", "description": "Monthly backup"}'

# Delete memory (IRREVERSIBLE, CASCADE)
curl -X DELETE http://localhost:3000/api/v1/memories/project-alpha

MCP Tool Patterns

// Select active memory for MCP session (persists across calls)
await select_memory({ name: "project-alpha" });

// Check current active memory
const active = await get_active_memory();
// Returns: { name: "project-alpha" }

// All subsequent MCP calls use selected memory automatically
await capture_knowledge({
  action: "create",
  title: "Note",
  content: "...",
  metadata: {}
}); // goes to project-alpha

await search({
  action: "text",
  query: "..."
}); // searches project-alpha only

// List all memories (API-only, use GET /api/v1/archives)
// Note: Archive management is API-only; use select_memory for switching contexts

// Create new memory (API-only, use POST /api/v1/archives)

// Delete memory (API-only, use DELETE /api/v1/archives/:name)

Common Mistakes

MistakeWhy It's WrongDo This Instead
Creating memory per tagMemories are heavyweight schema isolation (full table set + indexes per schema)Use tags within single memory
Forgetting X-Fortemi-Memory header in HTTP APIOperations go to default memory silently, no errorAlways set header explicitly or use select_memory in MCP
Searching without selecting memorySearch defaults to the public schema if no memory is selectedAlways select memory first via `X-Fortemi-Memory` header or `select_memory` in MCP
Exceeding MAX_MEMORIES limitEach clones the full per-memory table set with indexesIncrease MAX_MEMORIES per hardware tier, prefer tags/collections
Manual vacuum schedulingUnnecessary — Fortemi handles maintenance automatically across all memory schemasNo action needed; auto-maintenance covers all schemas at normalized intervals
Assuming cross-memory links workLinks are memory-scoped, no foreign keys across schemasExport/import notes or use same memory
Using memories for temporary groupingMemories are permanent structures, expensive to deleteUse collections for temporary grouping
Over-segmenting by topicReduces discoverability, creates knowledge silosUse single memory with tags unless >10k notes per topic
Creating memories just for different embedding modelsEmbedding configs are bound to embedding sets, not memoriesUse multiple embedding sets within a single memory for different models
Expecting auto-migration between memoriesNo built-in migration, must export/importPlan memory structure upfront, migrations are manual

MCP Tool Quick Reference

ToolPurposeUses Active Memory?Returns Error if Non-Default?
`select_memory`Set active memory for MCP sessionSets it (no active required)No
`get_active_memory`Check current active memoryNo (retrieves state)No
`capture_knowledge`Create/update/delete notesYesNo
`search`Search notes (text/semantic/combined/federated)YesNo (per-schema pools enable search in all memories)
`manage_tags`Tag operations (list/add/remove/update)YesNo
`manage_collection`Collection operationsYesNo
`record_provenance`Provenance trackingYesNo
`manage_concepts`SKOS concept operationsYesNo
`manage_embeddings`Embedding set CRUD, membership, refreshYesNo

Performance Characteristics

Memory Creation

  • Time: ~200ms (clones all per-memory tables + indexes)
  • Storage: ~1MB empty, scales with data
  • Limit: 10 memories by default (scale via MAX_MEMORIES env var per hardware tier)

Memory Cloning

  • Time: Proportional to data size (indexes rebuilt)
  • Storage: Doubles (full copy including embeddings)
  • Use case: Backups, snapshots, archival

Memory Deletion

  • Time: ~500ms (CASCADE drop)
  • IRREVERSIBLE: No recovery, no soft delete
  • WARNING: Deletes all notes, tags, collections, embeddings, attachments

Search Performance

  • Default memory: <100ms typical for <50k notes
  • Non-default memory: Uses per-schema connection pool; similar performance to default
  • Federated search: Queries multiple memories in parallel with unified ranking

Decision Flow for Agents

START: Does user need multi-memory?
  |
  +--> NO legal/regulatory isolation required?
  |    +--> <50k total notes expected?
  |         +--> Use SINGLE MEMORY (default)
  |
  +--> Multi-tenant SaaS?
  |    +--> YES --> Strategy A: One memory per tenant
  |
  +--> Independent projects with <30% concept overlap?
  |    +--> YES --> Strategy B: One memory per project
  |
  +--> Content types need different retention/chunking policies?
  |    +--> YES --> Strategy C: One memory per domain
  |    (Note: different embedding models do NOT require separate memories — use embedding sets)
  |
  +--> Compliance requires historical immutability?
  |    +--> YES --> Strategy D: Rolling time-based archives
  |
  +--> Default to SINGLE MEMORY, use tags/collections

Migration Path

From Single Memory to Multi-Memory

1. Create target memory: `POST /api/v1/memories` 2. Export notes: Use bulk export with tag filter 3. Import to new memory: `POST /api/v1/notes` with `X-Fortemi-Memory` header 4. Verify: Check note counts match 5. Update agents: Change MCP `select_memory` or API headers 6. Archive old memory: Clone to backup, then delete (optional)

From Multi-Memory to Single Memory

1. Export all memories: Per-memory bulk export 2. Tag for provenance: Add `source-memory:{name}` tag to all notes 3. Import to default memory: Omit `X-Fortemi-Memory` header 4. Update links: Re-link notes (links don't migrate across memories) 5. Verify: Check total note counts 6. Delete old memories: After verification period

Operational Checklist

  • [ ] Plan memory structure upfront (migrations are manual)
  • [ ] Limit to <50 memories for performance
  • [ ] Set `X-Fortemi-Memory` header explicitly in all HTTP API calls
  • [ ] Use `select_memory` at start of MCP sessions
  • [ ] Use `search` for per-memory search, `search_memories_federated` for cross-memory
  • [ ] Verify auto-maintenance is running (maintenance covers all schemas automatically)
  • [ ] Clone memories before risky operations (deletion is irreversible)
  • [ ] Monitor memory count (`list_archives`) against MAX_MEMORIES limit
  • [ ] Use tags/collections for organization within memories
  • [ ] Document memory naming conventions for team

Future Features (Not Yet Implemented)

  • Cross-memory links: Reference notes across schema boundaries
  • Memory templates: Pre-configured memories with tags/collections
  • Auto-archival: Scheduled cloning to time-based archives
  • Memory-level permissions: Fine-grained access control per memory

Summary

Default recommendation: Use single memory with tags/collections unless you have: 1. Legal/regulatory data isolation requirements (multi-tenant, client separation) 2. >50k notes with clear segmentation boundaries (projects, time periods)

Note: Different embedding models do NOT require separate memories — use embedding sets within a single memory instead.

Key principle: Memories are heavyweight. Over-segmentation reduces discoverability. When in doubt, use tags.