El deploy terminó bien, pero una ejecución antigua volvió con el payload de la tool v1. El handler nuevo espera subject y priority; el payload persistido todavía contiene title y details. El fallo aparece solo cuando alguien retoma la ejecución, después de que el cambio ya está en producción.

Para evitar esta ruptura, trata la versión del contrato como parte de la ejecución. Registra qué versión de la tool se eligió, valida la entrada en el límite, adapta las versiones antiguas en un solo lugar y detén el proceso antes del efecto cuando la versión sea desconocida. No dejes que cada handler adivine si un payload antiguo sigue siendo seguro.

Esto es distinto de validar una llamada. Validar tool calls de agentes de IA en TypeScript ayuda a rechazar argumentos inválidos. Este artículo trata el cambio del contrato a lo largo del tiempo, mientras siguen vivos las colas, los checkpoints y el contexto antiguo.

Diagrama de una ejecución antigua que pasa por un adaptador de contrato mientras se rechaza una versión desconocida.

Respuesta breve

  • Registra las versiones del contrato de entrada y salida junto con la ejecución.
  • Haz que cada ejecución nueva elija una versión explícita.
  • Mantén un adaptador para versiones antiguas mientras haya trabajo en curso.
  • Valida y adapta antes de llamar al handler que crea efectos externos.
  • Rechaza versiones desconocidas y retira el adaptador antiguo solo después de drenar esas ejecuciones.

¿Qué se rompe cuando cambia una tool?

Una tool expuesta a un modelo tiene más partes que su función. Su nombre, descripción, schema de entrada, resultado y reglas de efecto influyen en lo que el agente puede pedir y en cómo el runtime interpreta la respuesta. La especificación de Tools de MCP define una tool con name, description, inputSchema y un outputSchema opcional. En el function calling de OpenAI, el schema también forma parte de la definición enviada al modelo.

Un cambio puede ser estructural o semántico:

Cambio Riesgo para una ejecución antigua
Agregar un campo opcional Normalmente bajo si el handler antiguo lo ignora y el camino nuevo acepta que falte.
Cambiar title por subject Alto. El payload antiguo no contiene el campo que lee el handler nuevo.
Cambiar status de string a objeto Alto. El JSON puede ser válido aunque haya cambiado el significado.
Eliminar una opción de enum Alto para tareas persistidas o llamadas en cola.
Cambiar lo que hace send_email sin cambiar su schema Muy alto. La forma parece compatible, pero la aprobación puede no cubrir el comportamiento nuevo.

El schema de un proveedor puede evitar algunos argumentos mal formados. No puede decidir por sí solo si la ejecución de ayer puede usar la política de hoy. Esa decisión pertenece al runtime de la aplicación.

El contrato debe viajar con la ejecución

Mantén separados al menos estos tres identificadores:

  1. agentRelease: la composición ejecutable, incluidas instrucciones, modelo, tools y políticas.
  2. toolContractVersion: la versión del contrato que la ejecución usó para elegir y construir la llamada.
  3. runId: la identidad de la ejecución que puede reanudarse, repetirse o investigarse.

No uses un único campo llamado version para los tres. Una descripción nueva de una tool puede no exigir migrar el estado. Una política nueva puede exigir una aprobación nueva aunque el JSON no cambie. Un deploy puede cambiar el código sin cambiar el contrato de la tool.

Un registro mínimo puede tener esta forma:

{
  "runId": "run_7f2",
  "agentRelease": "support-agent-2026-10-29.1",
  "toolContractVersion": 1,
  "status": "waiting_for_tool",
  "nextTool": "create_ticket"
}

runId no es un control de compatibilidad. Permite localizar el trabajo. La versión del contrato indica qué interpretación debe aplicar el runtime antes de ejecutar.

¿Dónde debe vivir el adaptador de compatibilidad?

Coloca el adaptador entre el mensaje recibido y el handler de la tool. El límite debe recibir un sobre versionado, validar la forma de esa versión y producir un único formato interno para el handler actual.

El ejemplo siguiente es ilustrativo y no reemplaza la validación del schema. Muestra la separación importante: el handler de negocio no necesita conocer todos los formatos históricos.

type ToolCall = {
  name: "create_ticket";
  contractVersion: 1 | 2;
  arguments: unknown;
};

type CreateTicketV1 = {
  title: string;
  details: string;
};

type CreateTicketV2 = {
  subject: string;
  body: string;
  priority: "low" | "normal" | "high";
};

function adaptCreateTicket(call: ToolCall): CreateTicketV2 {
  if (call.contractVersion === 1) {
    const oldArgs = call.arguments as CreateTicketV1;

    return {
      subject: oldArgs.title,
      body: oldArgs.details,
      priority: "normal",
    };
  }

  if (call.contractVersion === 2) {
    return call.arguments as CreateTicketV2;
  }

  throw new Error("Unsupported tool contract version");
}

async function execute(call: ToolCall) {
  const args = adaptCreateTicket(call);
  // Valida `args` aquí en una implementación real.
  return createTicket(args);
}

En producción, valida arguments contra el schema de esa versión antes del cast, limita qué versiones puede atender cada release y registra la versión resuelta en el trace. OpenAI recomienda strict: true para que las llamadas de función sigan el schema proporcionado. Eso reduce las llamadas mal formadas, pero la compatibilidad entre v1 y v2 todavía debe existir en tu sistema.

El adaptador también es el lugar donde decides si una respuesta antigua puede volver al agente. Si v2 devuelve { ticketId, url } y una ejecución v1 espera { id }, adapta la salida, conserva el worker antiguo o detén el flujo explícitamente. No envíes un objeto nuevo a un consumidor antiguo solo porque ambos son JSON válido.

Cuatro decisiones para cada versión activa

Cuando encuentres una llamada antigua, elige una política explícita:

Situación Política Acción
El contrato antiguo todavía es seguro Fijar Ejecuta con el worker y el schema antiguos hasta que termine la ejecución.
Cambió la estructura, pero no el significado Adaptar Valida la versión antigua, conviértela en el límite y llama al handler actual.
El estado puede convertirse sin perder una decisión o efecto Migrar Aplica una transformación registrada, valida el estado nuevo y marca su versión.
La versión es desconocida o cambió la semántica Rechazar Detén el flujo antes del efecto, registra el motivo y pide intervención o una nueva planificación.

El error más peligroso es confundir adaptar con migrar. Adaptar convierte una llamada en el límite de entrada o salida. Migrar cambia el estado persistido de la ejecución. Si una persona aprobó los argumentos antiguos, convertir el JSON no demuestra que la aprobación siga cubriendo los argumentos nuevos.

Los sistemas de workflow hacen una distinción parecida. La documentación de Temporal sobre versionado de workflows separa workers ligados a revisiones de código y cambios compatibles mediante patching. Su guía de versionado de workers describe ejecuciones fijadas a una versión de deployment para evitar que el cambio de código rompa trabajo en curso. La plataforma no define la semántica de tu tool, pero el principio se mantiene: una ejecución larga necesita una ruta conocida entre la versión que la creó y la que la retoma.

¿Cómo desplegar el cambio sin adivinar?

Un despliegue seguro no empieza sustituyendo el schema en todas partes. Haz el cambio por etapas:

  1. Inventaria el trabajo activo. Cuenta ejecuciones por versión de contrato, etapa, edad, aprobación pendiente y efecto externo confirmado. Una cola vacía no demuestra que no exista un checkpoint dormido en la base de datos.
  2. Define la compatibilidad. Lista campos agregados y eliminados, renombres, cambios de enum y cambios de efecto. Marca cada uno como fijar, adaptar, migrar o rechazar.
  3. Despliega el adaptador antes del productor v2. La versión nueva debe poder leer una llamada v1 antes de que una ejecución empiece a producir v2.
  4. Fija la elección al iniciar la ejecución. Una reanudación no debe recalcular el conjunto de tools solo porque el deployment actual es más nuevo.
  5. Envía las ejecuciones nuevas a v2. Productor, runtime y tool deben coincidir en la versión. Si la selección ocurre en el prompt, registra también esa decisión en el sobre del runtime.
  6. Observa la transición. Registra conteos por versión, rechazos, adaptaciones, errores de validación, efectos duplicados y ejecuciones detenidas.
  7. Drena y retira. Quita el adaptador solo cuando ninguna ejecución activa lo necesite o cuando una migración verificada las haya reemplazado a todas.

Si el sistema usa MCP, no trates el cambio de la lista de tools como una migración de contrato. El protocolo define el descubrimiento y las llamadas, y los servidores pueden avisar que la lista cambió. La aplicación sigue siendo responsable de la relación entre ejecución, schema y política.

¿Cómo probar un cambio de schema?

Construye una matriz pequeña antes del deploy. Cada fila necesita una entrada y un resultado esperado en el límite:

Caso Resultado esperado
Llamada v1 válida Se adapta al formato interno actual.
Llamada v1 sin un campo obligatorio Se rechaza sin efecto externo.
Llamada v2 válida La ejecuta el handler actual.
Versión desconocida Se rechaza antes del handler.
Respuesta v2 para un consumidor v1 Se adapta, se mantiene en el worker antiguo o se detiene explícitamente.
Reintento después de un timeout Consulta el estado de la operación antes de crear otro efecto.

El test de contrato debe verificar más que un status HTTP. Comprueba la versión recibida, el schema validado, los argumentos normalizados, el handler llamado y la ausencia de efecto en el camino rechazado. El artículo sobre pruebas de contrato para un servidor MCP en TypeScript muestra cómo convertir la interfaz de una tool en un límite comprobable.

Para ejecuciones persistidas, conserva fixtures de cada versión que todavía pueda aparecer. Combínalas con pruebas de regresión de agentes sin comparar texto exacto. La pregunta no es si el modelo repitió la misma frase. Es si el runtime siguió eligiendo una tool permitida, conservó la intención registrada y evitó ejecutar un efecto con argumentos ambiguos.

Fallos que parecen compatibilidad, pero no lo son

Aceptar cualquier objeto JSON. La sintaxis válida no es un contrato válido. Comprueba campos obligatorios, tipos, enums e invariantes de negocio antes del handler.

Mantener v2 en el nombre de cada tool para siempre. create_ticket_v1 y create_ticket_v2 pueden ayudar durante una transición. Si se convierten en el catálogo permanente, el modelo ve opciones duplicadas y el equipo tiene más superficies que mantener.

Migrar el estado sin revisar las aprobaciones. Una aprobación está vinculada a lo que vio una persona, no solo a lo que la base de datos puede convertir. Un cambio material en los parámetros debe volver a abrir la decisión.

Retirar el adaptador después del primer deploy verde. Un deploy demuestra que el código nuevo inició. No demuestra que hayan desaparecido las colas, reintentos, sesiones o checkpoints antiguos.

Esconder la versión en el prompt. El prompt puede ayudar al modelo a elegir una tool, pero el runtime debe validar la versión recibida. El límite de seguridad no puede depender solamente de la instrucción que leyó el modelo.

Lista de comprobación antes de publicar v2

  • ¿Cada ejecución registra agentRelease y toolContractVersion por separado?
  • ¿El adaptador valida el payload antiguo antes de transformarlo?
  • ¿El handler actual recibe un único formato interno ya validado?
  • ¿Una versión desconocida falla antes de cualquier efecto externo?
  • ¿Existe una política de compatibilidad para la salida?
  • ¿Hay fixtures para todas las versiones todavía activas?
  • ¿Se vuelven a evaluar las aprobaciones antiguas cuando cambia el significado de la acción?
  • ¿El trace permite encontrar la ejecución, la versión, la decisión y el resultado?
  • ¿El adaptador tiene una condición verificable para retirarlo?

Conclusión

Versionar una tool significa controlar un cambio de contrato, no solo renombrar una función. Fija la versión cuando comienza la ejecución, valida y adapta en el límite, mantén separadas las ejecuciones nuevas y antiguas, y rechaza lo que no puedas interpretar con seguridad. Así reduces la posibilidad de que un deploy rompa trabajo ya aceptado y dejas claro cuándo terminó la compatibilidad.

Si el cambio también incluye checkpoints, colas o reanudaciones, relaciónalo con la ejecución durable de agentes con checkpoints, colas y workflows. Una tool no debe ser la única parte versionada de una ejecución larga.

Cómo se produjo este artículo

Samuel Fajreldines escribió este artículo a partir del inventario de posts y enlaces del repositorio, la inspección de resultados actuales y la documentación pública de MCP, OpenAI y Temporal. La matriz fijar, adaptar, migrar y rechazar es una síntesis original para explicar el límite de compatibilidad. El código es ilustrativo: no se ejecutó contra un proveedor de modelos, un servidor MCP ni una cola de producción.

Usé RemoteCode como herramienta del autor para organizar la investigación y revisar la consistencia del texto. No ejecutó un agente de producción ni sustituye la validación en el entorno del lector.

Fuentes consultadas