Category: AI

  • Benchmarks de IA programando: qué mide de verdad ese 96%

    Benchmarks de IA programando: qué mide de verdad ese 96%

    "AI Is Already Better at Coding Than Most Software Developers."

    El titular circula en inglés y provoca siempre dos reacciones. El que lo comparte con un "ya está, se acabó". Y el que responde "pues a mí me inventó un import que no existe".

    Los dos discuten la conclusión sin mirar de dónde sale. Y sale de un sitio concreto: los benchmarks de IA programando. De uno solo, en realidad. SWE-bench Verified.

    La tesis, y no te va a gustar ninguna de sus dos mitades: el titular es literalmente cierto en el examen. Y el examen se rompió.

    No porque la IA sea mala escribiendo código —es buenísima—, sino porque mide una tarea que no se parece a tu trabajo: un bug de cinco líneas, en un repo que el modelo ya había visto, con los tests ya escritos por otro.

    En corto: OpenAI dejó de reportar ese benchmark por saturación, tests rotos y contaminación. El 91% de sus tareas son bugs de menos de una hora.


    De dónde sale el número: un leaderboard con todos empatados

    Snapshot del leaderboard de SWE-bench Verified a 15 de septiembre de 2026:

    Modelo Resolución
    Claude Opus 5 96%
    Claude Mythos 5 95,5%
    Claude Fable 5 95%

    Ahí tienes el 96% del titular. Y el primer problema no es el 96, sino la distancia entre filas: menos de un punto entre los tres punteros.

    Un examen en el que todos sacan la misma nota ha dejado de ser un examen. Es un sello.

    SWE-bench Verified es un benchmark de 500 tareas construidas a partir de issues reales de GitHub en repositorios de Python populares: el modelo recibe el repo y el enunciado del issue, y tiene que entregar un parche que pase una suite de tests ya escrita. Sobre el papel suena exactamente a tu trabajo. Por eso el titular funciona tan bien, y por eso conviene abrir la caja.

    Por qué OpenAI retiró SWE-bench Verified

    Esto no lo dice un escéptico de la IA con ganas de tráfico. Lo dice OpenAI, en un post titulado "Why SWE-bench Verified no longer measures frontier coding capabilities", y lo confirman Mia Glaese y Olivia Watkins, de su equipo de Frontier Evals, en esta entrevista.

    Tres razones, las tres con número.

    Saturación. El estado del arte pasó de 74,9% a 80,9% en seis meses. Cuando la aguja apenas se mueve ya no mides capacidad: mides techo.

    Tests rotos. Auditaron el subconjunto de problemas que los modelos fallan una y otra vez, un 27,6% del dataset: seis ingenieros revisaron 138 problemas a mano. Más del 59% tienen tests defectuosos que rechazan soluciones funcionalmente correctas: unos demasiado estrechos, otros demasiado amplios, que exigen features ni siquiera documentadas en el enunciado.

    Traducido: parte de lo que el leaderboard cuenta como fallo del modelo es un fallo del corrector.

    Contaminación. Esta es la peor. Dándoles solo el Task ID —sin enunciado y sin código—, todos los modelos frontera auditados (GPT-5.2, Claude Opus 4.5, Gemini 3 Flash) reproducen el parche correcto o el enunciado verbatim.

    Parte de la nota es memoria. No razonamiento. Y no hay forma de saber qué parte.

    La recomendación de OpenAI es reportar SWE-bench Pro en su lugar. Guárdate ese nombre.

    Qué mide de verdad el examen

    Epoch AI analizó tarea por tarea qué hay dentro de esas 500 muestras:

    Dimensión Dato
    Tareas triviales (menos de 15 min) 39%
    Tareas pequeñas (15 min – 1 h) 52%
    Tareas de 1 a 4 h 8%
    Tareas de más de 4 h 3 issues
    Parche medio, triviales 5 líneas
    Parche medio, de 15 min a 1 h 14 líneas
    Repositorios distintos 12
    Peso de Django casi el 50%
    Issues anteriores a 2020 50%

    El parche medio de ese trabajo va de cinco a catorce líneas. Y un análisis de Amazon sobre el mismo dataset, recogido por Epoch: el 78% de los cambios tocan funciones, no clases, con 1,87 funciones de media.

    La diversidad es peor que el tamaño. Doce repositorios, y los cinco mayores concentran más del 80% de las muestras. Casi la mitad es Django, de los proyectos Python más presentes en cualquier corpus de entrenamiento. La mitad de los issues son anteriores a 2020, en un dataset construido en octubre de 2023.

    Conclusión literal de Epoch: mide "arreglar issues pequeños y bien definidos" en "repos de Python open source familiares".

    Falta el detalle que más duele, y no sale en ninguna tabla: los tests ya vienen escritos. La parte difícil —decidir qué significa "correcto" en este sistema y para estos usuarios— venía hecha antes de que el modelo empezara.

    Los parches que pasan sin resolver nada

    SWE-Bench+ auditó los parches que el benchmark daba por buenos. El 32,67% son solution leakage: la solución venía escrita en el propio issue o en sus comentarios. Otro 31,08% queda como sospechoso: pasa con tests demasiado débiles para garantizar que el parche arregle algo.

    Al filtrar ambos casos, SWE-Agent con GPT-4 cae de 12,47% a 3,97%. Mismo modelo. La nota se divide por tres.

    SWE-bench Pro vs SWE-bench Verified: 27 puntos de diferencia

    SWE-bench Pro, de Scale AI, está diseñado para resistir la contaminación. Mismo modelo, dos exámenes, febrero de 2026: 80,8% en Verified y 53,4% en Pro. Veintisiete puntos por cambiar el papel del examen.

    Pasa lo mismo fuera de Python. Terminal-Bench 2.0 mide 16 categorías de tareas de terminal en Docker y en septiembre de 2026 sus líderes van altísimos: GPT-5.6 Sol 91,9%, Claude Mythos 5 88,0%, GPT-5.6 Terra 87,4%.

    Ahora coge TerminalWorld-Verified, con escenarios más parecidos a un entorno real: los modelos evaluados allí, con marcas de entre 57% y 82,7% en Terminal-Bench 2.0, caen a un rango de 49% a 62,5%.

    El número no describe al modelo. Describe al examen.

    Un diseño mejor existe: SWE-Lancer usa más de 1.400 encargos freelance de Upwork con un millón de dólares en pagos reales, y pregunta si el trabajo se habría cobrado. Cítalo por el diseño, no por el marcador: es de febrero de 2025.

    Qué mide ese 96% y qué mide tu lunes

    Nada de esto significa que la IA no programe bien. Programa muy bien, y cada mes mejor.

    Significa otra cosa, más incómoda: el número que usas para decidir no mide lo que crees. Mide velocidad en una tarea acotada, con el criterio de corrección regalado, sobre repos que el modelo ya conocía. Tu lunes no se parece a eso: repo privado que no estuvo en ningún corpus, requisitos a medio escribir y ninguna suite que te diga si lo que acabas de aceptar está bien.

    Hay un experimento que mide esa brecha. METR hizo un ensayo aleatorizado con 16 developers open source experimentados sobre 246 tareas reales en sus propios repositorios: más de 22.000 estrellas y más de un millón de líneas de media cada uno. Estimaron que irían un 24% más rápido con IA. Al terminar creían haber ido un 20% más rápido. La medición decía que habían sido un 19% más lentos.

    Y la advertencia sin la cual ese dato no se puede usar: el estudio es de julio de 2025, con Cursor Pro y Claude 3.5/3.7. Modelos viejos. No describe el rendimiento de las herramientas de hoy, y quien lo cite como si lo hiciera te está vendiendo algo.

    Sirve para una sola cosa, y es suficiente: nadie sabe si va más rápido hasta que lo mide. Ni tú ni yo. Es la conclusión a la que llegué desde otro camino cuando escribí que el cuello de botella ya no es escribir código, sino verificarlo.

    Cómo evaluar código generado por IA en tu repo: 4 pasos

    El leaderboard no te va a decir si un modelo te sirve. Eso lo mides tú, en tu repo. Cuatro pasos:

    1. Congela veinte tareas tuyas. Veinte PRs de tu proyecto ya cerrados, de dificultad variada. Ese es tu benchmark privado: no está en ningún corpus y se parece a tu trabajo por construcción. Cómo montarlo lo detallo en evals de código generado por IA.
    2. Escribe tú el criterio, y antes. Aquí está el fallo que copian los benchmarks: los tests los puso otro. Define el contrato —entradas, salidas, errores, invariantes— antes de pedir el código. Eso es revisión por contrato para código de agentes de IA, la diferencia entre revisar un diff y auditar una promesa. Si quieres el método completo, descarga el ebook gratuito.
    3. Mide tiempo, no sensación. Cronómetro desde que abres la tarea hasta que pasa code review, reescrituras incluidas. Ese es el único número que importa.
    4. Compara en tu harness, no en el ranking. El mismo modelo con distinto contexto, herramientas y reglas rinde de forma muy diferente. La variable que más mueve tu resultado casi nunca es el modelo.

    Es la mecánica del curso Construye con IA y el principio del libro Spec-Driven Development: sin criterio escrito antes no evalúas nada, ni a un modelo ni a un humano.

    Cifras verificadas a 17 de septiembre de 2026.

    La próxima vez que veas un titular con un porcentaje, haz una sola pregunta antes de compartirlo o de indignarte: ¿qué examen era?

    Casi siempre, la respuesta explica el titular entero.


    Preguntas frecuentes

    ¿Qué es exactamente SWE-bench Verified?

    Un benchmark de 500 tareas construidas a partir de issues reales de GitHub en repositorios de Python populares. Al modelo se le da el repo y la descripción del issue, y tiene que producir un parche que pase una suite de tests ya existente. Es el número detrás de casi todos los titulares sobre IA programando. Su problema no es que sea falso: su perfil de tarea —bugs pequeños, repos muy conocidos, criterio de corrección regalado— se parece poco al trabajo diario en una base de código privada.

    Si OpenAI dejó de usarlo, ¿por qué todo el mundo lo sigue reportando?

    SWE-bench Verified se sigue reportando por inercia y por comparabilidad: todos los modelos anteriores tienen una puntuación ahí, así que es la única cifra que permite poner dos años de lanzamientos en la misma tabla. OpenAI recomienda reportar SWE-bench Pro en su lugar, y lo razonable es leer las dos juntas. La diferencia entre ambas te dice más que cualquiera por separado.

    Entonces, ¿los benchmarks de IA programando no sirven para nada?

    Sirven para una pregunta más estrecha de la que se les hace: comparar modelos bajo condiciones idénticas y detectar regresiones entre versiones. No sirven para estimar cuánto vas a ganar tú en tu proyecto, porque su diseño elimina las dos partes más caras de tu trabajo: definir qué es correcto y verificar que el cambio no rompe nada más.

    ¿No es contradictorio decir que la IA programa muy bien y a la vez desconfiar del 96%?

    Decir que la IA programa muy bien y desconfiar del 96% son dos afirmaciones sobre cosas distintas. La primera habla de capacidad de generar código, que es real y muy alta. La segunda habla de qué mide un número concreto, y ese número resume un tipo de tarea muy particular. La IA genera código excelente a una velocidad que ningún humano iguala; lo que no hace es decidir qué debería hacer ese código ni garantizar que encaja en tu sistema. Ese trabajo es ahora la parte cara.


    Por Bezael Pérez — Developer senior con más de 15 años de experiencia y fundador de Dominicode.

  • NVIDIA compra Hugging Face: tu from_pretrained() es el riesgo

    NVIDIA compra Hugging Face: tu from_pretrained() es el riesgo

    El 3 de septiembre, la noticia de que NVIDIA compra Hugging Face por 12.930 millones de dólares llegó con la reacción por defecto ya puesta: "se acabó el open source".

    Hilos, capturas, indignación. Y casi nadie haciendo la única pregunta que de verdad importa: ¿qué pasa exactamente en tu build el día que algo de esto cambie?

    Si no sabes responderla en treinta segundos, la noticia no te ha creado ningún riesgo. Te lo ha enseñado.

    Así que este no es un post de indignación. Es un post de auditoría de dependencias. Y la tesis es incómoda: si la noticia te molestó, es porque tenías una dependencia de infraestructura que nunca decidiste tener.

    En corto: el 3 de septiembre de 2026 NVIDIA anunció un acuerdo definitivo para comprar Hugging Face por unos 12.930 millones de dólares. La operación no está cerrada y no cambia nada en el Hub a día de hoy. Lo que sí puedes cambiar hoy es tu código: fijar por commit SHA cada modelo del que depende tu build tarda menos que leerte los hilos.


    Cuánto paga NVIDIA por Hugging Face: 12.930 millones por 150 de ingresos

    Las cifras de plataforma salen del comunicado oficial de NVIDIA. La estructura del pago, la fecha de cierre y los ingresos anualizados no están ahí: vienen de la cobertura del día del anuncio. Lo señalo porque la diferencia importa cuando alguien repite el dato tres meses después.

    Dato Cifra
    Importe total ~12.930 millones de dólares
    Estructura ~11.900 M en efectivo + hasta 1.000 M en equity de retención
    Anuncio 3 de septiembre de 2026 — acuerdo definitivo, no cerrado
    Cierre previsto Primera mitad de 2027, sujeto a revisión regulatoria
    Ingresos anualizados de Hugging Face ~150 millones de dólares
    Múltiplo sobre ingresos ~86x
    Desarrolladores en la plataforma +18 millones
    Modelos / datasets / Spaces 3 M / 500.000 / ~1 M
    Clientes empresa +200.000
    Puesto en NVIDIA 2ª mayor adquisición, tras los 20.000 M por activos de Groq (diciembre de 2025)

    Divide. Son unas 86 veces los ingresos.

    Nadie paga 86x por el revenue. Se paga por la posición. Y la posición de Hugging Face es que se ha convertido en el sitio por defecto desde el que el mundo descarga pesos de modelos, igual que npm es el sitio por defecto desde el que descarga JavaScript. Esa es la compra.

    Un detalle que casi nadie está teniendo en cuenta: no está cerrada. Es un acuerdo definitivo, sí, pero el cierre está previsto para la primera mitad de 2027. Hay meses de ventana regulatoria por delante y bastante margen para que cambien condiciones. Cualquiera que hoy te cuente cómo va a quedar el Hub en 2028 se lo está inventando.


    ¿Obligará NVIDIA a usar sus GPUs? Lo que prometió Huang

    No, no va a obligarte. Al menos no según lo que ha firmado por escrito.

    Y hay que decirlo, porque el drama se está comiendo el matiz. La declaración de Jensen Huang fue explícita (traduzco):

    "Hugging Face seguirá siendo una plataforma abierta para todo el ecosistema de IA. Los desarrolladores elegirán los modelos que quieran, los frameworks que quieran, las clouds y los proveedores de inferencia que quieran y las plataformas de cómputo que quieran. El cómputo de NVIDIA no será un requisito para construir sobre Hugging Face ni para desplegar a través de Hugging Face."

    No es humo genérico. Es una promesa concreta y verificable: si mañana empieza a hacer falta hardware NVIDIA para desplegar desde el Hub, esa frase queda por escrito y con fecha.

    Y NVIDIA lleva tiempo predicando con el ejemplo ahí: más de 500 modelos y 250 datasets abiertos publicados en la propia plataforma.

    Clem Delangue, CEO de Hugging Face, justificó la venta en términos igual de concretos: la plataforma "necesita más cómputo, más soporte, más colaboración y más visibilidad".

    Ahora la parte que no cambia: una promesa corporativa no es una garantía arquitectónica.

    No porque Huang mienta. Porque las promesas tienen un plazo, un CEO y un contexto competitivo, y tu build no. Tu build se ejecuta cada noche durante los próximos cinco años y no lee notas de prensa.

    La pregunta correcta nunca fue "¿me fío de NVIDIA?". La pregunta correcta es "¿qué pasa exactamente en mi pipeline el día que algo de esto cambie?". Si no sabes responderla en treinta segundos, ahí está el trabajo.

    Es el caso hermano de Shopify comprando Tailwind y el embudo que eso destapa, con una diferencia que importa: allí se compraba un embudo. Aquí se compra el punto por el que pasa el tráfico de pesos del ecosistema entero.


    Qué hace tu from_pretrained() cuando no lo miras

    Cada from_pretrained() de tu código es una descarga de red contra un repositorio de terceros que puede cambiar sin avisarte.

    Esta es la parte que la mayoría de devs nunca ha auditado.

    Cada from_pretrained(), cada pipeline(), cada load_dataset() es una llamada de red a un CDN de terceros. No es una importación de librería que se resolvió en el pip install: es una descarga que ocurre en tiempo de build o, peor, en tiempo de ejecución, la primera vez que arranca el contenedor.

    Y hay un segundo problema, más silencioso, que es el que de verdad me preocupa: la mayoría de ese código apunta a main.

    Un repo de modelo en el Hub es un repo git. main se mueve. El mantenedor sube una cuantización distinta, corrige el tokenizador, reentrena. Los pesos que descargaste ayer y los que descargas hoy pueden no ser los mismos, tu build pasa en verde, tus tests pasan en verde, y nadie en tu equipo se entera de que el modelo que hay en producción cambió.

    No hace falta ninguna adquisición para que eso te explote. La adquisición solo añade un actor nuevo con capacidad de decidir sobre la infraestructura.

    Y aquí está el resumen honesto del riesgo: no creo que NVIDIA vaya a "cerrar" Hugging Face. Lo que sí tienes, desde ya, es un proveedor crítico, con un único punto de fallo, propiedad de la empresa que te vende las GPUs, del que no tienes inventario ni plan de salida. Eso, con cualquier otro proveedor, lo llamaríamos por su nombre y lo pondríamos en el registro de riesgos.


    Cómo auditar tus dependencias de modelos de IA en 10 minutos

    Auditar tus dependencias de modelos son cinco pasos: inventario, revisión fijada por SHA, build sin red, espejo de lo crítico y plan de salida a local.

    Los dos primeros son los diez minutos del título y puedes aplicarlos hoy mismo. Del tercero al quinto es trabajo de una tarde.

    1. Saca el inventario real

    No preguntes al equipo de qué modelos depende el producto. Pregúntaselo al repo:

    rg -n "from_pretrained\(|load_dataset\(|hf_hub_download\(|snapshot_download\(|pipeline\(" \
      -g "*.py" -g "*.ipynb" -g "*.toml" -g "Dockerfile*" .
    

    Cuenta con algún falso positivo: pipeline( también casa con los Pipeline de scikit-learn y con funciones tuyas que se llamen igual. Se descartan a ojo.

    Ese listado es tu superficie de exposición. Casi siempre es entre dos y cinco veces más grande de lo que la gente cree, porque incluye los modelos que nadie recuerda: el de embeddings del buscador interno, el reranker, el clasificador de spam que alguien metió en un script de 2024.

    Contrasta con lo que se ha descargado de verdad en la máquina:

    HUB="${HF_HUB_CACHE:-${HF_HOME:-$HOME/.cache/huggingface}/hub}"
    ls -1 "$HUB"
    du -sh "$HUB"/* | sort -h | tail -20
    

    Si en la caché aparecen artefactos que no salen en el grep, tienes descargas implícitas dentro de alguna librería. Esas son las peores, porque no las controlas desde tu código.

    2. Fija la revisión por commit SHA

    El cambio con mejor relación coste/beneficio:

    from transformers import AutoModel, AutoTokenizer
    
    MODEL = "org/modelo"
    REVISION = "3f8a1c9d2e7b5a4f6c0d8e1b2a3c4d5e6f708192"  # commit SHA exacto
    
    model = AutoModel.from_pretrained(MODEL, revision=REVISION)
    tokenizer = AutoTokenizer.from_pretrained(MODEL, revision=REVISION)
    

    Sacar el SHA actual es una línea:

    from huggingface_hub import HfApi
    
    print(HfApi().model_info("org/modelo").sha)
    

    Y el matiz importante, porque veo mucho revision="v1.2" por ahí: ni la rama ni el tag te sirven. La rama se mueve por definición. El tag parece estable, pero un tag en git es un puntero, y quien mantiene el repo puede reapuntarlo cuando quiera: en el Hub no hay nada que lo impida. El SHA es lo único que identifica contenido inmutable.

    No es una opinión mía sobre cómo debería ser: la documentación de transformers lo dice sin rodeos — revision acepta una rama, un tag o un commit id, y su valor por defecto sigue siendo main. El problema no es la API. El problema es el valor por defecto.

    Es el mismo razonamiento por el que llevas años usando un lockfile en JavaScript. Con los modelos, la industria entera se saltó ese paso.

    3. Que el build falle ruidosamente

    Una vez tienes caché y revisiones fijas, ciérrale la puerta de la red:

    export HF_HOME=/opt/hf-cache
    export HF_HUB_OFFLINE=1
    

    Con HF_HUB_OFFLINE=1, cualquier intento de salir al Hub durante el build lanza un error en vez de descargar en silencio. Está documentado como comportamiento oficial, no como efecto secundario: cualquier llamada al Hub levanta OfflineModeIsEnabled en lugar de ir a la red. Es un detector de dependencias ocultas: si tu pipeline se rompe al activarlo, acabas de encontrar una descarga en caliente que no sabías que tenías. Mejor que se rompa en tu CI un martes que en producción el día que el Hub tenga una caída.

    Con un límite que conviene saber: solo cubre lo que pasa por huggingface_hub. Los pesos que alguna librería se baje por su cuenta con una URL directa siguen saliendo a la red sin que te enteres.

    Esa manía de no dejar que la herramienta haga cosas por su cuenta es el eje del curso Construye con IA: de la idea al producto: ahí montamos un proyecto entero con Claude Code y el criterio es el mismo, si no puedes reconstruirlo desde cero no es tuyo.

    4. Espeja lo que es crítico

    Para los dos o tres modelos que sostienen tu producto, descarga y aloja tú los pesos:

    hf download org/modelo \
      --revision 3f8a1c9d2e7b5a4f6c0d8e1b2a3c4d5e6f708192 \
      --local-dir ./vendor/org__modelo
    

    Si lo has escrito como huggingface-cli por inercia, ojo: ese binario ya no funciona. Imprime un aviso y sale con error. Se renombró a hf. Es el mismo problema del post en miniatura — código tuyo que daba por hecho que la herramienta de otro no se mueve.

    De ahí a tu S3, tu registry o tu artifact store. Los pesos abiertos se pueden alojar; para eso son abiertos.

    Y presta atención al modelo que descubras que no puedes espejar, por licencia o por estar gated. Ese es, exactamente, el que más te ata. Anótalo, porque es el único riesgo del inventario que no puedes mitigar con ingeniería.

    5. La salida de verdad es no necesitarlo

    Todo lo anterior reduce la exposición. No la elimina.

    La única salida completa es que el modelo que te importa corra en tu máquina, en tu servidor o en la del cliente, sin depender de descargar nada de nadie al arrancar. Un GGUF en disco no tiene dueño corporativo.

    Si nunca has montado ese camino, empieza por cómo ejecutar modelos de IA en local, que es la parte mecánica, y sigue por los mejores modelos de IA local en 2026 para elegir con criterio en vez de por hype.

    Y si lo quieres ver montado entero y no en teoría, un agente SQL corriendo en local con Qwen3 y DuckDB no llama a ningún Hub cuando arranca.

    No te estoy diciendo que te lleves todo a local. Te estoy diciendo que tengas al menos un camino probado, aunque sea más lento y con menos capacidades, para el día en que lo necesites. Eso es un plan de salida.


    Trata a Hugging Face como a cualquier otro proveedor crítico

    Si mañana tu proveedor de pagos lo comprase un competidor directo tuyo, no abrirías Twitter. Abrirías el contrato, revisarías tu exposición y prepararías un plan B. Sin ruido.

    Haz exactamente eso. La operación no está cerrada, tienes hasta bien entrado 2027 y la tarea real cabe en una tarde.

    Y si quieres el marco completo para decidir qué se queda dentro de tu infraestructura y qué puede salir a una API de terceros sin que el producto dependa de ello, ahí están las 4 capas de una arquitectura de IA local.

    Ejecuta el grep del punto 1 en tu repo principal ahora mismo. Cuenta cuántos artefactos externos salen y cuántos de ellos tienen la revisión fijada. Ese número es tu respuesta a la noticia, y vale mucho más que cualquier hilo de opinión.

    Si quieres ver este tipo de decisiones con el código delante, en Dominicode Labs desmonto arquitecturas reales sin diapositivas, y en el canal de YouTube voy publicando los montajes de IA local según los pruebo.


    Preguntas frecuentes

    ¿NVIDIA va a cerrar Hugging Face o a obligar a usar sus GPUs?

    Nada indica que ese sea el plan. La declaración de Jensen Huang dice literalmente lo contrario: la plataforma seguirá siendo abierta y el cómputo de NVIDIA no será un requisito ni para construir sobre el Hub ni para desplegar a través de él. Lo que esa promesa no te da es una garantía técnica sobre tu build dentro de tres años. Por eso la respuesta profesional no es decidir si te fías, sino saber qué pasa en tu pipeline si algo cambia.

    ¿Cuándo se cierra la operación?

    El cierre está previsto para la primera mitad de 2027. A día de hoy lo que existe es un acuerdo definitivo confirmado el 3 de septiembre de 2026, no una adquisición consumada, y queda por delante la revisión regulatoria de una operación de 12.930 millones de dólares entre dos piezas centrales del mercado de IA. Tienes margen de sobra para hacer los deberes sin prisa.

    ¿Por qué 12.930 millones por una empresa que factura 150 millones?

    Porque no se paga por la facturación. Son unas 86 veces los ingresos anualizados, un múltiplo que no se justifica con ninguna proyección razonable de revenue. Se paga por la posición: 18 millones de desarrolladores, 3 millones de modelos, 500.000 datasets y 200.000 clientes empresa convierten a Hugging Face en el punto de distribución por defecto de los pesos de modelos abiertos. Comprar eso es comprar el sitio por el que pasa el ecosistema entero.

    Fijar el commit SHA me obliga a actualizar a mano. ¿No es peor?

    Es el mismo trabajo que ya haces con las dependencias de tu package.json o tu requirements.txt, y por las mismas razones. Actualizar a mano significa que la actualización es una decisión con un commit, una revisión y un responsable, en lugar de un cambio invisible que se cuela en el siguiente despliegue. Un modelo pesa gigabytes y afecta directamente a la salida de tu producto: es la última dependencia del stack que deberías dejar flotando en main.

    Tengo el modelo cacheado en el contenedor. ¿No basta con eso?

    Solo si esa caché forma parte de una imagen que se construye con revisiones fijadas y que no vuelve a salir a la red. Si la caché se llena en el primer arranque descargando lo que haya en ese momento, no tienes reproducibilidad: tienes suerte. La prueba dura un minuto, pero hazla en frío: borra la caché, pon HF_HUB_OFFLINE=1 y reconstruye sin capas cacheadas, con docker build --no-cache. Si la haces con la caché caliente y pasa en verde, lo único que has demostrado es que ya lo tenías descargado. Si falla, acabas de localizar la descarga en caliente que tenías sin saberlo.


    Por Bezael Pérez — Developer senior con más de 15 años de experiencia y fundador de Dominicode.

  • Harness multiagente vs un solo agente: qué midió Uncle Bob

    Harness multiagente vs un solo agente: qué midió Uncle Bob

    Robert C. Martin —Uncle Bob, el de Clean Code— pasó meses construyendo lo que parecía la cosa correcta: un harness de orquestación de agentes IA con roles especializados, sesiones aisladas y handoffs que no se contaminaban entre sí. Gates deterministas. Métricas de complejidad. Todo.

    Mientras lo montaba, el suelo se movía debajo.

    Cuando por fin lo tuvo funcionando hizo lo que casi nadie hace: medirlo contra la alternativa tonta. Le dio la misma tarea a un solo agente, con un par de directrices y cero orquestación. Se fue cuarenta minutos. Al volver estaba hecho. Y mejor que lo que le entregaba el enjambre.

    Su conclusión pública cabe en seis palabras: "OK. It's time to rethink this."

    En corto: Uncle Bob midió su harness multiagente de seis roles contra un solo agente en la misma tarea: cuarenta minutos frente a tres o cuatro horas, y mejor código. La orquestación con roles fijos y handoffs por contrato era un andamio para modelos débiles, y los modelos dejaron de serlo. Hoy un agente único bien dirigido suele ganar en tiempo, en calidad y en tokens. Lo que sigue valiendo del harness no es la orquestación: es la verificación determinista —tests, tipos, cobertura, complejidad— contra la que mides su salida.


    Qué era SwarmForge, el harness que Uncle Bob construyó y tiró

    Un harness de orquestación multiagente es una capa de software que reparte una tarea entre varios agentes con roles predefinidos, controla el orden en que se pasan el trabajo y bloquea el avance hasta que cada etapa cumple unos criterios medibles.

    SwarmForge, el harness de Uncle Bob, se autodescribe en su README como "a simple tool for coordinating several AI agents". Es bastante más que eso.

    Los roles están separados de verdad. El six-pack del repo los nombra así: especificación, implementación, limpieza, arquitectura, hardening y QA. Cada agente vive en su propia sesión de tmux y su git worktree bajo .worktrees/ para no pisarse, y los handoffs los mueve un daemon en Babashka.

    Las técnicas que aplica cada rol no están en el repo: las cuenta él. Gherkin para la especificación, TDD para implementar, revisiones de duplicación y de CRAP para la limpieza, mutation testing para el hardening.

    Cada decisión ahí responde a un fallo real que conoce cualquiera que haya montado esto. Si llegas frío, la pieza por pieza está en la anatomía de un agent harness, y el recorrido completo en construir un agente de IA desde cero.

    Y aun así perdió contra un agente solo. Con el mismo modelo corriendo dentro y fuera del harness.

    El experimento es de septiembre de 2026 y el modelo era Grok. Uncle Bob no precisa la versión, y eso limita la reproducibilidad: esto es la medición de un practicante con oficio, no un paper.

    El experimento que lo tiró abajo

    El experimento fue este: misma tarea, mismo modelo, dos caminos — el harness de seis roles y un agente solo con dos directrices. Y Uncle Bob relajó las restricciones a favor del harness, a propósito.

    En vez de su umbral habitual de CRAP por debajo de 6, pidió mantenerlo por debajo de 12. CRAP —Change Risk Anti-Patterns— combina complejidad ciclomática con cobertura de tests: cuanto más ramifica un método y menos cubierto está, más alto puntúa y más caro es tocarlo.

    Algo de mutation testing y tests unitarios. Nada de Gherkin. Dos directrices y a correr.

    El agente hizo algo que nadie le pidió: partió el código en módulos y dejó todo el CRAP por debajo de 6 igualmente. Por debajo del umbral relajado y del estricto.

    Cuarenta minutos. Lo que al harness completo le costaba tres o cuatro horas, y salía aceptable-pero-no-bueno.

    Métrica Harness SwarmForge Un solo agente
    Agentes implicados 6 (six-pack del repo) 1
    Tiempo en la misma tarea tres o cuatro horas cuarenta minutos
    Calidad entregada aceptable, no buena mejor, con un par de quejas menores
    Umbral de CRAP pedido por debajo de 6 (su estándar) por debajo de 12 (relajado a propósito)
    CRAP entregado — por debajo de 6, y modularizado sin pedírselo
    Consumo de tokens la referencia cayó "by a huge factor" al abandonar el harness

    Todas las cifras salen de lo que cuenta Uncle Bob en sus hilos; el recuento de agentes, del README del repo.

    Días después llegó la segunda medición, la que duele en la factura: "Since I stopped using my harness, my token consumption has fallen by a huge factor. That harness was massively inefficient."

    Cada handoff es un resumen que uno escribe, otro lee y un tercero vuelve a expandir. Y aquí conviene no confundir dos facturas distintas. Cuando medí el overhead de los frameworks de IA en tokens el resultado fue que no hay prompts ocultos inyectados: ese impuesto es un mito. El coste de un harness es el opuesto, explícito y a la vista: serializar el estado en cada salto para que el siguiente agente pueda leerlo. Nadie te lo esconde. Lo pagas igual.

    Lo que se ha roto no es el multiagente

    La idea que se cae no es "usar varios agentes". Es otra, más específica: tratar al agente como un componente de un diagrama de software. Una caja con interfaz fija, un rol asignado y un contrato de handoff.

    Tenía sentido hace un año, cuando los modelos se perdían en tareas largas: el rol estrecho y el gate duro eran una prótesis para una debilidad real.

    Los modelos dejaron de ser débiles. El andamio se convirtió en camisa de fuerza.

    El detalle que lo resume es la modularización. Nadie se la pidió. Un pipeline con un rol architect habría producido esa decisión como etapa obligatoria, en su turno, con su handoff. El agente solo la tomó porque veía el problema entero de una vez.

    Ahí está el fondo: un harness de roles fijos parte el contexto por la línea que dibujaste hace tres meses, no por donde el problema se parte hoy.

    Harness, agente único y subagentes bajo demanda

    No son tres sabores del mismo plato. Se diferencian en una cosa: quién decide el reparto del trabajo.

    Harness orquestado Agente único dirigido Subagentes bajo demanda
    Quién reparte Tú, antes de empezar Nadie: no hay reparto El modelo, en ejecución
    Qué resuelve Determinismo, trazabilidad por etapa, aislamiento fuerte El criterio del modelo sobre el problema completo Aislar contexto sucio sin fijar roles
    Qué cuesta Meses de construcción y tokens en cada handoff Una sesión larga y directrices bien escritas Latencia y contexto duplicado
    Límite o riesgo Bloquea decisiones transversales que el modelo tomaría solo; envejece con cada modelo nuevo Se cae si la tarea no cabe en una sesión o cruza permisos Si abusas, vuelves a un pipeline implícito
    Cuándo elegirlo Aprobación humana intermedia, aislamiento por datos o permisos, paralelismo real Casi todo el trabajo normal de feature o refactor Investigación previa a escribir código

    Fíjate en la fila de límites: ninguna columna está limpia. La pregunta no es "multiagente sí o no", sino cuánta estructura te puedes permitir antes de que la estructura decida por el modelo.

    Lo que defendí hace un mes y qué parte ha caducado

    El 24 de agosto publiqué Arquitectura de subagentes IA: por qué falla el mega-prompt: un agente mío con 3.000 palabras de system prompt y 28 herramientas que colapsaba a la cuarta tarea compleja.

    Esa mitad sigue en pie. Un prompt con cincuenta reglas y treinta herramientas reparte la atención del modelo entre instrucciones que casi nunca aplican. Una ventana más grande no lo arregla: solo retrasa el momento en que se nota.

    La otra mitad ha caducado. Allí proponía un pipeline fijo —investigador, implementador, revisor— comunicándose por artefactos en disco, con el orden decidido por mí antes de empezar. Eso es orquestación rígida: lo mismo que acaba de tirar Uncle Bob, en pequeño.

    La distinción que reconcilia las dos posiciones es quién manda.

    Subagentes bajo demanda: el modelo decide delegar cuando le conviene, el subagente vive lo que dura su pregunta y muere con su contexto sucio dentro. Nadie le asignó un rol permanente. Sigue siendo buena idea, porque aislar contexto no ha dejado de importar.

    Orquestación rígida: los roles existen antes que la tarea, el orden vive en un fichero de configuración y el trabajo pasa por todas las etapas aunque tres no aporten nada. Esto es lo que los modelos han dejado obsoleto.

    Escribí aquello hace un mes. Un mes. Esa es la velocidad a la que caduca hoy una decisión de arquitectura sobre agentes, y el mejor argumento para construir lo menos posible alrededor del modelo.

    Cuándo el harness sigue ganando

    Tirar la orquestación entera sería el error simétrico. Cuatro casos donde aún compensa:

    La tarea no cabe en una sesión. Migrar cuatrocientos ficheros no es un problema de criterio, es de volumen: repartir gana, aunque reparta trabajo y no roles.

    Hay una aprobación humana en medio. Si alguien firma antes del siguiente paso, necesitas una parada explícita con un artefacto revisable. Un agente continuo no te la da.

    El aislamiento es por permisos o por datos. El agente que lee el ticket del cliente no debería tener credenciales de producción. Eso no es diseño: es requisito, y sobrevive a cualquier modelo mejor.

    Paralelismo real sobre repos distintos. Tres repositorios independientes, tres agentes, cero coordinación. Funciona precisamente porque no hay handoffs.

    Y un límite más, del propio experimento: verificar de más deja cicatrices. Uncle Bob es honesto con el mutation testing —encontró bugs y omisiones reales, pero el algoritmo empuja al agente a hacer cosas tontas con tal de matar mutantes, y eso queda escrito en el código. Ningún gate es gratis.

    Y lo obvio: esto es la medición de una persona, con sus tareas y su modelo. No es un benchmark controlado. Si tu dominio no se parece al suyo, lo que te vale es el método, no la conclusión.

    Quédate la verificación, tira la orquestación

    Del harness se tira la orquestación y se conserva la verificación: la primera decide quién hace qué y caduca con cada modelo nuevo; la segunda define qué tiene que cumplir el resultado y no caduca.

    Separa las dos cosas que el harness mezclaba.

    La orquestación dice quién hace qué y en qué orden. Es la parte que envejece cada vez que sale un modelo mejor.

    La verificación dice qué tiene que cumplir el resultado para ser aceptable: tests que pasan, tipos que compilan, lint sin warnings, cobertura mínima, complejidad bajo umbral. No depende de quién escriba el código ni de cuántos agentes participen. Por eso no caduca.

    Tres cosas para esta semana:

    1. Escribe el contrato antes que el prompt. Entradas, salidas, errores, invariantes y umbrales. Si no puedes decir qué hace fallar la entrega, no tienes un gate: tienes una opinión. Lo tienes en revisión por contrato para código de agentes y entero en el ebook gratuito de 30 páginas.
    2. Convierte cada gate en un comando que devuelva 0 o 1. Si el criterio vive dentro del prompt de un rol, no es determinista: es una sugerencia. Un verify no necesita ser más que esto, y el agente lo ejecuta igual que tú:
    #!/usr/bin/env bash
    set -e                            # el primer fallo corta y devuelve != 0
    bun test                          # los tests pasan
    bunx tsc --noEmit                 # los tipos compilan
    bunx eslint . --max-warnings 0    # cero warnings
    bunx vitest run --coverage        # cobertura sobre el umbral del config
    
    1. Mide tu pipeline contra un agente solo. Misma tarea, dos caminos, cronómetro y factura de tokens. La comparación que casi nadie hace y la única que decide.

    El paso previo es tener la especificación escrita antes de que el agente toque nada: lo que trabajamos en Construye con IA y la tesis del libro de Spec-Driven Development. Un agente sin criterio escrito no va más rápido: va más rápido equivocándose.

    Si llevas meses montando tu orquestador, esta es la conclusión que importa: no tires el trabajo, tira la mitad correcta. Los roles y los handoffs ya no te compran nada. Los gates sí.


    Preguntas frecuentes

    ¿Qué es un harness de orquestación multiagente?

    Una capa de software que reparte una tarea entre varios agentes con roles predefinidos, controla el orden de los handoffs y bloquea el avance hasta que cada etapa cumple criterios medibles. SwarmForge lo implementa con una sesión de tmux y un git worktree por agente. Su valor original: compensar las limitaciones del modelo con estructura externa.

    ¿Significa esto que los subagentes ya no sirven?

    No. Lo que ha dejado de compensar son los roles fijos decididos antes de conocer la tarea. Delegar bajo demanda sigue siendo útil: cuando un subagente explora el repo o lee logs enormes, su contexto sucio muere con él sin contaminar la sesión principal. La diferencia está en quién decide: si lo decides tú en un fichero de configuración, es orquestación rígida; si lo decide el modelo en ejecución, es aislamiento de contexto.

    ¿Qué es CRAP y por qué se usa como gate?

    CRAP —Change Risk Anti-Patterns— combina complejidad ciclomática y cobertura en un número: un método muy ramificado y poco cubierto puntúa alto, y eso indica que cambiarlo es caro. Funciona como gate porque lo calcula una herramienta, no una opinión. Uncle Bob trabaja con umbral por debajo de 6, y aquí lo relajó a 12 a propósito.

    ¿Por qué un harness multiagente consume tantos más tokens?

    Porque cada handoff obliga a serializar el estado: uno resume lo que ha hecho y el siguiente reconstruye el contexto que el anterior ya tenía cargado. Multiplícalo por seis roles y por cada iteración. Uncle Bob lo comprobó al dejar de usar el suyo: su consumo cayó de forma drástica y calificó el harness de "massively inefficient".


    ¿Merece la pena construir mi propio harness multiagente hoy?

    Solo si tu problema es de los que no arregla un modelo mejor: volumen que no cabe en una sesión, una aprobación humana en medio, aislamiento por permisos o por datos, o paralelismo real sobre repos separados. Si tu motivo es "que el agente no se despiste", ya no lo necesitas: escribe los gates como comandos verificables y dale la tarea entera. Construir el harness te va a costar meses y va a envejecer con el siguiente modelo; los gates no.

    Por Bezael Pérez — Developer senior con más de 15 años de experiencia y fundador de Dominicode.

  • Qué es Jev de TypeSafe AI: primitivas, calibración y límites

    Qué es Jev de TypeSafe AI: primitivas, calibración y límites

    Tienes un clasificador de tickets en producción. Entra un ticket, lo mandas a un modelo de cientos de miles de millones de parámetros y esperas casi dos segundos a que razone en voz alta para acabar escupiendo una palabra: billing.

    Pagas la entrada, pagas la salida —cinco veces más cara— y te llevas una etiqueta. Y no sabes si el modelo estaba seguro o echando una moneda al aire: el JSON sale válido en los dos casos.

    Ese es el agujero que quiere tapar Jev de TypeSafe AI, que salió el 15 de septiembre de 2026. Hace cuatro días.

    Lo firma Diogo Almeida, co-autor de InstructGPT y co-inventor del RLHF en OpenAI, con 40 millones de dólares liderados por DCVC. No es un wrapper: es una arquitectura nueva que renuncia a generar texto a propósito.

    En corto: Jev es el primer modelo de TypeSafe AI y no genera texto. Recibe un estado no estructurado y devuelve decisiones tipadas —binarias, elecciones o puntuaciones— con probabilidades calibradas, en unos 250 ms medidos de extremo a extremo —unos 100 ms de inferencia más el viaje de red— y a $0,042 por millón de tokens de entrada con la salida gratis. Sirve para clasificar, enrutar, extraer y puntuar. No sirve para escribir código, contar ni hacer cuentas.


    ¿Qué es Jev, el System One Model de TypeSafe AI?

    Jev —que el fabricante escribe así, no "JEV"— es un "System One Model": un modelo que convierte estado no estructurado en decisiones tipadas con probabilidades calibradas, sin generar ni una línea de texto libre. Es el primer modelo de TypeSafe AI y se lanzó el 15 de septiembre de 2026.

    Lo aclaro porque el acrónimo en mayúsculas ya estaba cogido: JEV es, en literatura médica, el virus de la encefalitis japonesa. A partir de aquí lo escribo como lo escriben ellos.

    El nombre viene de Kahneman. El Sistema Dos delibera y escribe: eso es un LLM. El Sistema Uno responde por reflejo, y su salida no es prosa sino un juicio. Jev es lo segundo, con la parte cara amputada.

    El truco es arquitectónico: evalúa todas las preguntas a la vez contra el mismo estado, en paralelo y de forma independiente, en lugar de construir una respuesta token a token. Por eso añadir preguntas apenas cambia el tiempo de respuesta. Y por eso no puede explicarte por qué decidió lo que decidió: no está entrenado para generar texto, así que no hay prosa donde escribirlo.

    Está entrenado exclusivamente con datos sintéticos, y con un método distinto: RLCD, Reinforcement Learning for Calibrated Decisions.

    ¿En qué se diferencia RLCD de RLHF?

    RLHF optimiza que la respuesta le guste a un evaluador humano. De ahí que los LLM suenen igual de seguros inventando que acertando: la seguridad puntúa bien. RLCD optimiza otra cosa — que las probabilidades sean epistémicamente honestas.

    Calibrado significa esto: de todo lo que el modelo responde con un 90% de probabilidad, debería acertar alrededor del 90% de las veces. No el 99% ni el 60%. El número significa lo que dice.

    Si has peleado con logprobs sabes por qué importa: esos números existen, pero no están calibrados. Un 0.95 no te promete 19 aciertos de cada 20. Jev dice que sí — y es la única promesa del lanzamiento que puedes verificar tú en una tarde: agrupa tus respuestas por tramo de probabilidad y mira qué porcentaje acierta cada tramo. Si cuadra, sobre eso puedes escribir un if.

    Es el mismo fondo que expliqué en por qué la IA se inventa cosas: el problema no es que el modelo se equivoque, es que se equivoca con el mismo tono con el que acierta.

    Las tres primitivas: noul, choice y score

    La API es un endpoint REST, POST https://api.typesafe.ai/v1/systemone, con bearer token. Le mandas tres cosas: model (por ejemplo jev-latest), state —string, objeto JSON o array de texto— y questions.

    Las preguntas referencian campos del estado con notación de backticks: `ticket`, `mensaje`. Y solo hay tres tipos:

    • noul — binaria sí/no. Devuelve una probabilidad de 0 a 1.
    • choice — elige entre opciones que tú defines. Devuelve la opción, la distribución completa de probabilidad y un confidence de 0 a 1.
    • score — sitúa algo en una escala ordenada. Devuelve la media ponderada, la distribución y un confidence.

    Con el SDK de JavaScript, @typesafe-ai/sdk, se ve así:

    import { choice, noul, score, TypeSafeClient } from '@typesafe-ai/sdk'
    
    const client = new TypeSafeClient()
    
    const { answers } = await client.systemOne({
      state: { ticket },
      questions: {
        category: choice('What kind of ticket?', { bug: '...', billing: '...' }),
        severity: score('How severe?', ['Low', 'Medium', 'High'])
      }
    })
    

    Hay SDK de Python equivalente (from typesafe_sdk import Choice, Noul, TypeSafeClient, con client.system_one(...)) e integración con el Vercel AI SDK vía experimental_evaluate() y typeSafeAi.evaluationModel('jev-latest'), o con el string de gateway 'typesafe-ai/jev'. — esto último está documentado del lado de Vercel, no en la doc de TypeSafe.

    Fíjate en lo que no hay en ese código: ningún prompt pidiendo "responde solo con JSON". Ninguna función de reparación. Ningún reintento. El tipo no es una súplica al modelo, es la superficie de salida del modelo.

    Si vienes de montar esto a mano con schemas y validación —el camino que recorro en diseñar schemas Zod para LLM— el contraste es incómodo: la mitad de ese andamiaje deja de tener función. La otra mitad no — los tipos siguen siendo tuyos en cuanto la respuesta entra en tu dominio, y eso lo trabajo entero en el curso de Zod para TypeScript.

    Caso práctico: triaje de tickets con umbrales

    El patrón que mejor rinde es el fan-out especulativo: preguntar de golpe todo lo que no dependa de nada. Sale más barato que la cadena secuencial equivalente.

    const { answers } = await client.systemOne({
      model: 'jev-latest',
      state: { ticket },
      questions: {
        category: choice('What kind of ticket is `ticket`?', {
          bug: 'Something in the product is broken',
          billing: 'Charges, invoices or refunds',
          feature: 'A request for something that does not exist yet',
          other: 'Anything else'
        }),
        severity: score('How severe is `ticket`?', ['Low', 'Medium', 'High', 'Critical']),
        impact: score('How many users does `ticket` affect?', ['One', 'Some', 'Many']),
        isAngry: noul('Is the author of `ticket` frustrated?')
      }
    })
    

    Una request, cuatro decisiones, ~250 ms medidos desde mi red. Y ahora la parte que decide si esto es ingeniería o un juguete: qué haces con el confidence.

    const { category, severity, impact, isAngry } = answers
    
    // La doc sugiere estos umbrales; ajústalos con tus propios datos.
    if (category.confidence < 0.5) {
      return escalarAHumano(ticket, { motivo: 'clasificación poco concentrada' })
    }
    
    // `score` es la media ponderada sobre los índices de nivel: 0..3 con cuatro
    // niveles, 0..2 con tres. Normalízalo antes de mezclar escalas distintas.
    // `noul` es directamente la probabilidad de que la respuesta sea sí.
    const prioridad =
      0.5 * (severity.score / 3) +
      0.3 * (impact.score / 2) +
      0.2 * (isAngry.noul > 0.7 ? 1 : 0)
    
    // `category.choice` es la opción ganadora; `category.probabilities`, la distribución completa.
    enrutar(category.choice, prioridad)
    

    Cada respuesta llega tipada bajo el id que le pusiste en la request: un choice
    trae choice, probabilities y confidence; un score trae score,
    probabilities, confidence y un legend con la descripción de cada nivel; un
    noul trae solo noul, la probabilidad de sí. La distribución suma 1, pero son
    floats: si la compruebas en un test, hazlo con tolerancia
    (Math.abs(suma - 1) < 1e-6), nunca con igualdad exacta. Y mira usage, que
    llega junto a answers y model con los tokens de entrada y salida: el coste
    real por decisión lo registras, no lo estimas.

    El confidence mide cuán concentrada está la distribución. Por debajo de 0,5, la doc recomienda no actuar sin revisión humana. Y para acciones destructivas —borrar, reembolsar, banear— pide confirmación aunque estés por encima de 0,9.

    Ese segundo umbral es el que todo el mundo se salta. Un número alto no es un permiso. Es la misma lógica de degradación controlada que aplico con los circuit breakers en agentes de IA: decidir de antemano qué pasa cuando el sistema duda.

    El scoring compuesto tiene una ventaja que no es de rendimiento: es auditable. Ese 0.5 / 0.3 / 0.2 lo discutes en una PR. Un prompt que dice "decide la prioridad del ticket" no lo discutes: lo reescribes y rezas.

    ¿Jev o un LLM con structured output?

    Esta es la comparación honesta, porque un LLM con salida estructurada ya funciona. Lo que Jev aporta no es capacidad nueva: es velocidad, coste y probabilidades que significan algo.

    Jev (System One) LLM con structured output
    Qué devuelve decisión tipada + distribución + confidence JSON validado contra tu schema
    Texto libre, código, prosa no, por diseño sí
    Latencia típica ~250 ms end-to-end medidos (~100 ms de inferencia) segundos
    Coste de entrada $0,042 / millón de tokens $0,20 – $10 / millón
    Coste de salida gratis ~5x el de entrada
    Probabilidades calibradas (RLCD) logprobs sin calibrar, o nada
    Aritmética, fechas, conteo no fiable mejor, aunque también frágil
    Límite / riesgo puede devolver un valor de tipo válido y completamente equivocado, con confidence alta; exige diseñar a mano el espacio de respuestas alucina contenido dentro de un JSON perfectamente válido; coste y latencia escalan mal con el volumen

    Haz la cuenta en vez de tragarte el titular. La home dice "193,6x más rápido, 444,6x más barato": con $0,042 de entrada frente a $0,20–$10, el ahorro solo en entrada va de unas 5x a unas 238x, y el 444x solo cuadra si además sumas la salida —gratis aquí, unas cinco veces la entrada allí— con un mix de tokens que no publican.

    Con la velocidad pasa lo mismo. Coge mi propia apertura, dos segundos, y lo que mido de verdad, 258 ms: eso son 8x, no 193x. El 193,6x sale de workflows con muchas preguntas independientes, donde el LLM las resuelve en cadena y Jev las resuelve de una tacada. Es una comparación de arquitectura de workflow, no de modelo contra modelo.

    Y ese “~100 ms” tampoco es lo que vas a medir tú. Lo he cronometrado contra la API real: 12 llamadas abriendo conexión nueva cada vez dan una mediana de 628 ms; reutilizando la conexión TLS, 258 ms, y nunca por debajo de 213. Solo el handshake TCP + TLS son 342 ms. Los 100 ms son tiempo de inferencia —ciertos, si tu código corre al lado de sus GPUs—; el resto es el viaje hasta San Francisco. Si te llevas una sola cosa de aquí que sea esta: reutiliza la conexión, son 2,4x gratis.

    El propio blog de TypeSafe rebaja eso a 40x–200x para inteligencia equivalente en tareas System One, y admite que esas cifras "están en el extremo alto de las ganancias del mundo real". Midieron contra la media de GPT-6 Astra y Fable 5.1, con precios de LLM sacados de OpenRouter —lo que "casi con certeza" introduce sesgo— y desde la costa oeste de EEUU.

    El número que yo me creo viene de fuera: Vercel midió el clasificador de seguridad de su modo automático de fx y le salió entre 5x y 18x más rápido en p95 con Jev que con gpt-5.6-luna, su opción anterior, y además con más acierto. Lo contaron su propio CEO y el ingeniero que hizo la prueba, y lo recogió TechCrunch. Tampoco es un árbitro imparcial —Jev viene integrado en su AI SDK, como has visto arriba—, pero al menos no vende el modelo, la carga de trabajo es real y el rango que publica es mucho más modesto que el de la home.

    ¿Cuándo NO conviene usar Jev?

    Para esto, el hilo de Hacker News sobre el lanzamiento —más de 1.900 puntos y cerca de 500 comentarios— es más útil que la documentación oficial.

    No puede escribir nada. Ni código, ni explicaciones, ni un resumen. Lo resumió un comentarista: "esto es probablemente súper útil para clasificación, routing y scoring, pero no se parece en nada a los modelos de generación de código que todos usamos hoy". El título original del post prometía un "nuevo modelo frontier" y hubo que editarlo.

    No sabe contar ni hacer cuentas. Aritmética, fechas y comparaciones numéricas se quedan en tu código. No delegues un "¿han pasado más de 30 días?" a Jev; eso es un if.

    El "no puede alucinar" es marketing. Lo que garantiza por diseño es que la salida es de un tipo válido: cero errores de tipo. Pero la objeción más votada del hilo lo parte por la mitad: "claro que no puede emitir un tipo inválido, pero sí puede emitir un valor válido completamente equivocado. También puedes forzar salida estructurada en un LLM". Un valor equivocado con confianza alta sigue siendo una alucinación cuando llega a tu base de datos.

    Es frágil con lenguaje difuso. El ejemplo del hilo: ante "quiero que tu agente me llame mañana a las cinco", la pregunta "¿el usuario quiere hablar con un agente humano?" responde que sí y se traga entero el matiz temporal. Esa segunda dimensión tienes que haberla previsto tú. Traducido: el espacio de respuestas lo diseñas tú, a mano y con cuidado. Jev no descubre categorías, puntúa las que le das. Si tu taxonomía está mal, la salida está mal — y con confidence alta.

    Piensa en inglés. La ficha del modelo lo dice sin adornos: el inglés es su idioma principal de entrenamiento y donde hoy la precisión es mejor. Otros idiomas funcionan, con menos puntería. Por eso las preguntas de los ejemplos de arriba están en inglés aunque el ticket entre en castellano: el state déjalo en el idioma que llegue, pero las preguntas, las opciones y los niveles escríbelos en inglés. Si los pones en español, mídelo antes de fiarte.

    El state sucio le baja la puntería. El detalle irrelevante actúa de distractor. Manda los campos que importan, no el objeto entero que te escupe el ORM.

    Y no trata el estado como hostil. Esto lo admite su propia página de limitaciones: una instrucción inyectada, un encuadre engañoso o un texto que argumenta a favor de su propia clasificación pueden mover la respuesta. Si lo que clasificas lo escribe un usuario —o peor, otro modelo—, el estado es una superficie de ataque, no un dato. Describe los dos lados de cada pregunta y prueba con entradas envenenadas antes de darle poder sobre nada.

    Solo lee texto. Nada de imágenes, audio ni vídeo: lo que no sea texto hay que preprocesarlo antes de mandarlo como estado. Y hay un techo de 255 opciones en una elección de una sola etapa — por encima toca hacer scoring en dos pasadas.

    Y ojo con las demos. La de Doom impresiona hasta que ves que al modelo le pasaban el estado del juego ya estructurado —coordenadas, ángulos—, no píxeles. La de Home Assistant sí convenció a bastante gente, pero tuvieron que saltar a un modelo de Anthropic para partir peticiones con varias intenciones.

    Donde sí le veo el hueco es en lo que nadie automatiza porque sale caro: deduplicar registros, casar entidades, revisar miles de filas una por una. Ahí velocidad y confianza calibrada sí cambian el juego.

    Qué cambia esto si construyes agentes

    Un agente es un bucle que decide muchas veces y actúa alguna. La mayoría de esas decisiones —¿consulta o queja?, ¿hace falta buscar?, ¿esto es urgente?, ¿me paro?— no necesitan prosa: necesitan un juicio rápido y honesto que hoy paga un modelo enorme escribiendo un párrafo para devolver una palabra.

    A 250 ms y coste casi nulo deja de tener sentido racionar decisiones. Esa es la tesis: Jev no compite con tu LLM, compite con los if que escribiste porque llamar al LLM salía caro.

    Con dos condiciones que no negocio. La primera, que midas: la viabilidad se calcula en coste por tarea resuelta, no por token, y sin evals sobre lo que genera la IA estás comparando titulares. La segunda, que las decisiones caras pasen por un contrato explícito — el método que explico en el ebook gratuito Revisión por Contrato.

    Hoy mismo puedes hacer esto: coge la llamada a un LLM más tonta y repetida que tengas en producción —la que solo devuelve una etiqueta—, reescríbela como un choice con su confidence y ponle un umbral de 0,5 con salida a revisión humana. Es una tarde. Si el número no mejora, lo sabrás con datos y no con una home.

    Y si quieres montar el agente entero con esta cabeza, el recorrido de idea a producto está en el curso Construye con IA.

    Si quieres ir más allá de este resumen, lo he escrito entero en un libro: Jev y las decisiones tipadas con IA. Las tres primitivas con la forma real de sus respuestas, cómo comprobar la calibración con tus propios casos, cinco patrones de producción y un capítulo entero sobre cómo te va a fallar. Cada cifra lleva su origen declarado.

    Preguntas frecuentes

    ¿Jev sustituye a mi LLM?

    No, y no lo pretende. Jev no escribe código, ni resúmenes, ni respuestas a un usuario. Sustituye a las llamadas de tu pipeline que solo devuelven una etiqueta, un booleano o una nota del 1 al 5. El LLM se queda para generar.

    ¿Qué significa exactamente que las probabilidades estén calibradas?

    Que el número es verificable: de todo lo que Jev responde con un 90% de probabilidad, acierta cerca del 90% de las veces. Es lo que optimiza RLCD, frente a RLHF, que optimiza que la respuesta le guste a un humano y produce modelos que suenan igual de seguros acertando que inventando.

    ¿Puede alucinar Jev?

    Sí, en el sentido que importa. Lo que no puede es devolver un tipo inválido: eso está garantizado por diseño, no por estadística. Pero puede devolver una opción perfectamente válida y equivocada, y hacerlo con confianza alta. Trata el confidence como una señal de enrutado, nunca como una garantía de verdad.

    ¿Cuánto cuesta y cuánto tarda?

    $0,042 por millón de tokens de entrada y salida gratis. Su documentación habla de unos 100 ms de inferencia; medido desde mi red, el end-to-end fue de 258 ms de mediana y nunca bajó de 213 ms. Los límites publicados: 250.000 tokens por segundo, 1.200 requests por minuto y dos techos de contexto que conviene no confundir — 64.000 tokens por request contando estado y preguntas juntas, y 32.000 para el estado más la pregunta más larga.

    ¿Qué hago si tengo más de 255 opciones?

    Ese es el techo de una elección de una sola etapa. Por encima toca scoring en dos pasadas: reduces el espacio con un score sobre categorías gruesas y luego haces el choice fino dentro del grupo ganador. Sigue saliendo más barato que encadenar llamadas a un LLM, pero deja de ser gratis en complejidad.


    Por Bezael Pérez — Developer senior con más de 15 años de experiencia y fundador de Dominicode.

  • El futuro de los agentic systems: coste por tarea, no benchmarks

    El futuro de los agentic systems: coste por tarea, no benchmarks

    La iteración 14 me costó más que las trece anteriores juntas.

    El agente no hizo nada raro. Leyó el repo, lanzó los tests, leyó los logs. En la iteración 14 una tool devolvió 5.000 tokens de logs que no necesitaba nadie, el contexto cruzó un umbral de facturación y la misma tarea pasó a costar el doble. No un 5 % más. El doble.

    Ahí está el techo real de los agentic systems, y no tiene nada que ver con la inteligencia del modelo. El futuro de los agentic systems no lo decide lo listo que sea el siguiente checkpoint: lo decide que hoy no puedo predecir lo que va a costar una tarea ni demostrar que el resultado es correcto sin leérmelo entero.

    Esta es la tesis, y la voy a defender con números que ya he publicado aquí:

    El techo de los agentic systems no es la capacidad del modelo. Es que nadie puede pagarlos de forma predecible ni auditar lo que producen. El futuro de los agentic systems se decide en el coste por tarea y en el harness de verificación (verification harness), no en el próximo benchmark.

    Los benchmarks van a seguir subiendo. Es lo único que tengo claro del próximo año. Lo que no va a subir solo es tu capacidad de presupuestar una tarea y de firmar que salió bien.

    El precio por token ya no te dice lo que vas a pagar

    El precio por token dejó de predecir la factura porque el mismo modelo puede mantener la tarifa y aun así consumir más tokens por tarea, cruzar un umbral de precio escalonado o abrir más subagentes.

    Durante dos años elegimos modelo mirando una tabla de dos columnas: dólares por millón de input, dólares por millón de output. Era una multiplicación. Funcionaba.

    Ya no.

    Mira Gemini 3.8 Flash. Mismo precio por token que 3.7 Flash: 0,75 $ de input y 3,75 $ de output por millón. Cero subida. Google no tocó la tarifa.

    Pero el modelo gasta un 30 % más de tokens de salida por tarea: 48.000 de media. Razona más, escribe más y encadena más turnos. Y cada turno extra reenvía el contexto acumulado, que se factura como entrada: de los 0,58 $ que cuesta hoy una tarea, solo unos 0,18 $ son salida. El resto es contexto reenviado. Resultado medido con el reasoning en high: alrededor de un 40 % más caro con la tarifa intacta.

    El precio se quedó igual y la factura subió un 40 %. Las dos cosas son ciertas a la vez.

    Y hay fecha de caducidad: esos 0,75 $ y 3,75 $ son precio introductorio hasta el 31 de diciembre de 2026. El 1 de enero de 2027 pasan a 1,50 $ y 7,50 $ — exactamente el doble. Si has calculado tus márgenes con la tarifa de 2026, tu coste por tarea ya tiene una subida programada en el calendario.

    Ese es el primer mecanismo: la verbosidad. El segundo es peor, porque no es gradual. Es un escalón.

    En la API de GPT-6 Astra la tarifa es 10 $ de input y 50 $ de output por millón. Pero cualquier request que supere los 272.000 tokens de input dobla el precio de input y de caché y multiplica el output por 1,5. Y no sobre el exceso: sobre la request entera.

    Los números del ejemplo que desglosé allí:

    Request Input Output Coste
    Por debajo del umbral 270.000 8.000 3,10 $
    Por encima del umbral 275.000 8.000 6,10 $

    Un 1,9 % más de tokens de input. Un 97 % más de factura.

    Y esto es un problema de arquitectura, no de hoja de cálculo: tú no decides cuándo se cruza el umbral. Lo decide el agente, en la iteración 14, cuando una tool devuelve más logs de la cuenta. Tu prompt inicial ocupaba 12.000 tokens. El contexto acumulado en el bucle es el que cruza la línea.

    El tercer mecanismo es todavía más silencioso: la propensión a delegar en subagentes cambia con el modelo. Claude Opus 5 delega más fácilmente que sus antecesores. Mismo código, mismo prompt, misma tarea — y de repente tienes varios subagentes con su propio contexto donde antes había uno.

    Tres formas de que tu factura suba sin que toques una línea de código: el modelo se vuelve más hablador, el contexto cruza un escalón, o el sistema decide abrir más ramas.

    Ninguna aparece en la tabla de precios. Por eso la métrica que sobrevive es el coste por tarea (cost per task): lo que cuesta resolver una unidad de trabajo completa, de principio a fin, incluyendo reintentos, tools, subagentes y las veces que el agente se equivoca y vuelve a empezar.

    Es la única cifra que puedes meter en un presupuesto y defender delante de alguien que no sabe qué es un token.

    Dónde no está el coste por tarea de un agente

    Voy a decir algo impopular: el framework que elegiste no es tu problema de coste.

    Circula la leyenda de que los frameworks de agentes te meten 1.500 tokens ocultos en cada llamada. Lo medí de verdad: el bloque de herramientas pasó de 213 bytes a 294 o 299 bytes según el framework, y la petición entera de 393 a 556 bytes. Unos 40 tokens de diferencia.

    Cuarenta. Con un coste por tarea de 0,58 $, eso es ruido estadístico.

    Mientras tanto, el bucle de ese mismo agente acumula 270.000 tokens de contexto y roza un escalón que dobla la factura entera.

    Quien se pasa la tarde optimizando el overhead del framework está limpiando el mostrador mientras se le inunda el sótano. El coste de un agentic system vive en el bucle: en cuántas iteraciones hace, en cuánto contexto arrastra, en qué devuelven las tools y en cuántas veces reintenta porque nadie le dijo que ya había terminado.

    Generar es barato. Auditar no ha bajado de precio

    El coste de generar código cae cada trimestre. El de verificarlo no ha bajado ni un céntimo, porque lo sigue pagando una persona leyendo diffs. Esta es la segunda mitad de la tesis, y la que casi nadie quiere mirar.

    La asimetría es brutal y va a peor: la unidad de trabajo del agente ya es la tarea; la unidad de trabajo del revisor sigue siendo la línea. Un agente cierra en cuatro minutos un refactor que tardas cuarenta en revisar. Multiplica eso por cinco agentes en paralelo y la cola de revisión se convierte en el proceso más lento del equipo.

    Lo que pasa entonces no es que la gente revise más rápido. Es que deja de revisar. Aprueba por cansancio. Y el sistema pierde la única propiedad que lo hacía utilizable: que alguien podía responder de lo que salía.

    La salida no es revisar más. Es dejar de revisar a ojo.

    Un agente en producción necesita que la verificación sea una función que devuelve verdadero o falso, no una opinión. Eso significa dos cosas concretas: evals deterministas (deterministic evals) que corren en CI y tumban el build, y un contrato explícito de lo que la tarea debía cumplir, escrito antes de que el agente empezara.

    Sin contrato previo no hay verificación posible: solo hay un humano decidiendo a posteriori, y con sesgo de confirmación, si le gusta lo que ve. Ese método lo escribí entero en el ebook gratuito Revisión por Contrato: el contrato se escribe antes, no después.

    Es el mismo motivo por el que llevo dos años empezando cada feature por la especificación y no por el código, y la mecánica completa está en el libro de Spec-Driven Development.

    El harness pesa más que el modelo

    El harness —el código que envuelve al modelo— cambia la puntuación de un mismo modelo más de lo que la cambia sustituir el modelo entero. Si solo te llevas un dato de este post, que sea este.

    ARC-AGI-3 le dio a GPT-6 Astra un 99,9 %. Ese número recorrió internet como prueba de que habíamos llegado a la AGI.

    Salió del adapter propio de OpenAI: un harness con estado, optimizado para el benchmark. Con el harness estándar —el que sí compara entre proveedores— la nota fue 62,7 %, justo en el techo del rango de aproximadamente 17 % a 63 % que la propia organización del benchmark da para las llamadas stateless: las que hace tu código.

    Mismo modelo. Mismos pesos. La misma semana.

    De 62,7 a 99,9 con los mismos pesos y la misma semana, solo cambiando lo que hay alrededor. Y por debajo del 62,7 está todo lo que consigue un arnés peor montado que el de ARC.

    Traducción para tu backlog: la diferencia entre un prototipo que funciona a medias y un sistema que cierra tareas de verdad no está en cambiar de modelo. Está en el harness: el bucle de acciones, el formato de las observaciones, la gestión del contexto, el estado entre pasos, las condiciones de parada.

    Eso es código tuyo. No es un proveedor. No lo compras con una API key.

    Y por eso el próximo salto de calidad en agentic systems no va a venir de un checkpoint nuevo, sino de cosas mucho más aburridas: compactar el contexto antes de cruzar un umbral, truncar lo que devuelve una tool, cachear lo que no cambia y montar un circuit breaker que mate el bucle cuando el coste acumulado o el número de iteraciones se disparan.

    Un agente sin condición de parada no es autónomo. Es una fuga.

    El futuro de los agentic systems: cuatro predicciones para 2027

    Cuatro predicciones sobre el futuro de los agentic systems, ordenadas por lo seguro que estoy de cada una: (1) el coste por tarea se convierte en un requisito no funcional, (2) la unidad de facturación se desplaza del token a la tarea, (3) el harness se estandariza y el modelo se vuelve intercambiable, y (4) la métrica pública pasa a ser el porcentaje de tareas cerradas sin intervención humana.

    Aquí dejo de describir el presente y me mojo.

    El coste por tarea se convierte en un requisito no funcional. Igual que hoy escribes "p95 por debajo de 200 ms" en una spec, vas a escribir "coste por tarea por debajo de 0,40 $". Con su alerta, su panel y su presupuesto que corta. Un agente que resuelve la tarea pero cuesta cuatro veces lo presupuestado es un incidente, no un éxito. (La que más firme: 18 meses.)

    La unidad de facturación se desplaza del token a la tarea. Ya está pasando en las herramientas de coding agéntico: nadie te vende millones de tokens, te vende sesiones y límites de uso. Quien pueda ofrecer "tarea cerrada o no cobro" tendrá una ventaja de precio que nadie podrá igualar sin verificación automática. (Probable en 2027; ya ha empezado.)

    El harness se estandariza y el modelo se vuelve intercambiable. Los proveedores ya no compiten solo por la nota del leaderboard: compiten por ser el sustrato del bucle —herramientas, estado, ejecución, adapters propios—. El 99,9 % de Astra salió exactamente de ahí. Cuando el harness sea portable de verdad, cambiar de modelo será una línea de configuración, y tu ventaja competitiva estará entera en tus evals y en tus contratos de verificación, que son los únicos activos que no te da el proveedor. (La más arriesgada. Si dentro de dos años cambiar de modelo sigue costando una semana de trabajo, me habré equivocado.)

    La métrica pública será el porcentaje de tareas cerradas sin intervención humana. No la puntuación en un benchmark de puzzles: tareas reales, cerradas de principio a fin, verificadas por una función. Y al lado, el coste medio de cada una. Llamémoslo por su nombre: tasa de cierre autónomo (autonomous closure rate) y coste por tarea cerrada. Ese par de números va a decidir presupuestos, y hoy casi nadie lo instrumenta. (La más lenta: tres años, y solo si alguien con cuota de mercado publica la cifra primero.)

    Capacidad sin economía ni verificación es una demo. Y las demos no entran en producción.

    Qué hacer hoy: instrumenta el coste por tarea

    Una sola cosa, y esta semana.

    No hace falta un stack de observabilidad. Hace falta que cada ejecución de tu agente escriba una línea con seis campos:

    1. tarea — un identificador de la unidad de trabajo, no del prompt
    2. tokens_input — acumulados en todo el bucle, no los de la primera llamada
    3. tokens_output — acumulados, incluidos los de los subagentes
    4. iteraciones — cuántas vueltas dio el bucle antes de parar
    5. coste — calculado con la tarifa vigente, tramo premium incluido si cruzó el umbral
    6. eval_ok — verdadero o falso, salido de una función, no de una impresión

    Diez minutos de código.

    Cuando tengas cincuenta filas vas a ver dos cosas que ahora no ves: que el 80 % del coste se lo come un puñado de tareas, y que hay tareas que el agente "termina" sin cerrar de verdad. Esas dos cifras valen más que cualquier comparativa de modelos que leas este mes.

    Si quieres montar esto entero —del bucle a la verificación— sin aprenderlo a base de facturas, lo enseño paso a paso en Construye con IA. Y si prefieres ver cómo lo medimos sobre proyectos reales, con los harness y las evals que usamos en producción, eso vive en Dominicode Labs.

    El próximo modelo será mejor que este. Da igual. Gana quien sepa lo que cuesta cada tarea y pueda demostrar que salió bien.

    Preguntas frecuentes

    ¿Qué es un agentic system?

    Un agentic system es un programa que le da a un modelo de lenguaje un bucle, herramientas y estado: el modelo decide la siguiente acción, el sistema la ejecuta, le devuelve el resultado y el ciclo se repite hasta que se cumple una condición de parada. La diferencia con un chatbot no está en el modelo, está en el bucle y en quién decide cuándo parar. Por eso su coste no se mide por llamada, sino por tarea completa.

    ¿Qué es el coste por tarea y por qué sustituye al precio por token?

    El coste por tarea es lo que pagas por resolver una unidad de trabajo completa: iteraciones del bucle, tools, subagentes y reintentos incluidos. El precio por token ya no predice la factura porque un modelo puede mantener la tarifa y consumir un 30 % más de tokens por tarea, como pasó con Gemini 3.8 Flash: mismo precio y un 40 % más caro por tarea resuelta.

    ¿Qué tengo que registrar en cada ejecución para medir el coste por tarea?

    Seis campos por ejecución: tarea, tokens de input y de output acumulados en todo el bucle, iteraciones, coste calculado y si pasó la verificación. Multiplica por la tarifa y divide entre las tareas cerradas con éxito, no entre las ejecuciones totales. Si cuentas los intentos fallidos como tareas, tu coste real queda infravalorado y el presupuesto se te romperá en producción.

    ¿Por qué el coste de verificar no baja aunque baje el de generar?

    Porque el coste de generar cae cada trimestre y el de verificar no: lo sigue pagando una persona leyendo diffs. Un agente cierra en minutos lo que tardas media hora en revisar, y con varios agentes en paralelo la cola de revisión pasa a ser el proceso más lento del equipo. La salida no es revisar más rápido, sino convertir la revisión en evals deterministas y contratos que devuelvan verdadero o falso.

    ¿Qué es un harness y por qué pesa más que el modelo?

    El harness es la capa de código que envuelve al modelo: el bucle de acciones, el formato de las observaciones, la gestión del contexto, el estado entre pasos y las condiciones de parada. Pesa más que el modelo porque el mismo GPT-6 Astra puntúa 62,7 % en ARC-AGI-3 con el harness estándar y 99,9 % con el adapter propio de OpenAI, la misma semana y con los mismos pesos. Esa diferencia no está en los pesos: está en código que escribes tú.

    ¿Cambiar a un modelo más barato reduce la factura?

    No necesariamente. La factura depende de cuántos tokens consume el modelo por tarea, de si el contexto cruza umbrales de precio escalonados y de cuánto tiende a delegar en subagentes. Un cambio de modelo altera las tres cosas a la vez sin que toques una línea de código, así que la única forma de saberlo es medir el coste por tarea antes y después.

    ¿Qué métricas debería vigilar en un agentic system en producción?

    Cuatro: coste medio por tarea cerrada, porcentaje de tareas cerradas sin intervención humana, iteraciones por tarea y tokens de contexto máximos dentro del bucle. Las dos primeras dicen si el sistema es viable económicamente; las dos últimas avisan antes de que una ejecución cruce un umbral de precio o se quede dando vueltas.


    Por Bezael Pérez — Developer senior con más de 15 años de experiencia y fundador de Dominicode.

  • ¿GPT-6 Astra es AGI? Los benchmarks no se ponen de acuerdo

    ¿GPT-6 Astra es AGI? Los benchmarks no se ponen de acuerdo

    La semana del 3 de septiembre de 2026, OpenAI lanzó GPT-6 Astra y su presidente dijo que era la llegada de la AGI. Esa misma semana, dos organizaciones independientes de evaluación publicaron su medición del modelo. Los mismos días, el mismo modelo.

    Epoch AI le puso la nota más alta que ha registrado nunca en su índice.

    Artificial Analysis le puso exactamente la misma nota que a su antecesor.

    No hablo de capturas de X. Son dos organizaciones serias que agregan decenas de benchmarks —Epoch combina más de cincuenta— para medir en buena parte las mismas capacidades. Y llegaron a veredictos opuestos.

    Cuarenta y ocho horas después del lanzamiento, una de las dos reescribió su índice.

    Aquí es donde el resto de artículos te explica cuál de los dos tiene razón. Este no.

    La pregunta "¿es AGI?" no tiene respuesta, y no por motivos filosóficos. Por motivos de instrumentación: el sistema con el que medimos modelos se rompió justo cuando más falta hacía. Y tiene una consecuencia práctica para ti: si los benchmarks públicos no se ponen de acuerdo sobre el modelo más potente del mundo, tú no puedes elegir modelo leyendo leaderboards.

    Un apunte de vocabulario, porque de él depende todo lo demás: AGI (Artificial General Intelligence) es un sistema capaz de aprender y resolver cualquier tarea intelectual que resuelva una persona, en dominios que no ha visto antes y sin reentrenarse para cada uno. No existe un test acordado que diga cuándo se cruza esa línea. Por eso la discusión se libra a base de benchmarks, y por eso cuando los benchmarks se contradicen no queda árbitro.

    Brockman dijo AGI. Y en el mismo briefing se desdijo

    GPT-6 Astra llegó en preview limitada el 3 de septiembre y en disponibilidad general el 4. Entrenado sobre más de 100.000 GPUs en Stargate, Texas: el mayor training run de OpenAI.

    Greg Brockman, presidente de OpenAI, cerró el briefing con un "Welcome to the AGI era" y dijo que personalmente cree que han llegado.

    En ese mismo briefing admitió que "no hay un momento AGI claramente definido" y que la transición está siendo "más gradual de lo esperado".

    Léelo otra vez. Hemos llegado a un sitio que no sabemos definir, por un camino que no hemos notado recorrer.

    Eso no es un anuncio técnico. Es posicionamiento.

    Qué puntuó GPT-6 Astra en ARC-AGI-3: hay dos números, y solo uno compara

    ARC-AGI-3 mide generalización: puzzles interactivos que el modelo no ha visto y debe resolver explorando.

    GPT-6 Astra puntuó 99,9 % en ARC-AGI-3. Ese es el número que circuló, y es real. Lo que casi nadie contó es que hay dos números.

    Dependen del harness, la capa que conecta el modelo con el entorno (bucle de acciones, formato de observaciones, gestión del contexto).

    Configuración Puntuación ARC-AGI-3 Coste de la evaluación
    Harness estándar de ARC, razonamiento máximo (comparable entre proveedores) 62,7 % 26.098 $
    Provider Adapter de OpenAI, razonamiento alto 99,9 % 18.817 $

    El adapter propio fue además 3,66 veces más rápido con un 49 % menos de tokens.

    Si leíste el rango del ~17–63 % que la propia organización del benchmark da para llamadas stateless, encaja: 62,7 % es el techo de ese rango, el que se alcanza con el harness estándar y el razonamiento al máximo.

    No es trampa: optimizar tu arnés es legítimo. Pero no es comparable. Es cronometrar una vuelta con un neumático que solo tiene un equipo.

    En el harness estándar, el que sí compara, la foto es esta: GPT-5.6 Sol 7,78 %, Claude Opus 5 30,16 %, Astra 62,7 %.

    El salto es brutal. Y es de 30 a 62. No a 99,9.

    Hay un detalle mejor que el titular: con el razonamiento al máximo, el coste bajó de 49.791 $ a 26.098 $ mientras la puntuación subía de 35,2 % a 62,7 %. Pensar más salió más barato, y ARC explica por qué: con razonamiento máximo resuelve las partidas con menos acciones, y menos acciones son menos coste total. Esa curva coste/acierto es la que deberías mirar, y el coste por tarea con precios de API lo desgloso en el post hermano.

    Astra necesitó menos acciones que la mediana humana en el 96 % de los niveles. Eso es eficiencia de juego, no de coste: los 12,78 $ que cita ARC son lo que cobró un jugador humano por partida intentada, antes de bonus, y los 26.098 $ son el coste total de la evaluación entera del modelo. Unidades distintas. ARC no publica cuántas partidas jugó Astra, así que el coste por partida del modelo no se puede calcular.

    Iguala al humano moviendo fichas. Lo que cuesta cada ficha, hoy, nadie lo ha publicado.

    Y ahora lo que debería haber sido el titular. ARC Prize dice, textualmente, que "saturating the benchmark would not represent 'proof of achieving AGI'" y que "we are not claiming that it is AGI". Avisan de que ARC-AGI-3 tiene un alcance muy acotado y no representa la apertura del mundo real.

    Cuando los autores del benchmark que lleva AGI en el nombre te avisan de que aprobarlo no prueba AGI, el problema no está en el modelo.

    Epoch AI vs Artificial Analysis: dos índices, el mismo modelo, veredictos opuestos

    Epoch AI y Artificial Analysis agregan decenas de benchmarks para medir en buena parte las mismas capacidades, y publicaron veredictos opuestos sobre GPT-6 Astra en la misma semana: 169 puntos y primer puesto histórico en el índice ECI de Epoch, 61 puntos y empate con GPT-5.6 Sol en el Intelligence Index de Artificial Analysis.

    Epoch AI (índice ECI) Artificial Analysis (Intelligence Index)
    Nota de Astra 169 puntos 61 puntos
    Posición 1º, récord absoluto Empatado con GPT-5.6 Sol
    Contexto +6 sobre el techo anterior (163) Por detrás de Claude Fable 5.1 (66)
    Detalle Récords en matemáticas, aprendizaje continuo y puzzles En coding, Fable 5.1 lidera con 70; Astra 67

    Mismo modelo, misma semana. Uno ve el récord absoluto de su índice; el otro, un empate técnico.

    El 5 de septiembre, Artificial Analysis reescribió su índice. Versión 4.2. Añadió dos benchmarks (AA-Briefcase y GDP.pdf), eliminó GPQA-Diamond por saturado —los modelos ya lo resuelven— y subió los datos de test privados al 40 % del peso.

    Con las reglas nuevas, Astra saca 4 puntos a Sol y queda segundo. Sigue detrás de Fable 5.1.

    El motivo declarado: "the top of the leaderboard moved so fast that an interim update was necessary".

    No acuso a nadie de manipular nada: recalibrar tu instrumento cuando deja de discriminar es lo honesto.

    Pero mira lo que significa: la regla de medir cambió en 48 horas porque el objeto medido se salía del rango.

    Si tu termómetro necesita recalibrarse cada dos días, lo que tienes no es una medición. Es una foto con fecha de caducidad.

    Donde GPT-6 Astra no brilla (y casualmente es tu trabajo)

    GPT-6 Astra saca 57,7 % en Terminal Bench 4.0, el benchmark que mide tareas reales de línea de comandos.

    Un modelo del que se dice que inaugura la era AGI falla más de 4 de cada 10 tareas de ese benchmark de terminal. Tú vives en la terminal.

    En GDPval-AA v2, que mide trabajo profesional real, pierde unos 80 puntos Elo. Retrocede también en soporte bancario, SciCode y razonamiento de contexto largo.

    OpenAI, por su parte, publicó cifras espectaculares: FrontierMath Tier 4 v2 97,6 %, GPQA Diamond 96,0 %, ARC-AGI-2 95,0 %, ExploitBench 100 %, SRE-Bench 88,0 %, DeepSWE v1.1 74,1 %, OSWorld 2.0 72,6 %.

    Son cifras de OpenAI recogidas por the-decoder, no mediciones independientes: probablemente ciertas y seguro que favorables. El mismo juego que conté en la comparativa entre Opus 5, GPT-5.6 y Kimi K3.

    El patrón que explica las dos listas: verificación

    François Chollet, creador de ARC, no dice que esto sea AGI. Ha adelantado su previsión porque el progreso va más rápido de lo que esperaba: "Sooner, because progress is happening faster than I expected".

    Su definición sigue siendo la más útil: la inteligencia no es la habilidad en sí, es la eficiencia con la que un sistema adquiere habilidades nuevas ante entornos desconocidos.

    Gary Marcus fue más directo: "Success on ARC-AGI is great and impressive, but not—despite the name of the task—proof of AGI". Su apuesta: fallará en tareas abiertas y solo rendirá bien en dominios verificables. Concede que es "pretty impressive" y llama "extraordinarily vindicating" que OpenAI adopte world models simbólicos, aunque marca su análisis como "VERY tentative".

    Junta ahora las dos listas.

    Donde Astra arrasa —matemáticas, exploits, puzzles ARC— existe una función de verificación barata y automática. El resultado es correcto o no, y lo decide una máquina en milisegundos.

    Donde flojea —GDPval, contexto largo, soporte— no existe esa función. Alguien tiene que leer la salida y opinar.

    No es casualidad. Es exactamente lo que predice la tesis de Marcus.

    Y es exactamente tu problema cuando un agente escribe código en tu repo.

    ¿Dónde te funciona bien? En lo que tiene tests. ¿Dónde te la cuela? En lo que solo puedes juzgar leyendo.

    Más capaz y más opaco

    Astra usa recurrent depth, una técnica que oculta parte de su razonamiento, y es el primer modelo que OpenAI clasifica en umbral "crítico" de ciberseguridad en su preparedness framework. Marcus avisa de que es menos monitorizable que sus predecesores.

    Más capaz y más opaco a la vez. Otra razón para no delegar el juicio.

    El leaderboard ya no te sirve. Monta tu eval

    Si dos organizaciones independientes de evaluación no se ponen de acuerdo sobre el modelo más potente del planeta, y uno reescribe su regla de medir en 48 horas, el leaderboard público ha dejado de ser herramienta de decisión. Es periodismo. Interesante, pero no accionable.

    Lo que decide es un eval propio. Y se monta en una tarde:

    1. Coge de 20 a 30 casos reales de tu dominio. De tu backlog cerrado, no inventados.
    2. Define para cada uno una verificación automática: un test que pasa, un schema que valida, un diff que coincide. Si no puedes verificarlo sin leerlo, no entra en el set.
    3. Ejecútalo contra dos o tres modelos candidatos.
    4. Mide acierto, coste y latencia. Los tres. Un modelo que acierta un 4 % más y cuesta el triple no es mejor: es más caro.
    5. Congela el set y reejecútalo cada vez que cambies de modelo o de versión.

    Ese conjunto te dirá lo que ningún índice te dirá nunca: si Astra es mejor para ti. Cómo construirlo paso a paso lo desarrollo en evals deterministas para agentes de IA.

    Si lo que te falta no es el eval sino el criterio para revisar lo que genera el agente, empieza por el ebook gratuito Revisión por Contrato: 30 páginas para convertir el "esto parece bien" en algo verificable, la versión aplicada de revisar código de agentes por contrato. El flujo entero, de la idea al producto con agentes, es lo que montamos en Construye con IA, y esas mediciones se discuten a diario en Dominicode Labs.

    La pregunta no es si GPT-6 Astra es AGI.

    Es si es mejor que lo que ya usas, en tu repo, para tu caso.

    Esa la respondes tú, o no la responde nadie.

    Preguntas frecuentes

    ¿GPT-6 Astra es AGI?

    No con los datos actuales, y ese es el problema. OpenAI lo insinúa, ARC Prize lo niega explícitamente ("we are not claiming that it is AGI"), Chollet adelanta plazos sin firmar la afirmación y Marcus la rechaza. Sin definición operativa ni test que zanje la discusión, la pregunta no es respondible.

    ¿Por qué ARC-AGI-3 da 99,9 % y 62,7 % para el mismo modelo?

    Porque son dos harness distintos. El 62,7 % sale del arnés estándar de ARC, igual para todos los proveedores. El 99,9 % sale del Provider Adapter, el arnés propio de OpenAI. Los dos son reales; solo uno es comparable con Claude Opus 5 o GPT-5.6 Sol.

    ¿Debería cambiar mi stack a GPT-6 Astra?

    Depende de si tu trabajo se parece más a matemáticas competitivas o a tareas de terminal. Astra marca récords donde hay verificación automática barata, pero saca 57,7 % en Terminal Bench 4.0 y retrocede en contexto largo. En coding, Artificial Analysis lo sitúa detrás de Claude Fable 5.1. Pruébalo con tus casos antes de migrar.

    ¿Por qué Artificial Analysis reescribió su índice dos días después del lanzamiento?

    Porque su scoring dejó de discriminar entre modelos punteros. Añadieron AA-Briefcase y GDP.pdf, retiraron GPQA-Diamond por resuelto y subieron los datos privados al 40 % del peso. Su explicación: "the top of the leaderboard moved so fast that an interim update was necessary". Honesto, y deja claro que un índice público es una foto, no una medida estable.

    ¿Qué significa que un modelo solo rinda en dominios verificables?

    Que brilla donde una función dice sí o no sin intervención humana: un test que pasa, un exploit que funciona. Cuando el criterio es difuso —un informe, una decisión de arquitectura, atender a un cliente— no hay señal de evaluación limpia y el rendimiento cae. Es el motivo por el que tu agente acierta más en el código cubierto por tests.


    Por Bezael Pérez — Developer senior con más de 15 años de experiencia y fundador de Dominicode.

  • Zettelkasten para developers: ahora tus notas las lee un agente

    Zettelkasten para developers: ahora tus notas las lee un agente

    Hace unos meses le pedí a uno de mis agentes que redactara una sección concreta de una guía. Devolvió algo correcto, genérico y absolutamente vacío.

    Lo incómodo es que yo ya había escrito eso. Meses antes. Con el contexto, la decisión y el motivo por el que descarté la alternativa.

    El agente no lo encontró. Y cuando fui a buscarlo yo, tardé veinte minutos: estaba enterrado en la mitad de un markdown gigante sin título propio.

    Ahí entendí que mi problema con el Zettelkasten para developers no era de disciplina. Era de formato.

    La tesis de este post, después de un año largo escribiendo así: el método no ha cambiado nada. Lo que ha cambiado es quién lee las notas.

    En 2021 tomabas notas atómicas para tu yo futuro. En 2026 las tomas también para el agente que las va a recuperar, y ese lector es mucho menos indulgente que tú.

    Por si llegas sin contexto: el método Zettelkasten son las fichas que el sociólogo Niklas Luhmann usó durante décadas para escribir, con una caja de papel y ningún ordenador. Aplicado a programadores, el Zettelkasten para developers es esto: una idea por archivo, en markdown plano, con un título que afirma algo, autocontenida y enlazada a las demás. No es una carpeta de apuntes ordenada. Es un grafo consultable.

    Crédito: en esto me metió Tomas Vik, con su introducción al método y su retrospectiva un año largo después. Lo que viene es mi versión, con mis cicatrices.


    El conocimiento que solo entiendes tú ahora muere dos veces

    Durante años tomé notas como casi todos los programadores que conozco: capturas de pantalla, fragmentos copiados de la documentación, bullets sin verbo, enlaces con un "revisar esto" al lado.

    Eso no es una base de conocimiento, ni gestión del conocimiento personal (PKM). Es un vertedero ordenado alfabéticamente.

    El problema clásico ya era grave: un formato que solo entiendes tú, en el momento exacto en que lo escribiste, deja de entenderse a los tres meses.

    Lo nuevo es la segunda muerte. Si trabajas solo —yo opero Dominicode sin equipo— tus agentes son literalmente tu equipo, y ese equipo lee lo que dejaste escrito. Si lo que dejaste son bullets sin sujeto, tu equipo entero trabaja a ciegas.

    Una nota mal escrita ya no solo te penaliza a ti dentro de seis meses. Penaliza cada ejecución de cada agente que consulta ese repositorio, hoy.


    El Zettelkasten para developers ya no es productividad, es infraestructura

    La nota atómica pasó de consejo de productividad a requisito técnico el día en que dejó de leerte solo tú. Este es el giro que hace que esto merezca un post en 2026 y no en 2015.

    "Sé conciso" y "una idea por nota" eran consejos de higiene mental. Sonaban a gurú de productividad. Se podían ignorar sin consecuencias visibles.

    Hoy son requisitos técnicos de recuperación.

    Cuando indexas tu conocimiento para que un modelo lo consulte —lo que todo el mundo llama RAG— el texto se parte en fragmentos (chunking), esos fragmentos se convierten en embeddings y se recuperan por similitud semántica. Una nota de un solo tema, autocontenida y con un título que afirma algo es exactamente la unidad que ese índice recupera bien: cabe en muy pocos fragmentos y cada uno de esos fragmentos sigue significando algo por su cuenta.

    Una nota-cajón de sastre de cuatro mil palabras hace lo contrario. Se parte por la mitad de un razonamiento. El fragmento recuperado empieza con "por eso lo descartamos" y el sujeto de esa frase se quedó tres páginas más arriba. El modelo recibe un texto gramaticalmente correcto y semánticamente huérfano, y con eso improvisa. A eso lo llamamos alucinación, pero muchas veces es mala segmentación.

    Cómo se recuperan de verdad esos fragmentos —y por qué la búsqueda vectorial pura falla— lo desmonté en búsqueda híbrida y embeddings en producción.

    Nota-cajón Nota atómica
    Temas por archivo Varios Uno
    Título Una etiqueta: "Angular signals" Una afirmación: "Signals no sustituye a RxJS cuando necesitas cancelar una petición en vuelo"
    Al partirse en fragmentos El fragmento pierde el sujeto Cada fragmento sigue significando lo mismo
    Tú dentro de seis meses Hay que releer el archivo entero Decides si la abres sin abrirla
    Tu agente Recupera contexto huérfano e improvisa Recupera la unidad completa

    Mi ancla concreta: la base de conocimiento de Dominicode es un índice de búsqueda sobre markdown plano. Ahora mismo son 118 documentos y 455 fragmentos, repartidos en colecciones por ámbito —agents, _brand, _research, labs, videos, books, revenue— que los agentes consultan antes de escribir sobre cualquiera de esos ámbitos.

    Haz la división: sale por debajo de cuatro fragmentos por documento de media. No es un número mágico —los capítulos de libro tiran de esa media hacia arriba—, pero sí es el síntoma de la política editorial: documentos cortos y monotema. Cuando un archivo se dispara muy por encima de esa media, dentro hay dos o tres notas peleándose por el mismo sitio.

    La infraestructura de todo esto —el repo, la estructura, el gobierno vía CLAUDE.md— la conté en cómo montar un segundo cerebro con Claude Code. Este post va de la capa de encima: qué escribes dentro de cada archivo.


    El título es la nota

    Si me quedo con una sola regla de este año, es esta: el título tiene que afirmar algo, no nombrar un tema.

    No "Angular signals". Eso es una etiqueta.

    Sí "Signals no sustituye a RxJS cuando necesitas cancelar una petición en vuelo". Eso es una nota que puedes recuperar, contradecir o confirmar.

    Un título-afirmación hace tres cosas a la vez. Te obliga a tener una conclusión antes de escribir, en vez de acumular material. Le da al índice la señal más fuerte que va a recibir de ese documento. Y te permite decidir meses después si abres la nota sin abrirla.

    La segunda regla es más dura de lo que parece: la nota tiene que sobrevivir a ser leída sola. Sin la nota anterior. Sin la pestaña que tenías abierta ese día. Sin acordarte del proyecto.

    En la práctica eso significa desterrar los pronombres huérfanos. "Esto no funciona en producción" no es una nota. "El provideHttpClient con interceptores funcionales no captura errores lanzados dentro del resolver de una ruta" sí lo es.

    Así se ve el cambio en un archivo real:

    <!-- Antes: nota-cajón -->
    # Errores HTTP
    
    - esto no funciona en producción
    - revisar interceptores
    - preguntar a alguien
    
    <!-- Después: nota atómica -->
    # El interceptor funcional no captura errores lanzados dentro del resolver de una ruta
    
    `provideHttpClient(withInterceptors([...]))` solo ve lo que pasa por
    `HttpClient`. Si el resolver falla antes de lanzar la petición, el error
    sale por el router y el interceptor nunca se entera.
    
    Alternativa descartada: mover el try/catch a cada componente. Funciona,
    pero hay que repetirlo en cada ruta.
    

    El de abajo es más largo de escribir. Es el único de los dos que sigue sirviendo dentro de un año, y el único que un agente puede recuperar suelto.

    La autocontención es la misma propiedad que hace útil a un fragmento recuperado. Escribes para ti dentro de un año y, sin proponértelo, escribes para un recuperador vectorial. Es el mismo requisito con dos nombres.

    Es la lógica que aplicamos en el curso Construye con IA al montar la capa de contexto de un producto: el modelo no falla por falta de inteligencia, falla porque le llega texto sin sujeto.


    Los enlaces son el trabajo que no puedes delegar

    Enlazar es la única parte del Zettelkasten que no puedes automatizar, y es donde está todo el retorno. Aquí es donde mucha gente se baja.

    Enlazar una nota nueva con las que ya tienes te obliga a responder dos preguntas que no se contestan en piloto automático: ¿dónde encaja esto? y ¿contradice algo que ya escribí?

    La primera es de arquitectura. La segunda es la buena.

    Cuando una nota nueva choca de frente con una de hace ocho meses, ha pasado algo real: o aprendiste, o una de las dos estaba mal, o —lo más habitual— las dos son ciertas en contextos distintos y nunca habías delimitado cuál era cuál. Resolver ese choque suele producir una tercera nota, y esa tercera nota es la única de las tres que vale dinero.

    Ese momento no te lo da un agente. Un modelo te sugiere enlaces plausibles todo el día, y como sugerencia sirven. Lo que no tiene es la sensación de "un momento, esto no cuadra con lo que decidí en marzo". Esa fricción es el aprendizaje. Externalizarla es quedarte con el grafo y perder el motivo por el que existe.

    Lo que sale de ahí es un grafo, con las relaciones como dato de primera clase. Es la idea detrás de qué es graph engineering: la estructura entre las piezas transporta tanta información como las piezas.

    Con un efecto secundario que no esperaba: es el mejor antídoto contra el context drift que he encontrado. Cuando la conversación con el agente lleva dos horas derivando, las notas enlazadas son el punto fijo al que volver. No lo que el modelo cree recordar, sino lo que tú decidiste y escribiste.

    Y si viven en markdown, acaban siendo consultables como datos: notas huérfanas, enlaces rotos, temas que lo acaparan todo. Salud del grafo con una consulta, como conté en DuckDB + Obsidian.


    El coste honesto: esto es lento

    El coste real del método es el tiempo, y toca la parte que casi nadie escribe.

    Escribir así es lento. Mucho más lento que guardar el enlace y seguir. Leer para destilar una nota es más lento que leer, y bastante menos placentero. Dejé libros y documentación técnica a medias porque procesarlos a ese ritmo se volvió un trabajo, no una lectura.

    Y algo dejó de funcionar del todo: la ambición de capturarlo todo. Durante meses convertí en nota cosas que no lo merecían y el grafo se llenó de ruido que empeoraba la recuperación. Más notas no es mejor.

    Ahora tengo tres filtros. Si la respuesta a cualquiera de los tres es sí, no hay nota:

    • ¿Caduca en menos de dos semanas? Es un recordatorio, no conocimiento. Va a un issue, no al grafo.
    • ¿Existe upstream, es estable y está bien escrito? Enlázalo. Reescribir la documentación oficial de una librería es trabajo perdido que además envejece mal.
    • ¿Podrías reconstruirlo en cinco minutos buscando? No es tuyo todavía. Es información disponible, no conocimiento propio.

    Lo que sí merece nota casi siempre es lo mismo: la decisión, la alternativa que descartaste y el porqué. Eso no existe upstream, no está en la documentación de nadie, y es exactamente lo que te vuelven a preguntar dentro de un año.

    Es la misma forma de pensar que sostiene el libro de Spec-Driven Development: escribir la decisión antes que el código, porque la decisión es la parte cara.

    Y un último coste, más traicionero: optimizar el sistema en vez de usarlo. Reorganizar carpetas, cambiar de herramienta, diseñar la taxonomía perfecta. Todo eso se siente productivo y no produce nada.


    Qué cambiaría de mi Zettelkasten si empezara hoy de cero

    Cuatro cosas, y ninguna es instalar nada.

    1. Títulos-afirmación desde la primera nota. Renombrar cientos de archivos después es un trabajo horrible, y hasta que no lo haces la recuperación no mejora.
    2. Escribir pensando en el fragmento, no en el documento. Antes de guardar, pregúntate si un trozo suelto de esa nota, leído sin nada alrededor, sigue significando lo mismo. Si no, pártela.
    3. Colecciones por ámbito, no carpetas por tema. Los temas se solapan siempre y acabas con la misma nota en tres sitios. El ámbito casi nunca es ambiguo, y es lo que te permite filtrar la búsqueda.
    4. La nota de decisión antes que la nota de resumen. Los resúmenes de lo que leí envejecen. Las decisiones que tomé, con su alternativa descartada, siguen valiendo años después.

    Y una cosa que no cambiaría: no delegar los enlaces.


    Empieza por la siguiente nota

    No migres nada. No reorganices el vault. No elijas herramienta.

    Coge la última decisión técnica que tomaste esta semana —esa que discutiste contigo mismo durante veinte minutos— y escríbela como una sola nota, con un título que afirme algo y sin un solo pronombre sin sujeto. Después enlázala con lo más parecido que ya tengas escrito.

    Eso es todo. Repítelo cuando vuelva a pasar.

    Dentro de un año esa nota la vas a leer tú, medio dormido, buscando por qué hiciste lo que hiciste. Y la va a leer un agente que no tiene tu memoria, ni tu contexto, ni tu paciencia. Escríbela para el segundo: el primero sale beneficiado gratis.

    Si quieres el paso siguiente —cómo hacer que un agente use ese conocimiento sin inventarse la mitad— te lo dejé en el ebook gratuito Revisión por Contrato. Y si prefieres verlo montado sobre proyectos reales, con el repo y los agentes funcionando, está en Dominicode Labs.


    Preguntas frecuentes

    ¿Qué es exactamente el Zettelkasten para developers y en qué se diferencia de tomar apuntes?

    El Zettelkasten para developers es un sistema de notas atómicas en markdown: una idea por archivo, con un título que afirma algo, autocontenida y enlazada con las demás. La diferencia con tomar apuntes es el enlace. Los apuntes se acumulan en carpetas y se consultan por nombre de archivo; el Zettelkasten forma un grafo donde la relación entre dos notas transporta tanta información como las notas.

    En 2026 hay una segunda diferencia: una nota atómica es también la unidad que un índice vectorial recupera entera. Un apunte largo no.

    ¿Sigue teniendo sentido el Zettelkasten ahora que la IA me resume cualquier cosa?

    Tiene más sentido que antes, y por un motivo distinto al de siempre. La IA resume bien lo que está publicado; no puede resumir la decisión que tomaste tú, con la alternativa que descartaste y el motivo. Eso no existe en ningún corpus.

    Lo que sí cambió es el destinatario. Antes escribías notas atómicas para tu yo futuro. Ahora las escribes también para el agente que las va a recuperar, y ese lector no perdona un pronombre sin sujeto.

    ¿Necesito una herramienta de Zettelkasten o me vale markdown en un repo?

    Te vale markdown en un repo, y es lo que uso. La única condición innegociable es que los archivos sean texto plano y tuyos: eso te da git, búsqueda y la posibilidad de indexarlos para que los lea un agente.

    Las herramientas específicas —Obsidian, Logseq, Roam— aportan comodidad al enlazar y visualizar el grafo. Pero elegir herramienta antes de tener notas es la forma más elegante de no empezar nunca.

    ¿Cuántas notas hacen falta para que esto empiece a servir?

    Menos de las que imaginas para el uso individual, y bastantes más para el efecto de red, que es cuando el grafo te sugiere conexiones que no habías visto.

    No te fijes un número, fíjate una señal: el sistema funciona el día que buscas algo, lo encuentras, y esa nota te resuelve el problema sin abrir nada más.

    ¿No puede el agente resumir mis notas largas y ahorrarme el trabajo?

    Puede resumir. El problema es cuándo. En el momento de la recuperación el agente ya no ve la nota entera: ve los fragmentos que el índice le devolvió —salvo que montes recuperación por documento padre, que es trabajo aparte—, y si están mal cortados el resumen es una reconstrucción sobre material incompleto.

    Usar un modelo para partir notas viejas en notas atómicas sí es buena idea. Lo que no puedes delegar es decidir qué idea va en cada nota, porque esa decisión es el contenido.

    ¿Esto sirve para documentación de equipo o solo para notas personales?

    Sirve para ambas, pero no son lo mismo. La documentación de equipo describe cómo funciona el sistema hoy y se actualiza cuando el sistema cambia. Las notas atómicas capturan por qué se decidió algo y se acumulan sin borrarse.

    Si trabajas en solitario la frontera casi desaparece: tu grafo de decisiones acaba siendo el onboarding de tus propios agentes.

    ¿Qué hago con las notas largas que ya tengo escritas?

    Nada, hasta que las necesites. Migrar el archivo entero es el clásico proyecto que se abandona a la tercera tarde.

    Aplica la regla al vuelo: la próxima vez que abras una nota vieja y te cueste encontrar dentro lo que buscabas, ese es el momento de partirla en dos o tres notas con título propio. Migras solo lo que demuestra que se usa.


    Por Bezael Pérez — Developer senior con más de 15 años de experiencia y fundador de Dominicode.

  • Streaming SSE con Hono y Bun: la API de tu agente de IA

    Streaming SSE con Hono y Bun: la API de tu agente de IA

    El endpoint funcionaba. El agente respondía. El usuario veía una ruleta girando veintidós segundos y luego, de golpe, un muro de texto.

    Lo peor no fue la espera: abrió la misma pregunta en tres pestañas porque creyó que se había colgado. Tres ejecuciones del agente, tres facturas de tokens, una respuesta leída.

    Lo reescribí usando streaming SSE con Hono y Bun. Y el arreglo de fondo no fue técnico, fue conceptual: yo devolvía la respuesta de un agente como si fuera un JSON. Un agente no devuelve un resultado. Un agente transcurre. Piensa, llama a una herramienta, se equivoca, reintenta.

    Si tu API no transmite ese transcurso, el usuario solo ve una ruleta y saca sus propias conclusiones.

    Para exponer un agente por HTTP con salida en tiempo real, usa el helper streamSSE de hono/streaming sobre Bun: emite eventos con nombre (token, tool_call, error, done) en lugar de texto plano, propaga la desconexión del cliente a un AbortSignal con stream.onAbort() para dejar de gastar tokens, y manda un comentario SSE (: ping) cada 15-20 segundos para que ningún proxy corte la conexión. Todo el código de este post está verificado ejecutándolo contra Hono 4.13.7 sobre Bun 1.3.


    SSE no está deprecado: lo que se deprecó fue el transporte HTTP+SSE de MCP

    Son dos capas distintas y solo se deprecó una. Si vienes de mi post sobre montar un MCP Server en producción con Streamable HTTP y auth, su primer titular dice que SSE está deprecado, y ahora te propongo construir una API con SSE. No hay contradicción.

    Lo que se deprecó es el transporte HTTP+SSE del protocolo MCP —el de dos endpoints, uno GET para abrir el canal y otro POST para enviar, definido en la revisión 2024-11-05—, sustituido por Streamable HTTP en la 2025-03-26 y reclasificado formalmente como Deprecated en la 2026-07-28. Eso decide cómo hablan entre sí un cliente MCP y un servidor MCP.

    Server-Sent Events, el mecanismo del navegador, no está deprecado en absoluto. La especificación vigente de MCP —revisión 2026-07-28— sigue construida sobre él: el servidor responde a cada petición con un único objeto JSON o con un stream de Server-Sent Events, y el cliente está obligado a aceptar text/event-stream. En el registro oficial de features deprecadas la única entrada de transporte sigue siendo HTTP+SSE transport, deprecado en 2025-03-26, con Streamable HTTP como ruta de migración. Cambió la coreografía de endpoints, no el formato del stream. Aquí no implementamos MCP: construimos tu propia API para tu propio frontend.


    Por qué SSE y no WebSockets para un agente

    Porque el flujo de un agente es unidireccional: el usuario manda una pregunta y luego solo escucha. Abrir un canal bidireccional para eso es pagar complejidad por una dirección que nunca usas.

    Server-Sent Events (SSE) es el estándar web que permite a un servidor enviar un flujo de mensajes al cliente sobre una única conexión HTTP abierta, en texto plano y con el formato event: / data: / id:. Es unidireccional por diseño: el cliente abre la conexión y a partir de ahí solo recibe.

    La diferencia práctica está en lo que tienes que operar después del primer despliegue.

    SSE WebSockets
    Dirección Servidor → cliente Bidireccional
    Protocolo HTTP normal, respuesta larga Upgrade a ws://
    Proxies, CDN y balanceadores Pasa como cualquier respuesta HTTP Necesitan soporte explícito de upgrade
    Reconexión Automática en el navegador, con Last-Event-ID La implementas tú
    Autenticación Tus cookies o headers de siempre (con fetch) Handshake aparte, token en query
    Estado en el servidor Ninguno: es una request más Conexiones vivas que gestionar
    Depuración curl -N y lo lees Herramienta específica

    Elige WebSockets cuando el cliente tenga que interrumpir, corregir o hablar durante la generación: audio en vivo, edición colaborativa. Para un chat de agente con herramientas, SSE gana por aburrimiento operativo.


    Streaming SSE con Hono y Bun: el endpoint en veinte líneas

    El helper vive en hono/streaming y su firma es streamSSE(c, callback, onError?). Dentro del callback recibes un objeto de stream y escribes eventos con writeSSE().

    import { Hono } from 'hono'
    import { streamSSE } from 'hono/streaming'
    
    const app = new Hono()
    
    app.post('/agent', (c) =>
      streamSSE(c, async (stream) => {
        await stream.writeSSE({
          event: 'tool_call',
          data: JSON.stringify({ name: 'search_docs' }),
          id: '1',
        })
        await stream.writeSSE({ event: 'token', data: JSON.stringify({ text: 'Hola' }), id: '2' })
        await stream.writeSSE({ event: 'done', data: '{}' })
      })
    )
    
    export default { port: 3000, fetch: app.fetch }
    

    Ese export default { port, fetch } no es de Hono: es el contrato de Bun.serve. Bun arranca el servidor con bun run index.ts, sin adaptador ni servidor HTTP intermedio, y empuja cada chunk al socket según lo produces — que es justo lo que necesita un stream.

    El objeto que acepta writeSSE es { data, event?, id?, retry? }, con data como string o Promise<string>. Hono pone por ti Content-Type: text/event-stream, Cache-Control: no-cache, Connection: keep-alive y Transfer-Encoding: chunked. Lo tienes documentado en el streaming helper de Hono.

    Lo que sale por el cable, verificado con curl -N, es exactamente esto:

    event: tool_call
    data: {"name":"search_docs"}
    id: 1
    
    event: token
    data: {"text":"Hola"}
    id: 2
    
    event: done
    data: {}
    

    Hono encaja aquí porque es un router sobre Web Standards: no te obliga a envolver la respuesta en abstracciones propias, y eso importa cuando lo que devuelves es un stream y no un objeto. La comparativa completa está en Hono vs NestJS vs Express.

    Si tu stack es NestJS, el mismo problema se resuelve de otra forma y lo cubrí aparte en streaming de respuestas de IA con NestJS y el Vercel AI SDK: allí el SDK gestiona el protocolo por ti sobre la Response nativa. Aquí el contrato de eventos lo defines tú, que es justo lo que quiero que controles.


    Eventos con significado, no un chorro de texto

    El error que veo en casi todas las implementaciones: mandar solo data: <trozo de texto> y que el cliente concatene. Con eso el frontend no puede renderizar estados, solo puede pintar letras.

    Un agente tiene fases visibles para el usuario. Dale un nombre a cada una.

    event data (JSON) Qué hace el cliente
    token {"text":"…"} Concatena en la burbuja de respuesta
    tool_call {"name":"search_docs","args":{…}} Muestra "Buscando en la documentación…"
    tool_result {"name":"search_docs","ok":true,"ms":412} Cierra el indicador de herramienta
    error {"code":"RATE_LIMIT","message":"…"} Pinta el fallo y ofrece reintentar
    done {"usage":{"input":812,"output":344}} Cierra el stream y guarda la conversación

    Ese contrato es una API pública aunque viva dentro de tu repo. Si mañana renombras tool_call a toolCall, rompes el frontend en producción sin que ningún compilador te avise: entre servidor y cliente solo viaja texto.

    Por eso defino el contrato como un discriminated union validado con Zod y lo importo en los dos lados. El servidor lo usa para serializar, el cliente para parsear. Si un evento no encaja con el schema, lo descartas y lo registras en lugar de romper el render. Es el patrón que enseño en el curso de Zod para validación y transformación de datos en TypeScript, aplicado al borde más frágil de una app de IA.

    Un detalle del formato: writeSSE parte tu data por saltos de línea y emite una línea data: por cada trozo. Con JSON.stringify no te afecta, porque produce una sola línea. Con texto crudo multilínea, sí.


    Cancelación: el usuario cierra la pestaña y tú sigues pagando

    Cuando el cliente se desconecta, Hono marca stream.aborted = true y dispara los listeners registrados con stream.onAbort(). Ese es el enganche para abortar el trabajo del agente.

    Aquí está el detalle que casi nadie cuenta, y lo verifiqué ejecutándolo: escribir en un stream muerto no lanza ninguna excepción. El write interno de Hono captura el error y sigue como si nada. Si tu bucle espera un try/catch para enterarse de la desconexión, va a seguir llamando al modelo hasta terminar la respuesta entera. Y la vas a pagar.

    app.post('/agent', (c) =>
      streamSSE(c, async (stream) => {
        const ac = new AbortController()
        stream.onAbort(() => ac.abort()) // el cliente se fue: corta el trabajo
    
        const agent = runAgent({ prompt: await c.req.json(), signal: ac.signal })
    
        for await (const chunk of agent) {
          if (stream.aborted) return // guardia explícita: no confíes en que write falle
          await stream.writeSSE({ event: 'token', data: JSON.stringify({ text: chunk }) })
        }
    
        await stream.writeSSE({ event: 'done', data: '{}' })
      })
    )
    

    Dos mecanismos, y quieres los dos. onAbort propaga la cancelación hacia abajo —al SDK del modelo, a tu fetch de herramientas, a la query de base de datos— porque casi todo el ecosistema acepta un AbortSignal. La guardia if (stream.aborted) return corta el bucle en el siguiente ciclo aunque la librería de turno ignore la señal.

    En mi prueba, con un cliente que abortaba a mitad de stream, onAbort se disparó en el mismo tick en que el bucle vio aborted = true, y el AbortSignal del agente quedó abortado.

    Hay un detalle propio de Bun que conviene conocer: onAbort depende de que el runtime cancele el ReadableStream de la respuesta, y Hono todavía arrastra una función isOldBunVersion() que considera antigua cualquier versión que empiece por 1.1, 1.0 o 0.. En esas escucha c.req.raw.signal para abortar el stream a mano. De Bun 1.2 en adelante funciona el camino nativo y no tienes que hacer nada.

    Esto es la contrapartida natural del agentic loop en producción con TypeScript: allí pones el techo de pasos para que el agente no se dispare solo, aquí pones el interruptor para que no siga corriendo cuando ya no hay nadie escuchando.


    Heartbeats: por qué tu stream muere a los sesenta segundos

    Porque los proxies inversos, los balanceadores y los CDN cierran conexiones que llevan demasiado tiempo sin transmitir bytes. En Nginx son los 60 segundos de proxy_read_timeout, su valor por defecto. Un agente pensando o esperando a una herramienta lenta produce exactamente ese silencio.

    La solución cabe en una línea. SSE define que toda línea que empieza por : es un comentario y el cliente la ignora:

    const beat = setInterval(() => {
      if (!stream.aborted) void stream.write(': ping\n\n')
    }, 15_000)
    
    stream.onAbort(() => clearInterval(beat))
    // y clearInterval(beat) también al terminar bien
    

    Y hay una segunda mitad que casi nadie configura: aunque mandes el heartbeat perfecto, un proxy con buffering activo va acumulando los eventos y entregándolos a golpes, así que el usuario sigue sin ver nada en tiempo real. Se desactiva con una cabecera, y la propia especificación de MCP la recomienda: los servidores SHOULD incluir X-Accel-Buffering: no al abrir un stream SSE, porque sin ella "los proxies pueden acumular mensajes antes de enviarlos al cliente".

    c.header('X-Accel-Buffering', 'no')
    

    Verificado en el cable: el ping viaja, no genera ningún evento en el cliente y mantiene la conexión con tráfico. Elige un intervalo por debajo del timeout de tu proxy: 15 segundos es seguro contra los 60 de proxy_read_timeout. En PaaS el corte llega antes y no lo decides tú — los timeouts de Render, Railway y Fly los desgloso en desplegar agentes LangChain en producción.

    Aprovecha también retry: al emitir { data: '…', retry: 3000 } le dices al navegador cuánto esperar antes de reconectar. Y si numeras los eventos con id, el navegador reenvía el último en la cabecera Last-Event-ID al reconectar, así que puedes reanudar en vez de empezar de cero. Eso solo aplica cuando el cliente es EventSource, y ahí viene el siguiente problema.


    El cliente: por qué EventSource se te queda corto

    Porque EventSource solo hace peticiones GET y no admite body ni headers personalizados. Para un agente necesitas mandar el prompt, el historial y un Authorization: o metes la conversación entera en la query string, o cambias de herramienta.

    Cambias de herramienta. fetch con un lector de stream y un parser de veinte líneas:

    async function* readSSE(res: Response) {
      const reader = res.body!.getReader()
      const decoder = new TextDecoder()
      let buffer = ''
    
      while (true) {
        const { done, value } = await reader.read()
        if (done) break
        buffer += decoder.decode(value, { stream: true })
    
        let sep: number
        while ((sep = buffer.indexOf('\n\n')) !== -1) {
          const raw = buffer.slice(0, sep)
          buffer = buffer.slice(sep + 2)
    
          let event = 'message'
          let id: string | undefined
          const data: string[] = []
          for (const line of raw.split('\n')) {
            if (line.startsWith(':')) continue // heartbeat
            if (line.startsWith('event:')) event = line.slice(6).trim()
            else if (line.startsWith('data:')) data.push(line.slice(5).replace(/^ /, ''))
            else if (line.startsWith('id:')) id = line.slice(3).trim()
          }
          if (data.length) yield { event, id, data: data.join('\n') }
        }
      }
    }
    

    Dos cosas se rompen si las improvisas. Los eventos llegan agrupados o partidos: en mi prueba el primer chunk traía dos eventos completos juntos, así que hay que bufferear y cortar por línea en blanco, nunca asumir un chunk igual a un evento. Y decoder.decode(value, { stream: true }) no es opcional: sin ese flag, un carácter multibyte partido entre dos chunks llega corrupto. En español eso es cualquier acento.

    El precio de dejar EventSource es que pierdes la reconexión automática y el Last-Event-ID. Si los necesitas, los implementas tú guardando el último id recibido y reenviándolo al reintentar. Cancelar, en cambio, es trivial: pasa un AbortController al fetch y llama a abort() cuando el usuario pulse "parar" o el componente se desmonte. Eso dispara todo el camino de cancelación de la sección anterior.


    El error a mitad de stream: ya enviaste un 200 OK

    Cuando el agente falla en el segundo 12, las cabeceras salieron hace 12 segundos. No hay un 500 que devolver. El fallo tiene que viajar dentro del stream, como un evento más.

    Hono lo contempla con el tercer argumento de streamSSE:

    app.post('/agent', (c) =>
      streamSSE(
        c,
        async (stream) => {
          // ...el agente...
        },
        async (err, stream) => {
          logger.error({ err }, 'agent stream failed')
          await stream.writeSSE({
            event: 'error',
            data: JSON.stringify({ code: 'AGENT_FAILED', message: 'No he podido completar la respuesta.' }),
          })
        }
      )
    )
    

    Dos avisos que solo se descubren mirando la respuesta cruda, y los comprobé.

    El primero: si pasas el tercer argumento a streamSSE, además de tu handler Hono emite automáticamente su propio evento error con el message de la excepción en crudo. Tu cliente recibirá dos eventos error por un solo fallo. Trátalo: quédate con el primero y descarta el resto hasta el cierre. Sin onError, en cambio, Hono no manda nada al cliente y la excepción se queda en un console.error del servidor.

    El segundo es de seguridad. Ese mensaje automático es el texto real de la excepción y va tal cual al navegador. Si tu error trae una URL interna, un nombre de tabla o un fragmento de credencial, acabas de filtrarlo. Lanza errores con mensajes ya saneados, o envuelve el cuerpo del handler en tu propio try/catch y nunca dejes que la excepción llegue al helper.

    Revisar este tipo de detalle en el código que genera un agente es lo que trabajo en el ebook gratuito Revisión por Contrato: un modelo te escribe este endpoint en treinta segundos, te devuelve el camino feliz impecable y te deja estos dos fallos intactos.


    Qué puedes montar hoy

    Coge tu endpoint de agente actual, el que devuelve un JSON al final, y cámbiale tres cosas: envuélvelo en streamSSE, emite token / tool_call / done en vez de un objeto final, y engancha stream.onAbort() a un AbortController que pases hacia abajo.

    Con eso dejas de pagar respuestas que nadie lee. El resto —heartbeats, reconexión, validación con Zod— lo añades cuando el primero se sostenga.

    Si quieres el flujo completo de idea a producto construyendo con agentes, lo enseño paso a paso en el curso Construye con IA.


    Preguntas frecuentes

    ¿SSE está deprecado en 2026?

    No. Lo que se deprecó fue el transporte HTTP+SSE del protocolo MCP, sustituido por Streamable HTTP en la revisión 2025-03-26. Server-Sent Events como mecanismo web sigue vigente y es estándar; en la revisión vigente 2026-07-28 Streamable HTTP lo sigue usando para la parte de streaming, respondiendo con Content-Type: text/event-stream. Son capas distintas: una es la coreografía de endpoints de MCP, otra es el formato del stream.

    ¿Cómo detecto en Hono que el cliente cerró la pestaña?

    Con stream.onAbort(callback) para reaccionar, y con la propiedad stream.aborted para comprobarlo dentro de tu bucle. Lo importante es no confiar en que la escritura falle: el write de Hono captura el error internamente y no lanza nada, así que un bucle sin la guardia if (stream.aborted) seguirá llamando al modelo y generando coste después de que el usuario se haya ido.

    ¿Puedo usar EventSource para llamar a mi endpoint de agente?

    Solo si tu endpoint es GET y no necesitas headers personalizados, porque EventSource no admite ni body ni Authorization. Para un agente al que le mandas prompt e historial, lo práctico es fetch con un parser propio del stream. Pierdes la reconexión automática y el manejo de Last-Event-ID, y si los necesitas los implementas tú guardando el último id recibido.

    ¿Cada cuánto debo mandar un heartbeat en un stream SSE?

    Cada 15 o 20 segundos, siempre por debajo del timeout de inactividad de tu proxy o balanceador — 60 segundos es el valor típico de Nginx. Se envía como un comentario SSE: una línea que empieza por dos puntos seguida de una línea en blanco, que el cliente ignora sin generar ningún evento. Recuerda limpiar el setInterval tanto al terminar bien como en onAbort.

    ¿Cómo devuelvo un error si ya envié las cabeceras con 200 OK?

    Emitiendo un evento error dentro del propio stream, porque el código de estado ya viajó. En Hono usas el tercer argumento de streamSSE. Ten en cuenta que Hono añade además su propio evento error con el mensaje crudo de la excepción, así que tu cliente recibirá dos, y conviene sanear los mensajes que lanzas para no filtrar detalles internos.

    ¿SSE o WebSockets para una app de chat con IA?

    SSE, salvo que el cliente necesite hablar durante la generación. El flujo de un chat con agente es una pregunta y luego solo escuchar, y SSE viaja sobre HTTP normal: atraviesa proxies y CDN sin configuración especial, reutiliza tu autenticación y no deja estado de conexión que gestionar. WebSockets compensa cuando hay audio bidireccional o interrupciones en vivo.


    Si quieres ver este endpoint construido en directo, con el agente conectado y midiendo la cancelación en tiempo real, lo publico en el canal de YouTube de Dominicode.

    Por Bezael Pérez — Developer senior con más de 15 años de experiencia y fundador de Dominicode.

  • Claude Code en monorepos: dale solo la rebanada que necesita

    Claude Code en monorepos: dale solo la rebanada que necesita

    Un cliente me pasó su monorepo el mes pasado. Nueve paquetes, pnpm workspaces, Turborepo por encima. Le pedí a Claude Code algo ridículamente pequeño: cambiar el tipo de una prop en packages/ui.

    Tres respuestas después me estaba proponiendo tocar el cliente HTTP del backend.

    No era un modelo tonto. Era yo. Había arrancado la sesión desde la raíz del repo, y trabajar con Claude Code en monorepos desde la raíz significa una cosa muy concreta: le has dado nueve paquetes de superficie para una tarea que vive en uno.

    Esto no es el problema del que ya escribí en Context Drift. Aquel es temporal: la sesión se alarga, el historial se pudre, el agente se olvida de la instrucción de la iteración 3. Este es espacial. Se degrada en el minuto uno, con la ventana medio vacía, porque el repo es grande y nadie le ha dicho qué parte del repo importa.

    La tesis del post es esta: en un monorepo, la decisión más importante que tomas no es qué prompt escribes. Es desde qué directorio arrancas el agente.

    Smart context slicing es la práctica de arrancar el agente en el subárbol mínimo del monorepo que la tarea necesita, calculado a partir del grafo de dependencias en vez de a ojo. Son tres decisiones concretas: desde qué directorio lanzas claude, qué paquetes vecinos añades con --add-dir y qué rutas bloqueas con reglas de denegación. Las tres, en ese orden, son el resto del post.

    Claude Code en monorepos: un CLAUDE.md en la raíz no escala

    La documentación de Anthropic recomienda mantener cada CLAUDE.md por debajo de 200 líneas, y lo justifica: los archivos largos consumen más contexto y reducen la adherencia a las instrucciones.

    Ahora divide. Nueve paquetes, 200 líneas: 22 líneas por paquete para explicar su stack, sus convenciones y sus trampas.

    Así que solo hay dos finales, y he visto los dos.

    O el CLAUDE.md crece hasta las 600 líneas y el agente ignora la mitad — incluidas las reglas que importaban. O se queda genérico ("usa TypeScript estricto", "escribe tests"), que es una forma elegante de no decir nada.

    Si todavía estás montando el tuyo, el punto de partida lo dejé en CLAUDE.md: el system prompt de tu proyecto. Aquí doy por hecho que ya lo tienes y se te ha quedado pequeño.

    La solución es partirlo: raíz para lo global, un archivo por paquete para lo local.

    monorepo/
      CLAUDE.md                 # reglas globales: commits, estilo, "corre los scripts desde el paquete"
      packages/
        ui/CLAUDE.md            # convenciones de componentes, tokens de diseño
        api/CLAUDE.md           # Knex, migraciones, .env obligatorio
        web/CLAUDE.md           # rutas, data fetching
    

    Pero partirlo no sirve de nada si no entiendes cuándo se carga cada trozo.

    La regla de carga que casi nadie ha leído

    Claude Code no trata igual a los CLAUDE.md que están por encima de ti y a los que están por debajo.

    Dónde vive el CLAUDE.md Cuándo entra en contexto
    Tu directorio de trabajo y todos sus ancestros Al arrancar la sesión, siempre
    Subdirectorios por debajo de ti Bajo demanda, solo cuando el agente lee un archivo de esa carpeta

    Si arrancas desde la raíz, cargas solo el CLAUDE.md raíz — y vas acumulando el de cada paquete que el agente toque. Toca muchos, porque no sabe dónde está el límite.

    Si arrancas con cd packages/ui && claude, cargas raíz + packages/ui de golpe, y los de api y web no existen para esa sesión mientras no los pises. Además, solo puede leer y editar dentro de ese subárbol hasta que le concedas más.

    Eso es una rebanada. Y te ha costado un cd.

    Compruébalo: lanza /context y mira la lista de Memory files. Ahí está lo que se cargó de verdad.

    El slice no lo decides tú: lo decide el grafo de dependencias

    "Trabaja desde el paquete" está bien hasta que la tarea toca de verdad a los vecinos. Cambiar un tipo exportado de ui puede romper a quien lo consume, y si el agente no ve a esos consumidores, te entrega algo que compila en su rebanada y revienta en CI.

    La pregunta correcta no es qué paquetes te apetece abrir, sino qué paquetes toca esta tarea de verdad. Y esa respuesta ya está en tu repo: en el grafo de dependencias.

    Monté un workspace de cinco paquetes para verlo, con pnpm 11.1.3 y Turborepo 2.10.12. @acme/api y @acme/web dependen de @acme/ui; @acme/ui depende de @acme/config; @acme/jobs va por libre.

    Inventario primero:

    pnpm ls -r --depth -1
    

    Ahora el blast radius hacia arriba — qué se rompe si toco @acme/ui. En la sintaxis de filtros de pnpm, los tres puntos delante del nombre significan "y todo lo que depende de él":

    pnpm --filter "...@acme/ui" ls --depth -1
    # (salida recortada al nombre de cada paquete)
    # @acme/ui
    # @acme/api
    # @acme/web
    

    Y hacia abajo, con los puntos detrás, "y todo aquello de lo que depende":

    pnpm --filter "@acme/ui..." ls --depth -1
    # (salida recortada)
    # @acme/ui
    # @acme/config
    

    Si quieres el cierre completo en los dos sentidos, pones los puntos a ambos lados: "...@acme/ui...". Y si te sobra el propio paquete, el circunflejo lo excluye: "...^@acme/ui" devuelve solo api y web.

    Turborepo lo da con un matiz. --dry enseña el plan sin ejecutar nada:

    turbo run build --filter="...@acme/ui" --dry
    
    • Packages in scope: @acme/api, @acme/ui, @acme/web
    • Running build in 3 packages
    

    El detalle que solo ves ejecutándolo: "Packages in scope" son 3, pero si sacas el JSON aparecen 4 tareas:

    turbo run build --filter="...@acme/ui" --dry=json | jq -r '.tasks[].directory' | sort -u
    # packages/api
    # packages/config
    # packages/ui
    # packages/web
    

    @acme/config no está en el scope de edición, pero entra en el grafo de build porque ui lo necesita compilado. Son dos rebanadas distintas y conviene no confundirlas:

    Rebanada Paquetes % del repo
    Repo completo 5 100%
    Slice de edición (ui + dependientes) 3 60%
    Slice de build (añade config) 4 80%
    Nunca entra (@acme/jobs) 1 20%

    En un repo de cinco paquetes, dejar fuera un paquete suena a poco. En el del cliente, con nueve, el slice real de la tarea eran tres paquetes: dos tercios del repo que no tenían por qué abrirse nunca.

    Con esa lista en la mano, el arranque deja de ser una corazonada:

    cd packages/ui
    claude --add-dir ../api --add-dir ../web
    

    Si el equipo entero trabaja así, lo fijas en packages/ui/.claude/settings.json:

    {
      "permissions": {
        "additionalDirectories": ["../api", "../web"]
      }
    }
    

    Ojo con una diferencia que muerde: additionalDirectories da acceso a los ficheros pero no carga nunca el CLAUDE.md ni las skills de esos directorios. Con --add-dir sí cargan las skills, y el CLAUDE.md solo si arrancas con CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD=1. Si escribiste un CLAUDE.md en packages/api y no sale en /context, es por esto.

    Y como el slice te dice qué se puede romper, también sabes qué verificar antes de dar la tarea por buena: los tests de api y web, no los de ui. Convertir "parece que funciona" en un veredicto ejecutable lo desarrollé entero en el ebook gratuito Revisión por Contrato, sobre cómo revisar lo que te entrega un agente sin leértelo línea a línea.

    Lo que no debe entrar en la ventana bajo ningún concepto

    Las búsquedas de contenido de Claude Code respetan tu .gitignore por defecto, así que node_modules/, dist/ y build/ ya están fuera de los resultados de un grep.

    El problema es lo que sí está commiteado: código generado, un SDK vendorizado, snapshots enormes. Para eso hay reglas de denegación:

    {
      "permissions": {
        "deny": [
          "Read(./**/dist/**)",
          "Read(./**/*.generated.*)",
          "Read(./vendor/**)"
        ]
      }
    }
    

    Un detalle que rompe esto sin avisar: los patrones relativos anclan en el directorio desde el que arrancas la sesión, no en la raíz del repo. Si guardas estas reglas en la raíz pero lanzas la sesión desde packages/ui, Read(./vendor/**) está apuntando a packages/ui/vendor/. Para que apliquen en todo el repo las escribes absolutas, con doble barra: Read(//ruta/absoluta/al/repo/vendor/**).

    Y si arrancando desde la raíz se te cuelan los CLAUDE.md de equipos con los que no trabajas, existe claudeMdExcludes, en el .claude/settings.local.json de la raíz. Los patrones se comparan contra rutas absolutas, así que empiezan por **/ para que casen en cualquier punto del árbol:

    {
      "claudeMdExcludes": ["**/packages/legacy-*/**"]
    }
    

    Con un aviso honesto: esa lista es estática, no un interruptor por tarea. Para alternar de paquete cada día la herramienta sigue siendo el cd.

    Cuando de verdad no sabes dónde está, delega la búsqueda

    Todo lo anterior asume que sabes qué paquete tocar. A veces no lo sabes, y ahí es donde la gente destroza la sesión: "busca en el repo dónde se genera el token de refresco". El agente lee doscientos archivos y te devuelve una frase. Los doscientos archivos se quedan en tu ventana. La frase también, pero ya da igual.

    Delégalo a un subagente. Corre en su propia ventana de contexto y te devuelve el resumen, no los archivos:

    Usa un subagente para localizar en qué paquetes se genera y se valida
    el token de refresco. Devuélveme solo la lista de rutas y una línea
    por cada una. No propongas cambios todavía.
    

    El resultado es una lista de paquetes. Cierras la sesión, haces cd al correcto y empiezas la tarea real con la ventana limpia. La exploración se paga una vez y se tira.

    Es el mismo principio que conté en Context Engineering: lo caro no es el token, es el token irrelevante que se queda mirándote el resto de la sesión.

    Lo que puedes hacer hoy en tu monorepo

    Una sola cosa, y es gratis: deja de arrancar el agente desde la raíz del monorepo.

    Antes de la próxima tarea, corre pnpm --filter "...<tu-paquete>" ls --depth -1, mira los tres o cuatro nombres que salen, y arranca así:

    cd packages/<tu-paquete>
    claude --add-dir ../<vecino>
    

    No hace falta que escribas ni un CLAUDE.md nuevo para notar la diferencia. Eso viene después, cuando ya sepas qué reglas son globales y cuáles de un paquete — y eso solo se ve claro tras unos días trabajando por rebanadas.

    Si quieres el flujo completo, de la idea al producto con estas decisiones tomadas antes de escribir código, es lo que montamos en el curso Construye con IA.

    Preguntas frecuentes

    ¿Es mejor arrancar Claude Code desde la raíz del monorepo o desde el paquete?

    Desde el paquete, salvo que la tarea cruce varios subsistemas de verdad. Arrancando desde packages/ui cargas el CLAUDE.md raíz más el de ui, y el agente solo puede leer y editar ese subárbol. Desde la raíz tienes acceso a todo: útil para refactors transversales, caro para cualquier otra cosa. Si necesitas un vecino puntual, --add-dir te lo añade sin romper el aislamiento.

    ¿Los CLAUDE.md de los subdirectorios se cargan siempre?

    No, y esta es la confusión más habitual. Los de tu directorio de trabajo y de todos sus ancestros se cargan al arrancar la sesión. Los de subdirectorios por debajo de ti se cargan bajo demanda, solo cuando el agente lee un archivo de esa carpeta. Para ver qué se cargó de verdad en una sesión, lanza /context.

    ¿Qué hago si la tarea toca varios paquetes a la vez?

    Dásela entera en una sola sesión, con el slice completo delante. Partirla en una sesión por paquete es peor: cada sesión redecide el diseño desde cero y acabas con tres criterios distintos. Calcula el slice con el filtro de dependientes, añade esos directorios y trabaja en plan mode antes de editar: el plan se escribe a un archivo que Claude Code reinyecta tras cada compactación.

    ¿Esto sirve si uso Nx o si mi repo es un solo árbol grande sin paquetes?

    Sí. En Nx el equivalente es nx graph para ver el grafo y nx show projects --affected para saber qué proyectos toca un cambio: cambia el comando, no la idea. Y en un repo de un solo árbol sustituyes "paquete" por "subsistema" — src/billing/, src/auth/, lib/core/. Un CLAUDE.md por subsistema y un cd hacen el mismo trabajo.

    ¿No basta con el .gitignore para que el agente no lea dist?

    Para las búsquedas de contenido sí: Claude Code respeta el .gitignore por defecto, así que dist/, build/ y node_modules/ no aparecen cuando busca texto. Lo que no cubre es lo commiteado — código generado, SDKs vendorizados, fixtures gigantes. Para eso necesitas reglas Read(...) en permissions.deny. Con un límite: cubren las herramientas de fichero y los comandos de Bash que Claude Code reconoce, pero un grep -r sobre una carpeta con ficheros denegados sigue sacándolos por pantalla.


    Por Bezael Pérez — Developer senior con más de 15 años de experiencia y fundador de Dominicode.

  • Tu agente no sale del repo: interoperabilidad de agentes de IA

    Tu agente no sale del repo: interoperabilidad de agentes de IA

    Escribí un subagente de revisión de código para Claude Code. Lee el diff, comprueba el contrato del módulo y marca lo que rompe.

    Un equipo con el que trabajo quiso ese mismo criterio en su pipeline, que no corre sobre Claude Code. Abrí el fichero para copiarlo y la ilusión me duró treinta segundos.

    Lo único portable era el criterio, y el criterio son cuatro párrafos de texto. El resto —cómo pide las herramientas, dónde guarda lo revisado, quién arranca el bucle, cómo reporta— estaba pegado al harness.

    Ese es el estado real de la interoperabilidad de agentes de IA hoy: no existe. Tenemos agentes que funcionan muy bien exactamente donde nacieron y en ningún otro sitio.

    La interoperabilidad de agentes de IA es la capacidad de ejecutar el mismo agente —su criterio, sus herramientas, su memoria y su bucle— en un harness distinto de aquel donde se escribió, sin reescribirlo. No consiste en que hable con otros agentes: consiste en que se mude.


    El software se volvió reutilizable. Los agentes, no

    El software se convirtió en una industria enorme por una razón aburrida: se escribe una vez y se usa muchas.

    Una librería la escribe un dev y la usan miles. Una API expone una capacidad y acaba dentro de productos que su autor nunca vio. Las app stores añadieron distribución global a eso. Cada pieza de software podía ser el bloque de construcción de otra cosa.

    Un agente debería llevar esa idea más lejos, no menos. No expone una función: expone un criterio. Entiende un objetivo, decide, usa herramientas, se comunica y ejecuta trabajo. Un buen agente de revisión, de extracción de facturas o de migración de tests debería ser un trabajador digital que enchufas donde haga falta su capacidad.

    Y sin embargo. El agente de extracción de facturas que montó tu compañero con LangChain no puede entrar en el CLI del equipo de al lado. El agente de tests que va fino en tu runtime se rompe entero en otro. No porque el criterio sea malo: porque el criterio nunca aprendió a viajar solo.

    Eso tiene tres consecuencias que ya estamos pagando.

    La primera es que cada equipo reconstruye lo mismo. Miles de empresas escribiendo su propio agente de research, su propio agente de soporte, su propio agente de procesamiento de documentos. El mismo trabajo de ingeniería repetido porque ninguno de esos agentes se mueve de su proyecto.

    La segunda es que impide la especialización. Nadie puede dedicar dos años a construir el mejor agente de auditoría de accesibilidad del mundo y distribuirlo por muchos sistemas. Cada agente se trata como un detalle de implementación interno, no como un producto.

    Y la tercera: sin portabilidad no hay mercado. No puede existir un marketplace real si un agente solo funciona dentro del harness donde nació, ni efecto red si añadirlo beneficia a una sola aplicación.


    El acoplamiento no está donde crees

    Cuando alguien dice "muevo mi agente a otro entorno" suele pensar en copiar el prompt. El prompt es lo barato. Lo caro es todo lo que el harness le daba gratis.

    Capa Qué cambia al mover el agente
    Tool calling El esquema de las tools, sus nombres, cómo se serializan los resultados
    Contexto y memoria Qué entra en la ventana, qué se resume, dónde persiste entre turnos
    Bucle de ejecución Quién decide cuándo parar, cuántos pasos caben, quién reintenta
    Transporte stdio, HTTP con streaming, cola de mensajes
    Permisos Quién aprueba una escritura y con qué granularidad
    Reporte de progreso Logs sueltos, eventos tipados, estados de tarea

    Copiar el prompt y creer que has movido el agente es como copiar un componente de React sin llevarte el router, los tipos ni el ciclo de vida. Tienes el texto. No tienes el comportamiento.

    Por eso insisto tanto en que el harness es la pieza que de verdad define a un agente. El modelo es intercambiable. El harness, hoy, no.


    Qué resuelven MCP y A2A de la interoperabilidad de agentes de IA (y qué no)

    MCP y A2A resuelven dos capas del problema: las herramientas y la comunicación entre agentes. Ninguno de los dos toca el runtime, el contexto ni el bucle, que es donde vive el acoplamiento real. Son dos intentos serios de estandarizar esto y conviene ser honesto con el alcance de cada uno.

    MCP estandariza la capa de herramientas y el contexto que se sirve. En su revisión 2026-07-28, un servidor expone tres primitivas —tools, resources y prompts— sobre JSON-RPC 2.0, y cualquier cliente las descubre e invoca igual. Eso arregla la primera fila de la tabla y parte del transporte, y no es poco: el mismo servidor vale para clientes distintos. Si nunca has montado uno, empieza por qué es MCP exactamente.

    Lo que MCP no define es el comportamiento del agente: qué entra en su ventana de contexto, quién arranca su bucle o cuándo decide parar. Los permisos ni siquiera intenta cubrirlos —la propia spec reconoce que "MCP itself cannot enforce these security principles at the protocol level" y los delega en el host. La revisión actual se acerca por los bordes, eso sí: la extensión Tasks cubre operaciones largas con polling y handles duraderos, y el grupo de trabajo Skills over MCP quiere distribuir instrucciones de agente como recurso. Ninguna de las dos, todavía, te deja mover un agente de harness.

    A2A estandariza el intercambio entre agentes. La versión 1.0.0 define la Agent Card para descubrir capacidades, las Tasks con su ciclo de vida de ocho estados (submitted, working, input-required, auth-required, completed, failed, canceled, rejected), los Messages y los Artifacts. Eso arregla la fila del reporte y buena parte de la comunicación.

    Y el límite lo pone la especificación misma, por escrito: los agentes colaboran "without needing to share their internal thoughts, plans, or tool implementations". Ahí está la frontera, literal. A2A te deja hablar con un agente remoto; no te deja traerte ese agente a casa.

    Puestos capa por capa contra la tabla de antes, el reparto queda así:

    Capa de acoplamiento MCP 2026-07-28 A2A 1.0.0 Quién la resuelve hoy
    Tool calling Sí — MCP
    Transporte Parcial (JSON-RPC 2.0) Parcial MCP / A2A
    Reporte de progreso Parcial Sí (ciclo de vida de Task) A2A
    Contexto y memoria Parcial (resources) No Casi nadie — tu harness
    Bucle de ejecución No No Nadie — tu harness
    Permisos No (delega en el host) No Nadie — tu harness

    Los dos juntos te dan el cableado. Ninguno te da el agente portable. Si quieres la comparativa fila a fila de A2A y MCP, la tienes desarrollada en su propio post.

    Mi tesis es incómoda pero creo que es la correcta: el agente reutilizable de verdad todavía no existe, y la frontera no la marca el protocolo sino el harness. Lo que sí podemos hacer hoy es diseñar como si esa capa ya estuviera, para no tener que rehacerlo cuando llegue.


    Los tres pilares de la interoperabilidad de agentes de IA

    Un agente portable necesita tres propiedades arquitectónicas: concurrencia (se activa por eventos, no por su posición en una cadena), awareness o conciencia del entorno (lo consulta en vez de suponerlo) y adaptividad (decide con estado de runtime, no con un orden hardcodeado).

    Compartir un agente es más que mover su código. Un agente que aterriza en un entorno nuevo tiene que poder trabajar sin esperar a una secuencia predefinida, entender qué hay a su alrededor y ajustar su comportamiento a lo que encuentra.

    1. Concurrencia: fuera los pipelines secuenciales

    Casi todos los sistemas multiagente que reviso son esto:

    // Acoplado: el paso 3 no existe hasta que termina el 2.
    const spec = await specAgent.run(input);
    const code = await codeAgent.run(spec);
    const review = await reviewAgent.run(code);
    

    Esto no es un sistema de agentes. Es una función con tres llamadas caras. Que use await no lo salva: el orden está hardcodeado en el código que las invoca, así que el agente de revisión no puede existir fuera de ese fichero. Es el mismo error de fondo que hace fallar al mega-prompt cuando el sistema crece.

    La alternativa es que cada agente sea una unidad independiente que decide si un evento le incumbe:

    interface Agent {
      readonly id: string;
      readonly capabilities: readonly string[];
      // ¿Este evento va conmigo?
      accepts(event: AgentEvent): boolean;
      handle(event: AgentEvent, ctx: RuntimeContext): Promise<AgentEvent[]>;
    }
    

    Ningún agente bloquea a otro. Ninguno conoce su posición en una cadena. Cuando esto está bien hecho, a menudo descubres que no necesitas orquestador.

    2. Awareness: el entorno se consulta, no se supone

    Un agente acoplado solo conoce su prompt. Lo que hay alrededor está implícito en el orden de las llamadas.

    Un agente portable pregunta. Necesita dos cosas: un canal de eventos compartido y un registro de participantes.

    type Unsubscribe = () => void;
    
    type AgentEventType =
      | 'spec.ready'
      | 'code.changed'
      | 'review.blocked'
      | 'test.requested';
    
    interface AgentEvent {
      readonly type: AgentEventType;
      readonly source: string; // id del agente que lo emitió
      readonly payload: unknown;
      readonly at: number;
    }
    
    interface AgentDescriptor {
      readonly id: string;
      readonly capabilities: readonly string[];
    }
    
    interface Workspace {
      // Quién más está trabajando aquí y qué sabe hacer.
      participants(): readonly AgentDescriptor[];
      publish(event: AgentEvent): void;
      subscribe(handler: (event: AgentEvent) => void): Unsubscribe;
    }
    
    interface RuntimeContext {
      // Todo lo que el agente necesita del entorno donde aterriza.
      readonly workspace: Workspace;
    }
    

    La diferencia práctica: con esto, el mismo agente de revisión funciona en un entorno donde hay tres compañeros y en otro donde está solo, porque en el primer caso lo sabe. Monté el patrón completo en event bus para agentes descentralizados.

    3. Adaptividad: la decisión sale del estado, no del orden

    El tercer pilar es el que casi nadie implementa, y es el que separa un agente de un script con LLM dentro.

    async function handle(
      event: AgentEvent,
      ctx: RuntimeContext,
    ): Promise<AgentEvent[]> {
      const emit = (type: AgentEventType, payload: unknown): AgentEvent => ({
        type,
        source: 'reviewer',
        payload,
        at: Date.now(),
      });
    
      const findings = await runReview(event.payload, ctx);
      if (findings.length === 0) return [];
    
      // Si hay alguien capaz de ejecutar tests, delego. Si no, bloqueo.
      const peers = ctx.workspace.participants();
      const hasTester = peers.some((p) => p.capabilities.includes('test.run'));
    
      return hasTester
        ? [emit('test.requested', { findings })]
        : [emit('review.blocked', { findings })];
    }
    

    Fíjate en lo que no hay: ningún if (step === 'review'). La rama se decide con estado de runtime, no con una posición hardcodeada. Ese agente se comporta distinto en dos entornos distintos sin que nadie toque su código.

    Las tres juntas son las caras del mismo triángulo: independencia, conexión, colaboración. Si te falta una, el agente no viaja.

    Esta forma de pensar el sistema —el agente como unidad con contrato propio, no como paso de un flujo— es la que trabajo en el curso Construye con IA: de la idea al producto con Claude Code.


    Lo que se desbloquea cuando los agentes viajan

    Construyes un agente una vez y lo distribuyes en todas partes. Combinas especialistas en lugar de reconstruirlos. Y puedes monetizar una capacidad sin vender la aplicación entera alrededor.

    El cambio de fondo es de economía, no de ingeniería. En un ecosistema interoperable, cada agente nuevo aumenta el valor de todos los demás. Hoy cada agente nuevo aumenta el valor de exactamente un repositorio.


    Cómo diseñar hoy un agente portable en tu proyecto

    No hace falta esperar a que se asiente ningún estándar. Cinco decisiones que puedes tomar esta semana:

    1. Separa el agente de su runtime. El agente es un objeto con capacidades declaradas y un handle. Quién lo arranca y cada cuánto es responsabilidad de otro fichero.
    2. Expón sus herramientas vía MCP, aunque hoy solo lo use tu propio harness. Es la capa que ya está estandarizada; aprovéchala.
    3. Saca el contexto del prompt. Ficheros, un store, lo que sea. Si la memoria del agente vive en la cadena de mensajes de tu framework, tu agente es tu framework.
    4. No hardcodees la secuencia. Sustituye await a(); await b(); por eventos tipados. Si te cuesta imaginarlo, empieza por construir un agente de IA desde cero y verás dónde está cada costura.
    5. Escribe el contrato antes que el código. Qué acepta, qué emite, qué permisos pide, qué garantiza. Es revisión por contrato aplicada al diseño, y el mismo principio que desarrollo en Spec-Driven Development: la especificación es la parte portable; la implementación es desechable.

    Si el punto 5 te suena a burocracia, empieza por el ebook gratuito Revisión por Contrato. Va justo de eso: definir por escrito qué puede y qué no puede hacer un agente antes de dejarlo suelto en tu repo.

    En Dominicode Labs están las masterclasses y los repos donde desmonto este tipo de decisiones de arquitectura con el código delante y sin diapositivas.

    Elige hoy uno de tus agentes y responde a una sola pregunta: si mañana cambias de harness, ¿qué sobrevive? Si la respuesta es "el prompt", ya sabes por dónde empezar.


    Preguntas frecuentes

    ¿MCP no resuelve ya la interoperabilidad de agentes de IA?

    Resuelve una parte importante, no el conjunto. MCP estandariza cómo un agente descubre e invoca herramientas y cómo un servidor le sirve contexto como recurso, así que el mismo servidor vale para clientes distintos y eso elimina una de las seis capas de acoplamiento. Hay trabajo en curso para llevarlo más lejos —la extensión Tasks y el grupo de Skills over MCP—, pero a día de hoy nada de eso está cerrado. Pero un agente no es solo el conjunto de herramientas que puede llamar: es también su bucle, su gestión de contexto, su política de permisos y su forma de reportar. Nada de eso está cubierto. Puedes tener dos agentes que hablan MCP perfectamente y seguir sin poder mover ninguno de los dos al entorno del otro.

    Entonces, ¿A2A sobra?

    Al contrario: resuelve un problema distinto y complementario. A2A estandariza el intercambio entre agentes —descubrimiento de capacidades, envío de tareas, mensajes y progreso— y con eso puedes hacer que tu sistema hable con un agente que corre en otra empresa. Lo que no te da es portabilidad: sigues invocando un agente remoto que vive en su propio runtime. MCP y A2A son cableado en dos capas diferentes. El agente portable es otra discusión.

    ¿Concurrencia no es simplemente lanzar todo con Promise.all?

    No. Promise.all lanza varias llamadas a la vez, pero el punto donde se lanzan y el punto donde se espera siguen escritos en tu código: tú decides qué va junto y dónde se bloquea. Lo que pido aquí es otra cosa, desacoplamiento temporal: cada agente se activa por su cuenta cuando aparece un evento que le incumbe, sin que nadie coordine el orden desde fuera. La prueba está en si puedes añadir un agente nuevo al sistema sin tocar el fichero que orquesta. Si tienes que tocarlo, tienes llamadas concurrentes, no agentes autónomos.

    ¿No es sobreingeniería para un agente que solo uso yo?

    Depende de cuánto te haya costado ese agente. Si es un script de veinte líneas, sí, es sobreingeniería. Si le has dedicado semanas a afinar su criterio —y en revisión de código o extracción de datos eso pasa rápido— entonces lo que estás haciendo al acoplarlo es tirar ese trabajo cada vez que cambies de herramienta. Y cambiamos de herramienta cada pocos meses. En mi experiencia, separar el agente de su runtime cuesta una tarde; reescribirlo entero, varias semanas.

    Mi agente ya está acoplado al harness. ¿Por dónde empiezo?

    Por el contexto, que suele ser lo más doloroso y lo que antes se rompe. Saca de la cadena de mensajes del framework todo lo que sea conocimiento del agente y llévalo a ficheros o a un store propio. Después extrae la lógica de decisión a una función pura que recibe estado y devuelve eventos. Cuando tengas esas dos piezas, el runtime original pasa a ser un adaptador fino de treinta líneas, y escribir un segundo adaptador para otro entorno deja de dar miedo.


    Por Bezael Pérez — Developer senior con más de 15 años de experiencia y fundador de Dominicode.