Ir al contenido principal
Multitenancy en Agentes IA: Contexto Aislado por Cliente

Multitenancy en Agentes IA: Contexto Aislado por Cliente

AI Integration
6 min readPor Daily Miranda Pardo

El agente funciona perfecto en demo. Lo despliegas para el primer cliente, va bien. Añades el segundo. El tercero.

Y entonces ocurre algo que no esperabas: el agente del Cliente B responde con contexto del Cliente A. O peor: no lo notas hasta que el Cliente B te llama para decirte que su asistente IA maneja información que no le pertenece.

Eso no es un bug del modelo. Es un bug de arquitectura. Y es más común de lo que debería.

El error que nadie documenta hasta que ocurre

La mayoría de sistemas multi-tenant nacen para un solo cliente y crecen sobre esa base sin repensar la arquitectura. El system prompt se parametriza, el vector store se reutiliza, la memoria se comparte, y todo parece funcionar.

Hasta que no funciona.

Hay tres patrones de fallo que aparecen sistemáticamente en producción.

Vector store compartido sin filtro de tenant

Un único índice en Pinecone, Weaviate o pgvector para todos los clientes. Las queries van al mismo espacio de embeddings y el LLM recibe fragmentos de cualquier cliente.

En producción esto produce respuestas donde el contexto pertenece a otro tenant. No siempre. A veces. Que es lo peor: parece que funciona hasta que en el peor momento posible deja de funcionar, y entonces no sabes desde cuándo lleva fallando.

System prompt monolítico con variables

Un único template con {company_name}, {tone}, {products}. El problema no es el template — es que no puede capturar la semántica de comportamiento específico de cada cliente. El agente del despacho de abogados acaba pareciéndose demasiado al de la clínica o al de la tienda de ropa.

Memoria stateful sin scope por tenant

Si usas memoria persistente — Redis, Supabase, cualquier base de datos — cada fragmento de memoria necesita un tenant_id. Sin ese campo, el agente puede recuperar contexto de conversaciones de otro cliente cuando hace búsqueda semántica sobre la memoria.

Patrones de arquitectura que sí funcionan

Tenant ID como ciudadano de primera clase

No lo añadas al final como un patch. Diseña desde el principio que cada request lleva un tenant_id verificado, que cada escritura en base de datos incluye ese campo, y que cada query filtra por él.

async function handleAgentRequest(tenantId: string, userMessage: string) {
  // 1. Cargar configuración por tenant
  const config = await tenantConfigRepo.findOrThrow(tenantId)

  // 2. Recuperar memoria con scope aislado
  const memories = await memoryStore.query({
    tenantId,          // siempre presente
    query: userMessage,
    topK: 5,
  })

  // 3. Construir contexto con el system prompt del tenant
  const response = await llm.chat({
    system: buildSystemPrompt(config),
    messages: [...memories.map(toMessage), { role: 'user', content: userMessage }],
  })

  // 4. Guardar con tenant_id en la escritura
  await memoryStore.save({
    tenantId,
    userMessage,
    assistantResponse: response.content,
    timestamp: Date.now(),
  })

  return response
}

El tenantId viaja por toda la cadena: config loading, vector search, memory read/write, audit log. Si en algún punto de esa cadena puede ejecutarse sin tenantId, hay una fuga potencial.

Vector store: colección por tenant o metadata filter

Dos opciones con tradeoffs reales:

Colección separada por tenant (Pinecone namespace, Weaviate class, pgvector schema separado):

  • Aislamiento perfecto, imposible leakage entre tenants
  • Coste más alto cuando escalan los clientes
  • Operaciones de mantenimiento más complejas (reindexar, migrar)
  • Recomendado para datos sensibles o clientes enterprise con SLA de privacidad

Índice compartido con filtro por metadata:

const results = await vectorStore.similaritySearch(userQuery, {
  filter: { tenant_id: { $eq: tenantId } },
  k: 5,
})
// CRÍTICO: verificar que results solo contiene documentos del tenant correcto
if (results.some(r => r.metadata.tenant_id !== tenantId)) {
  throw new Error(`Context leak detected for tenant ${tenantId}`)
}

Esta opción es más barata a escala pero depende de que el filtro funcione correctamente en el vector database que uses. Añade siempre la verificación posterior — si el filtro falla por un bug de implementación o un update de la librería, quieres detectarlo antes que tu cliente.

Config loading: un objeto completo por tenant, no un template

En lugar de interpolar variables en un string, carga un objeto de configuración completo desde base de datos:

interface TenantConfig {
  systemPrompt: string
  allowedTools: ToolName[]
  escalationRules: EscalationRule[]
  model: 'claude-haiku-4-5-20251001' | 'claude-sonnet-5' | 'claude-opus-5'
  maxTokensPerRequest: number
  language: 'es' | 'en' | 'fr'
}

const config = await tenantConfigRepo.findOrThrow(tenantId)

Esto permite que cada cliente tenga herramientas distintas, reglas de escalación distintas, incluso modelos distintos según su plan. El sistema deja de asumir que todos los clientes son iguales — porque no lo son.

Lo que no detectas sin tests de aislamiento

La leakage de contexto entre tenants es silenciosa. No genera un error 500. No aparece en los logs de error del servidor. El agente responde, el request llega con 200, y nadie nota que la respuesta contiene información que no debería estar ahí.

Por eso necesitas tests de aislamiento específicos en tu suite de integration tests:

describe('Tenant isolation', () => {
  it('Tenant B cannot see Tenant A documents', async () => {
    // Indexar documento con dato único para Tenant A
    await vectorStore.upsert([{
      id: 'doc_secret_a',
      content: 'CONFIDENTIAL_MARKER_TENANT_A_XK9',
      metadata: { tenant_id: 'tenant_a' },
    }])

    // Query desde Tenant B
    const results = await vectorStore.similaritySearch(
      'CONFIDENTIAL_MARKER_TENANT_A_XK9',
      { filter: { tenant_id: { $eq: 'tenant_b' } }, k: 5 }
    )

    // Tenant B no debe ver ningún resultado de Tenant A
    expect(results.length).toBe(0)
    expect(results.every(r => r.metadata.tenant_id === 'tenant_b')).toBe(true)
  })
})

Estos tests van en CI. Si algún cambio en la capa de acceso a datos o en el vector DB rompe el aislamiento, lo detectas antes de que lo detecte un cliente.

El coste real de construirlo mal desde el principio

Añadir multitenancy a posteriori no es un refactor limpio. Es una cirugía: cambiar el esquema de base de datos, migrar datos existentes, encontrar cada punto del código que hace queries sin tenant_id, actualizar la pila de llamadas completa.

En sistemas en producción con clientes activos, ese proceso puede llevar semanas y exige un cuidado extremo para no perder datos ni introducir nuevas fugas durante la migración.

El coste de diseñarlo bien desde el principio: dos o tres días de diseño de arquitectura antes de escribir la primera línea de código de negocio.

El coste de arreglarlo en producción con clientes activos: tres semanas mínimo, riesgo de incidente de datos, y una conversación incómoda con los clientes afectados.

En DAILYMP construimos agentes IA con arquitecturas multi-tenant correctas desde el diseño inicial. Si estás empezando un sistema que va a servir a más de un cliente, vale la pena revisar la arquitectura antes de que sea un problema. Y si ya tienes algo en producción y sospechas que el aislamiento no está funcionando bien, el primer paso es auditar cómo viaja el tenant_id por tu sistema.

Antes de escribir la primera línea

La separación de contexto entre tenants no es una feature que se añade cuando escala. Es una decisión de arquitectura que determina si tu sistema puede crecer sin comprometer la privacidad de los datos de tus clientes.

Si tienes dudas sobre el diseño de tu sistema de agentes multi-tenant, en DAILYMP hacemos ese tipo de revisión de arquitectura — con código concreto, no con diagramas genéricos.

Quiero revisar la arquitectura de mi agente multi-tenant →

Compartir artículo

LinkedInXWhatsApp

¿Procesos repetitivos en tu empresa?

Descarga gratis el Mapa de Automatización IA — los 5 procesos que más tiempo roban y cómo resolverlos.

Sin spam. Solo el PDF. Puedes darte de baja cuando quieras.

Escrito por Daily Miranda Pardo

Ayudo a empresas a automatizar procesos, crear agentes IA y conectar sistemas inteligentes.