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
Install Dependencies
Install LangChain and the HatiData agent SDK.
pip install langchain langchain-openai hatidata-agent
hati initHatiData initialized successfully.
Proxy running on localhost:5439Note: Multi-tenancy is achieved through namespace isolation. Each tenant's memories live in a separate namespace.
Create Tenant Namespaces
Set up isolated memory namespaces for each tenant.
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") Created namespace: tenant-acme-corp-system (enterprise)
Created namespace: tenant-startup-xyz-system (growth)
Created namespace: tenant-agency-123-system (free)
Initialized 3 tenant namespacesStore Tenant-Scoped Memories
Each tenant's agent stores memories that are invisible to other tenants.
# 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")Acme Corp: 2 memories stored
Startup XYZ: 1 memory storedNote: Namespace convention: tenant-{customer_id}-{type}. This ensures complete logical isolation between tenants.
Verify Tenant Isolation
Demonstrate that tenants cannot see each other's memories.
# 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)")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.
Admin Cross-Tenant Query
Admin users can query across all tenant namespaces for reporting and analytics.
# 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}")=== 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: 6Note: Admin access should be restricted to platform operators only. Combine with API key scopes for proper access control.
Related Use Case
Operations
Customer Support
Agents That Remember Every Customer