← All Cookbooks
LangChainAdvanced30 min

LangChain + HatiData: Multi-Tenant Memory

Build a multi-tenant LangChain application where each customer's agent memories are logically isolated in HatiData using namespace-based tenancy.

What You'll Build

A multi-tenant LangChain application where each customer's agent memories are logically isolated in HatiData.

Prerequisites

$pip install langchain langchain-openai hatidata-agent

$hati init

$OpenAI API key

Architecture

┌──────────────┐    ┌──────────────┐    ┌──────────────┐
│  LangChain   │───▶│  HatiData    │───▶│   Engine     │
│  (Tenant A)  │    │  Namespace A │    │  + Vectors   │
└──────────────┘    └──────────────┘    └──────────────┘
┌──────────────┐    ┌──────────────┐    ┌──────────────┐
│  LangChain   │───▶│  HatiData    │───▶│   Engine     │
│  (Tenant B)  │    │  Namespace B │    │  + Vectors   │
└──────────────┘    └──────────────┘    └──────────────┘

Key Concepts

  • Namespace isolation: each tenant's memories live in a dedicated namespace, providing logical data separation
  • Naming convention: tenant-{customer_id}-{type} makes namespaces self-documenting and easy to manage
  • Defense in depth: combine namespace isolation with API key scoping for production-grade multi-tenancy
  • Admin analytics: platform operators can query across all namespaces for reporting without breaking tenant isolation
  • Implicit creation: namespaces are created on first write — no provisioning step needed for new tenants

Step-by-Step Implementation

1

Install Dependencies

Install LangChain and the HatiData agent SDK.

Bash
pip install langchain langchain-openai hatidata-agent
hati init
Expected Output
HatiData initialized successfully.
Proxy running on localhost:5439

Note: Multi-tenancy is achieved through namespace isolation. Each tenant's memories live in a separate namespace.

2

Create Tenant Namespaces

Set up isolated memory namespaces for each tenant.

Python
from hatidata_agent import HatiDataAgent

hati = HatiDataAgent(host="localhost", port=5439, agent_id="tenant-manager")

# Tenant namespaces are created implicitly on first write
# Convention: tenant-{customer_id}-{memory_type}
tenants = [
    {"id": "acme-corp", "plan": "enterprise"},
    {"id": "startup-xyz", "plan": "growth"},
    {"id": "agency-123", "plan": "free"},
]

for tenant in tenants:
    # Store a welcome memory in each tenant's namespace
    hati.execute(f"""
        SELECT store_memory(
            'Welcome to HatiData! Your {tenant["plan"]} plan is active.',
            'tenant-{tenant["id"]}-system'
        )
    """)
    print(f"  Created namespace: tenant-{tenant['id']}-system ({tenant['plan']})")

print(f"\nInitialized {len(tenants)} tenant namespaces")
Expected Output
  Created namespace: tenant-acme-corp-system (enterprise)
  Created namespace: tenant-startup-xyz-system (growth)
  Created namespace: tenant-agency-123-system (free)

Initialized 3 tenant namespaces
3

Store Tenant-Scoped Memories

Each tenant's agent stores memories that are invisible to other tenants.

Python
# Acme Corp agent stores customer data
acme_agent = HatiDataAgent(host="localhost", port=5439, agent_id="acme-support")
acme_agent.execute("""
    SELECT store_memory(
        'Acme Corp uses quarterly billing. Primary contact: alice@acme.com',
        'tenant-acme-corp-customers'
    )
""")
acme_agent.execute("""
    SELECT store_memory(
        'Acme Corp renewed enterprise contract for 3 years in Jan 2026',
        'tenant-acme-corp-customers'
    )
""")

# Startup XYZ agent stores different data
startup_agent = HatiDataAgent(host="localhost", port=5439, agent_id="startup-support")
startup_agent.execute("""
    SELECT store_memory(
        'Startup XYZ prefers monthly billing. Tech stack: Python + FastAPI',
        'tenant-startup-xyz-customers'
    )
""")

print("Acme Corp: 2 memories stored")
print("Startup XYZ: 1 memory stored")
Expected Output
Acme Corp: 2 memories stored
Startup XYZ: 1 memory stored

Note: Namespace convention: tenant-{customer_id}-{type}. This ensures complete logical isolation between tenants.

4

Verify Tenant Isolation

Demonstrate that tenants cannot see each other's memories.

Python
# Acme Corp agent searches — only sees Acme data
acme_results = acme_agent.query("""
    SELECT agent_id, content
    FROM _hatidata_memory.memories
    WHERE namespace = 'tenant-acme-corp-customers'
    ORDER BY created_at DESC
""")
print(f"Acme Corp sees {len(acme_results)} memories:")
for r in acme_results:
    print(f"  {r['content'][:70]}...")

# Startup XYZ agent searches — only sees Startup data
startup_results = startup_agent.query("""
    SELECT agent_id, content
    FROM _hatidata_memory.memories
    WHERE namespace = 'tenant-startup-xyz-customers'
    ORDER BY created_at DESC
""")
print(f"\nStartup XYZ sees {len(startup_results)} memories:")
for r in startup_results:
    print(f"  {r['content'][:70]}...")

# Startup tries to access Acme namespace — returns empty
cross_results = startup_agent.query("""
    SELECT COUNT(*) AS cnt
    FROM _hatidata_memory.memories
    WHERE namespace = 'tenant-acme-corp-customers'
""")
print(f"\nStartup XYZ querying Acme namespace: {cross_results[0]['cnt']} results (isolated)")
Expected Output
Acme Corp sees 2 memories:
  Acme Corp uses quarterly billing. Primary contact: alice@acme.com...
  Acme Corp renewed enterprise contract for 3 years in Jan 2026...

Startup XYZ sees 1 memories:
  Startup XYZ prefers monthly billing. Tech stack: Python + FastAPI...

Startup XYZ querying Acme namespace: 0 results (isolated)

Note: In production, combine namespace isolation with API key scoping for defense-in-depth multi-tenancy.

5

Admin Cross-Tenant Query

Admin users can query across all tenant namespaces for reporting and analytics.

Python
# Admin agent can see all namespaces
admin_agent = HatiDataAgent(host="localhost", port=5439, agent_id="platform-admin")

# Cross-tenant analytics
stats = admin_agent.query("""
    SELECT
        SPLIT_PART(namespace, '-', 2) || '-' || SPLIT_PART(namespace, '-', 3) AS tenant,
        COUNT(*) AS memory_count,
        MAX(created_at) AS last_activity
    FROM _hatidata_memory.memories
    WHERE namespace LIKE 'tenant-%'
    GROUP BY tenant
    ORDER BY memory_count DESC
""")

print("=== Cross-Tenant Analytics (Admin) ===")
for s in stats:
    print(f"  {s['tenant']}: {s['memory_count']} memories, last active: {s['last_activity']}")

total = sum(int(s['memory_count']) for s in stats)
print(f"\nTotal memories across all tenants: {total}")
Expected Output
=== Cross-Tenant Analytics (Admin) ===
  acme-corp: 3 memories, last active: 2026-02-28 15:10:00
  startup-xyz: 2 memories, last active: 2026-02-28 15:09:30
  agency-123: 1 memories, last active: 2026-02-28 15:09:00

Total memories across all tenants: 6

Note: Admin access should be restricted to platform operators only. Combine with API key scopes for proper access control.

Ready to build?

Install HatiData locally and start building with LangChain in minutes.

Join Waitlist