Investigación

Mejorando continuamente nuestra harness de agentes

Stefan Heule & Jediah Katz12 min de lectura

Abordamos la creación de la harness de agentes de Cursor igual que abordaríamos cualquier producto de software ambicioso. Gran parte del trabajo está guiado por una visión: partimos de una idea clara de cómo debería ser la experiencia ideal con agentes.

A partir de ahí, formulamos hipótesis sobre cómo acercarnos a esa visión, hacemos experimentos para ponerlas a prueba e iteramos a partir de señales cuantitativas y cualitativas procedentes de las evaluaciones y del uso real. Ese proceso depende de contar con la instrumentación online y offline adecuada, para poder saber cuándo un cambio realmente mejora la harness.

Cuando obtenemos acceso anticipado a nuevos modelos, todos estos enfoques convergen. Dedicamos semanas a personalizar nuestra harness según las fortalezas y particularidades de un modelo, hasta que ese mismo modelo, dentro de nuestra harness especialmente ajustada, es notablemente más rápido, más inteligente y más eficiente.

De vez en cuando descubrimos mejoras cualitativas. Pero, más a menudo, mejorar la harness consiste en acumular obsesivamente pequeñas optimizaciones que, en conjunto, hacen que los agentes sean mejores desarrollando software.

La evolución de la ventana de contexto

En el centro de la interacción con los modelos de lenguaje de gran tamaño está la ventana de contexto. Cuando se le pide al agente que cree algo, la ventana de contexto empieza con el prompt del sistema y las descripciones de las herramientas, seguida del estado actual de la conversación y, por último, de la solicitud del usuario.

La forma en que llenamos y gestionamos esa ventana ha evolucionado de forma significativa a lo largo de la historia de Cursor.

Cuando desarrollamos por primera vez nuestro agente de programación a finales de 2024, a los modelos les costaba mucho más elegir su propio contexto, e invertimos mucho trabajo de ingeniería de contexto en crear salvaguardas; por ejemplo, mostrar al agente los errores de linting y de tipado después de cada edición, reescribir las lecturas de archivos cuando solicitaba muy pocas líneas, e incluso limitar la cantidad máxima de herramientas que podía usar en un mismo turno.

También proporcionábamos grandes cantidades de contexto estático que siempre estaban disponibles para el agente al inicio de cada sesión. En distintos momentos, eso incluía la estructura de carpetas de la base de código, fragmentos de código que coincidían semánticamente con la consulta y versiones comprimidas de archivos que el usuario adjuntaba manualmente.

Eso ya casi ha desaparecido por completo.

Seguimos incluyendo algo de contexto estático útil (p. ej., sistema operativo, estado de git, archivo actual y archivos vistos recientemente). Pero nos hemos adaptado a la mayor capacidad de los modelos eliminando salvaguardas y proporcionando más contexto dinámico, que el agente puede obtener mientras trabaja. En una publicación anterior, hicimos un análisis en profundidad de algunas de nuestras técnicas detrás del contexto dinámico, muchas de las cuales desde entonces han sido adoptadas por otros agentes de programación. Gran parte de nuestro trabajo ahora se centra en ofrecer más formas de que el agente obtenga contexto de manera dinámica e interactúe con el mundo.

Con el contexto dinámico, el modelo puede decidir cuándo incorporar información adicional a la ventana de contexto, como conversaciones anteriores, sesiones de terminal activas o herramientas relevantes.Con el contexto dinámico, el modelo puede decidir cuándo incorporar información adicional a la ventana de contexto, como conversaciones anteriores, sesiones de terminal activas o herramientas relevantes.

Dos formas de evaluar cambios en el harness

El harness y el modelo juntos determinan qué tan bueno es el agente, pero no es fácil precisar qué significa "bueno". Para medirlo, hemos creado varias capas de evaluación.

Mantenemos benchmarks públicos junto con nuestro propio conjunto de evaluaciones, CursorBench, que nos proporciona una medida rápida y estandarizada de la calidad y nos permite comparar a lo largo del tiempo. Pero incluso los mejores benchmarks solo se aproximan al uso real, así que pasaríamos por alto señales importantes si dependiéramos por completo de ellos.

Por eso también realizamos experimentos en línea en los que desplegamos dos o más variantes del harness en paralelo y las sometemos a pruebas A/B con uso real. Medimos la calidad del agente en estas pruebas mediante diversas métricas. Algunas son directas, como la latencia, la eficiencia en el uso de tokens, el recuento de llamadas a herramientas y la tasa de aciertos en caché. Sirven para orientarnos, pero aun así no responden a las preguntas más difusas, y más importantes, sobre si el agente realmente hizo un buen trabajo. Medimos eso de dos maneras.

La primera es la "Keep Rate" del código generado por el agente. Para un conjunto determinado de cambios de código que el agente propuso, hacemos un seguimiento de qué fracción de esos cambios permanece en la base de código del usuario después de intervalos de tiempo fijos. Esto nos permite entender cuándo los usuarios tienen que ajustar manualmente el resultado del agente, o necesitan iterar y pedirle al agente que solucione cosas, lo que indica que la respuesta inicial del agente era de menor calidad.

En segundo lugar, usamos un modelo de lenguaje para leer las respuestas del usuario al resultado inicial del agente y determinar, a nivel semántico, si quedó satisfecho o no. Que un usuario pase a la siguiente funcionalidad es una señal clara de que el agente hizo su trabajo, mientras que que un usuario pegue un stack trace es una señal fiable de que no lo hizo.

A veces, estas pruebas en línea nos dicen que dejemos de lado una idea que parece prometedora. En un experimento, probamos un modelo más caro para resumir el contexto y observamos que producía una diferencia insignificante en la calidad del agente que no justificaba el mayor costo.

Seguimiento y reparación de degradaciones

A medida que añadimos más modelos y capacidades, el harness se vuelve más complejo y tiene más estados posibles, como cualquier otra pieza de software. Con ello también aumenta la superficie expuesta a errores, muchos de los cuales solo podemos detectar a escala.

Las herramientas del agente son una de las áreas más amplias donde pueden surgir errores, y los errores en las llamadas a herramientas pueden ser extremadamente perjudiciales para una sesión en Cursor. Aunque el agente a menudo puede autocorregirse, los errores permanecen en el contexto, desperdician tokens y provocan un “deterioro del contexto”, donde los fallos acumulados degradan la calidad de las decisiones posteriores del modelo.

A veces, el agente puede quedarse bloqueado o descarrilar por completo tras una llamada a herramienta fallida. Aunque métricas como el volumen de llamadas a herramientas y la tasa de errores no miden directamente si el agente hizo un buen trabajo, sí sirven como indicadores de una incidencia más amplia.

Cualquier error desconocido representa un bug en el harness, y lo tratamos como tal. Pero muchos errores son “esperados”; por ejemplo, el modelo propone ocasionalmente una edición incorrecta o intenta leer un archivo que no existe. Clasificamos estos errores esperados según su causa. InvalidArguments y UnexpectedEnvironment reflejan errores del modelo y contradicciones en la ventana de contexto, mientras que ProviderError recoge interrupciones del proveedor en herramientas como GenerateImage o WebSearch.

Tenemos otras clasificaciones, como UserAborted y Timeout, que en conjunto abarcan la mayoría de los errores esperados.

En un sprint centrado a principios de este año, llevamos todas las llamadas a herramientas a al menos dos y, a menudo, tres nueves de fiabilidad.En un sprint centrado a principios de este año, llevamos todas las llamadas a herramientas a al menos dos y, a menudo, tres nueves de fiabilidad.

Definimos alertas basadas en estas métricas para detectar regresiones importantes que llegan a producción. Como los errores desconocidos siempre son bugs, generamos una alerta siempre que la tasa de errores desconocidos de cualquier herramienta supere un umbral fijo. Pero puede ser difícil determinar si los errores esperados representan un bug en el harness o un comportamiento esperado.

Por ejemplo, un timeout en una búsqueda con grep puede deberse a un problema de rendimiento de la herramienta, o puede que la base de código simplemente sea enorme y el modelo haya formulado una consulta ineficiente. Para abordar esto, tenemos alertas de detección de anomalías que se activan cuando los errores esperados superan significativamente la referencia. Calculamos referencias por herramienta y por modelo, porque distintos modelos pueden equivocarse en las llamadas a herramientas a ritmos diferentes.

También ejecutamos una Automatización semanal equipada con una habilidad que enseña al modelo a buscar en nuestros registros, detectar incidencias nuevas o que se han disparado recientemente, y crear o actualizar tickets en un backlog con una investigación. Nos apoyamos mucho en Cloud Agents para poner en marcha soluciones para muchas incidencias a la vez, e incluso podemos activarlos directamente desde Linear.

Este proceso forma parte de cómo estamos materializando una “fábrica de software” automatizada para nuestro harness de agentes. En el transcurso de un sprint centrado a principios de este año, redujimos los errores inesperados en llamadas a herramientas en un orden de magnitud.

Personalización del harness para diferentes modelos

Todas nuestras abstracciones de harness son agnósticas al modelo y pueden personalizarse ampliamente para cada modelo que admitimos. Por ejemplo, los modelos de OpenAI están entrenados para editar archivos con un formato basado en parches, mientras que los modelos de Anthropic están entrenados con reemplazo de cadenas. Cualquiera de los dos podría usar cualquiera de las dos herramientas, pero darle una que no le resulta familiar consume tokens de razonamiento adicionales y produce más errores. Por eso, en nuestro harness, configuramos cada modelo con el formato de herramienta con el que fue entrenado.

Esta personalización llega muy lejos e incluye prompts personalizados para distintos proveedores e incluso para diferentes versiones de modelos. Los modelos de OpenAI tienden a ser más literales y precisos al seguir instrucciones, mientras que Claude es un poco más intuitivo y más tolerante con instrucciones imprecisas.

Cuando obtenemos acceso anticipado a un modelo nuevo antes de su lanzamiento, partimos del harness del modelo existente más cercano y empezamos a iterar. Ejecutamos evaluaciones offline para encontrar dónde se confunde el modelo, hacemos que personas de nuestro equipo lo usen y detecten problemas, y ajustamos el harness en respuesta. Iteramos así hasta tener una combinación de modelo y harness que consideramos lista para lanzar.

Gran parte de este proceso de ajuste consiste en personalizar el harness según las fortalezas de un modelo nuevo, pero a veces encontramos peculiaridades reales del modelo que podemos mitigar con el harness. Por ejemplo, observamos que un modelo desarrolló lo que acabamos llamando ansiedad de contexto: a medida que se llenaba su ventana de contexto, empezaba a rechazar tareas, dando a entender que la tarea parecía demasiado grande. Pudimos reducir ese comportamiento mediante ajustes en el prompt.

Facilitar el cambio de modelo a mitad del chat

Diseñar el harness para que sea compatible con cambios de modelo a mitad de una conversación es especialmente complicado, porque los distintos modelos tienen comportamientos, prompts y formatos de herramientas diferentes.

Cuando un usuario cambia de modelo, Cursor cambia automáticamente al harness adecuado, con el conjunto personalizado de prompts y herramientas de ese modelo. Sin embargo, el modelo aún tiene que aplicar esas herramientas a un historial de conversación generado por un modelo distinto y que queda fuera de la distribución de datos con la que fue entrenado.

Para resolverlo, agregamos instrucciones personalizadas que le indican al modelo cuándo está tomando el relevo a mitad del chat de otro modelo. Estas instrucciones también lo orientan para que evite invocar herramientas que aparecen en el historial de la conversación, pero no forman parte de su propio conjunto de herramientas.

Evitar que los modelos invoquen herramientas que no forman parte de su conjunto de herramientasEvitar que los modelos invoquen herramientas que no forman parte de su conjunto de herramientas

Un segundo desafío es que las cachés son específicas de cada proveedor y de cada modelo, por lo que cambiar implica un fallo de caché y un primer turno más lento y más costoso. Mitigamos esto resumiendo la conversación en el momento del cambio, lo que le da al modelo un resumen claro que reduce la penalización de caché. Pero si el usuario está metido de lleno en una tarea compleja, el resumen puede perder detalles importantes; por eso, en general recomendamos usar un solo modelo durante toda la conversación, a menos que haya algún motivo para cambiar.

Otra forma de sortear las dificultades de cambiar de modelo a mitad de una conversación es usar un subagente, que empieza con una ventana de contexto nueva. Recientemente agregamos al harness la capacidad de que los usuarios pidan directamente que se ejecute un subagente con un modelo concreto.

El harness y el futuro del desarrollo de software

El futuro de la ingeniería de software asistida por IA será multiagente. En lugar de hacer pasar cada subtarea por un único agente, el sistema aprenderá a delegar entre agentes y subagentes especializados: uno para la planificación, otro para ediciones rápidas y un tercero para la depuración, cada uno enfocado en lo que mejor hace.

Lograr que eso funcione bien es, en esencia, un desafío del harness. El sistema necesita saber qué agente asignar, cómo enfocar la tarea según las fortalezas de ese agente y cómo integrar los resultados en un flujo de trabajo coherente. La capacidad de orquestar ese tipo de coordinación residirá en el harness, y no en un único agente. Esto significa que, aunque la ingeniería del harness siempre ha sido importante para el éxito de los agentes, de ahora en adelante no hará más que volverse aún más crítica.