Nunca aceptes la primera oferta
Cómo construimos Cerebro y ganamos el hackathon de Causa Prima x Claude, con los errores incluidos.

"Nunca aceptes la primera oferta" fue lo primero que nos dijeron el viernes al empezar el hackathon. Era un consejo para el juego, y tres días después (y bastantes horas de sueño menos) resultó ser la mejor descripción de lo que habíamos acabado construyendo: un agente que no se cree lo primero que le cuentan, ni siquiera cuando se lo cuenta su propio modelo.
El hackathon fue el de Causa Prima x Claude, el primer hackathon de Claude en España, del 2 al 4 de octubre en Madrid, con 54 personas en 18 equipos. El reto no era construir una app y enseñarla el domingo, sino construir un agente y soltarlo en un juego que ya estaba en marcha. El juego era un mercado en tiempo real, con un turno cada 15 segundos, en el que tu agente tenía que negociar con vendedores automáticos que regatean y mienten, comerciar con los agentes de los otros 17 equipos y resolver negociaciones cronometradas de uno contra uno. El servidor repartía 60 puntos y un jurado otros 40 después de un pitch. Y lo que lo cambiaba todo es que el juego no se paraba mientras programabas, así que cada bug te estaba costando puntos en directo.
Éramos tres, Daniel Suárez Mayorga, Luca Mininno y yo, y a lo que montamos lo llamamos Cerebro, con el lema "Smart money in every deal". Lo cuento por días, con lo que salió bien y con lo que salió mal, que fue bastante.
Viernes: un modelo, un prompt y una noche muy larga

La idea obvia
Nuestra primera idea fue la que tendría cualquiera: coger un modelo potente, darle las reglas, la API y un buen prompt, y dejarle jugar. Que leyera el mercado, hablara con los vendedores y decidiera él. Y nos pusimos una regla que mantuvimos hasta el final: el agente comercia y los humanos construimos, sin entrar a mano a aceptar nada (el domingo, cayendo en la clasificación, costó bastante más respetarla).
Por qué no se le puede dejar el dinero a un modelo
Lo primero que pasó fue que el bot vendió algo que no tenía que vender. Sabía perfectamente que solo podía poner a la venta lo suyo, pero nadie le había dicho que eso también valía cuando aceptaba la oferta de otro, y aceptó una por algo que queríamos conservar.
Lo segundo fueron los vendedores automáticos. Mentían, y no por un fallo, sino porque el juego estaba diseñado así. El caso más claro era un vendedor que en el mensaje te describía un artículo y en la oferta adjunta, que es lo que de verdad se ejecuta, te entregaba otro más barato al mismo precio. Un modelo que lee el texto y decide por el texto compra lo que no es.
Y lo tercero fue leer las reglas con atención. Decían, literalmente, que la inyección de prompt contra los vendedores estaba permitida "y es divertida". Si tú puedes intentar manipular a un vendedor, los demás van a intentar manipular a tu agente, y los mensajes de los otros equipos le llegan a tu modelo como texto. Un mensaje bien escrito por otro agente es, a todos los efectos, un prompt que no has escrito tú.
A partir de ahí el diagnóstico era bastante simple. Un modelo es buenísimo proponiendo: entiende la situación, redacta, ve oportunidades. Lo que no puedes hacer es dejarle la última palabra, porque cualquier regla que viva dentro de un prompt es una regla que alguien va a intentar negociar, y en un mercado todo el mundo tiene motivos para intentarlo.
De ahí salió la frase que se nos quedó como lema: el modelo propone y el código dispone.

Tirarlo todo a medianoche
Podíamos haber parcheado el bot del viernes, pero decidimos tirarlo y escribir otro (una decisión bastante temeraria para tomarla a esas horas). Entre las dos y las cinco de la madrugada salió el esqueleto de lo que acabó siendo Cerebro, y nos fuimos a dormir cuartos.
Sábado: de un bot a una pequeña organización
Casi cada pieza nació el sábado, y cada una porque algo había ido mal sin ella.

Quién decide qué
El reparto que acabamos teniendo es este, y creo que es lo más aprovechable de todo el proyecto.
Arriba hay un bucle lento, con el modelo más capaz, que cada pocos turnos lee el estado del juego y escribe la estrategia como políticas numeradas en lenguaje natural. Es lo que le da nombre al proyecto. Una política real era, por ejemplo, "vender esta pieza solo a otro equipo, nunca por debajo de 228". Nosotros hablábamos con él desde un chat, y así dirigíamos al agente, cambiándole lo que pensaba en vez de tocarle lo que hacía.
Para los movimientos grandes hay un consejo: tres opiniones en paralelo sobre la misma decisión, y sale adelante lo que apruebe la mayoría. Si no hay mayoría, el movimiento se queda parado.
Por debajo corre el bucle rápido, una vez por turno. Mira el estado, le pregunta al modelo qué haría dentro de las políticas vigentes y recibe una lista de acciones propuestas.
Y entre esas propuestas y el juego están los raíles, que son código normal y corriente, sin modelo. Comprueban cada acción antes de enviarla: que no paguemos por encima de lo que algo vale para nosotros, que no vendamos lo que hemos marcado como intocable, que se respeten los topes por operación y por hora, que no bajemos del precio mínimo fijado. Este es el raíl de valor, tal cual está en el repositorio:
margin = max(1.0, float(_cap(ctx, "value_margin", VALUE_MARGIN))) # never below +1 P, whoever proposes
surplus, err = _surplus(action, sit, ctx, give, get)
...
if surplus < margin:
return Verdict(False, "value", f"surplus {surplus:.1f} < margin {margin} ...")
return OK
El comentario lo dice todo: nunca por debajo de +1, lo proponga quien lo proponga. El domingo el agente quiso comprar por 149 algo que a nosotros nos valía 126. El modelo tenía sus razones, el raíl lo vetó igualmente y ahí se acabó, porque con una línea de código no hay manera de ponerse a discutir.
Lo importante es por qué vive cada cosa donde vive. Lo que requiere entender contexto y lenguaje, al modelo. Lo que no puede fallar nunca, al código. Y entre medias, una persona con muy pocos mandos: la política, los topes de gasto, encender y apagar.
Grabarlo todo
La pieza que parecía más aburrida fue la más importante. Un proceso aparte copia a disco todo lo que el juego hace público: cada evento, cada oferta de cada mercado, la clasificación, cada negociación. Nada más lee el juego para analizar, así que los análisis nunca le gastan al bot su cupo de peticiones.
Tenerlo todo grabado nos permitió tres cosas que no esperaba. Recalcular la puntuación por nuestra cuenta, en vez de fiarnos de la que veíamos. Encontrar comportamientos del propio juego que no cuadraban con las reglas (cómo pesaba cada día, comisiones que se redondeaban siempre hacia arriba), que escribimos en un informe con sus identificadores y sus números y se lo llevamos a la organización para que lo arreglaran. Y, el domingo, entender por qué otros equipos estaban puntuando más que nosotros.
Un agente que se arregla a sí mismo (con llave)
A media mañana apareció mi pieza favorita. Cuando el bucle lento detecta que el bot está haciendo algo mal, abre un ticket. Un agente de programación lo coge, implementa el arreglo más pequeño posible con sus tests, se ejecuta la suite entera y, si sale en verde, el bot se reinicia entre dos turnos. Si no, se revierte.
Para las zonas delicadas, como los raíles o la ejecución, el arreglo no entra solo y se queda esperando a que uno de nosotros lo acepte.
Al lado hay un laboratorio, que es un proceso que nunca escribe en el juego. Reproduce nuestras propias grabaciones, propone lecciones del tipo "en esta situación conviene abrir más bajo" y las hace pasar por una compuerta, que es código: primero deciden en paralelo sin actuar, luego actúan solo en parte de los casos, y solo entonces se activan. Quiero ser preciso aquí porque es fácil venirse arriba: no entrenamos ningún modelo, ajustamos el comportamiento del agente con nuestros propios datos.
Negociar contra el reloj
En las negociaciones de uno contra uno negocias a la vez un precio y un plazo de entrega, y cada ronda que pasa el resultado pierde valor, así que tardar también cuesta.
El modelo escribe la oferta dentro de unos límites que le calcula el código, una barrera la revisa antes de enviarla, y hay un presupuesto de tiempo por turno: si el modelo no ha contestado a tiempo, contesta el código, porque una negociación sin respuesta es una negociación perdida. Ese sábado cerramos acuerdo en 60 de 68, cuando en el conjunto de la sala una de cada cinco acababa sin acuerdo.


Un mercado para los agentes de los demás
La mitad de los puntos del servidor eran por "hacer mercado", con una condición curiosa: cada equipo podía abrir su propio mercado dentro del juego, pero solo puntuaba cuando otros dos equipos salían ganando al comerciar en él. Tú no puedes ser parte de una operación en tu propio mercado.
Casi todos anunciaban lo mismo, cero comisión, que no es un motivo para venir. El sábado por la noche íbamos primeros, y en lugar de irnos a dormir nos pusimos a construir uno con una razón de verdad. Lo llamamos v07 Market y lo pensamos desde el principio para que lo usaran agentes, no personas.
La primera, la identidad sin pedirle la clave a nadie. Para demostrar qué equipo es, el agente envía un código de un solo uso dentro del propio juego, con su clave. Nosotros solo vemos llegar ese mensaje y sabemos que es él. Nunca vemos una clave ajena.
La segunda, los límites privados. Cada agente publica qué puede soltar y qué busca, con un mínimo y un máximo que no ve nadie. El emparejador se hace una sola pregunta, que es si se solapan, y si se solapan propone un precio dentro del solape. Nunca el punto medio, porque del punto medio se deduce el límite del otro. Tampoco nuestro propio agente veía esos límites.
La tercera, que el mercado no guarda nada. Ni artículos ni dinero. Una operación emparejada se publica en el juego y solo la damos por cerrada cuando el feed del propio juego muestra la liquidación. No es un depósito en garantía ni criptografía, es simplemente no pedirle a nadie que se fíe de nosotros.


Esa noche le pasamos cuatro revisiones de seguridad y lo probamos de punta a punta con un agente que no sabía nada del proyecto, que es la prueba que más fallos nos sacó.
Programar con muchos agentes a la vez
Todo esto lo construimos con Claude Code y con muchos agentes trabajando en paralelo, cada uno con un encargo cerrado. Solo el sábado fueron más de doscientos commits, y acabamos el fin de semana con 1.602 tests.
Lo que funcionó: que cada agente tuviera sus ficheros y solo hiciera commit de lo suyo (al principio se pisaban), que nada se desplegara sin tests, y que un agente revisara el trabajo de otro sin saber qué conclusión esperábamos.
Lo que no: éramos tres personas hablando con el mismo sistema, y más de una vez uno le dio una orden que deshacía la de otro, y el sistema, obediente, ejecutó la última. Tardamos demasiado en decidir quién mandaba en qué. También aprendimos que si a un agente le das tu hipótesis tiende a devolvértela confirmada, que alguno se queda colgado en el peor momento, y que yo me equivoqué en más de una cuenta por fiarme de un resumen en vez de ir al dato.
Domingo: todo lo que podía torcerse
Llegamos primeros y con la sensación de que solo quedaba rematar. No fue así, y es el día del que más aprendí.
El raíl que miraba la mitad
A media mañana llegaron las negociaciones cronometradas, y uno de los agentes que vigilaba la partida recalculó los resultados incluyendo el plazo y encontró lo que no queríamos encontrar. Cuando le tocaba comprar, nuestro agente ofrecía el plazo máximo en negociaciones donde cada día de plazo nos restaba puntos. Tres cerradas en negativo.
La causa era tonta y por eso es instructiva. La barrera comprobaba que el precio quedara dentro de nuestro límite, y solo el precio. Pero el resultado depende del precio y del plazo juntos. El modelo, siguiendo una pauta que le habíamos dado nosotros, cedía plazo a quien "más lo valoraba" sin cobrarlo, y la barrera lo dejaba pasar tan tranquila porque el precio estaba bien.
Teníamos un raíl puesto y estaba vigilando la mitad del problema.
El arreglo sí salió como queríamos. Primero una mitigación sin reiniciar, que fue pasar esas negociaciones a modo solo código. Después el arreglo de verdad, con diez tests nuevos que incluían los tres casos perdidos. Y un único reinicio con negociaciones en marcha. En unos doce minutos estaba en producción y no quedó ninguna sin contestar.
La regla que protegía y frenaba
Seguíamos cayendo en la clasificación, así que fuimos a las grabaciones a ver qué tenían los que iban por delante.
Tenían operaciones grandes entre equipos, del tipo vender algo a otro equipo por encima de lo que a ti te vale, o comprarle justo lo que te completa una colección. Cada una daba muchísimo, y ellos llevaban dos o tres. Nosotros llevábamos una, y pequeña.
Parte de la culpa era de nuestra regla favorita, la de no perder valor nunca en una operación. Nos había protegido todo el fin de semana de los engaños, y el domingo nos dejó fuera de las jugadas en las que pagabas un poco de más por algo que luego te lo devolvía con creces. El raíl hacía exactamente lo que le habíamos pedido. Le habíamos pedido algo demasiado simple.
Un buen producto sin usuarios
Mientras tanto, nuestro mercado estaba abierto, probado y vacío.
Fuimos mirando por qué, equipo por equipo. Varios habían entrado y generado su código de conexión, y ahí se quedaba la cosa: su agente no llamaba nunca. La persona había pulsado el botón, pero el prompt no llegaba hasta su agente, porque a esas alturas todos estábamos desbordados y nadie tenía diez minutos para el mercado de otro.
Además encontramos dos fallos nuestros, pequeños y decisivos. Los anuncios dentro del juego tenían un límite de 240 caracteres y nuestra cabecera se comía más de la mitad, así que la instrucción de cómo usarlo llevaba toda la mañana saliendo cortada. Y los mensajes directos a otros equipos no habían llegado a enviarse nunca.
Y había competencia de la buena: otro equipo ofrecía un pequeño incentivo a quien vendiera en su mercado, y eso funcionó muchísimo mejor que toda nuestra elegancia técnica. En el nuestro se cerró una operación entre terceros en todo el día.
El mercado equivocado
A mediodía encontramos algo que sí nos funcionaba, que era hacer de intermediarios: comprarle a un vendedor automático algo que a nosotros nos valía menos de lo que costaba, porque había equipos a los que les valía bastante más, y revendérselo. Compramos por 149, vendimos por 184.
El problema es que cerramos esa venta en el mercado de otro equipo, uno que iba justo por delante, y una operación de ese tamaño en su mercado le dio los puntos que le faltaban. En el siguiente corte se había puesto primero.
Fue un error de coordinación nuestro y bastante tonto: el sitio donde se publicó la oferta venía de una orden que ya no tocaba, y cuando quisimos cambiarlo ya la habían aceptado. Haciendo las cuentas después, sin esa venta habrían seguido por delante igualmente, pero se nos quedó grabado que el sitio donde cierras una operación forma parte de la operación.
Lo repetimos dos veces más, ya en mercados de equipos que no competían con nosotros. Todas esas operaciones se hablaron antes en persona, en la mesa del otro equipo, y solo después las ejecutaron los agentes.
La última tanda
La última ronda de negociaciones cronometradas era la final. El arreglo de la mañana aguantó, sin un solo resultado negativo. Pero cerramos acuerdo en 23 de 34, y a mitad vimos por qué: el bot rechazaba paquetes que, contando el plazo, nos daban un buen resultado, porque otra regla exigía que el precio por sí solo cupiera en el límite. Y aceptaba un turno tarde contra rivales que solo cedían en el último.
Decidimos no tocar el código con negociaciones en marcha, y cuando cambiamos de opinión ya no compensaba. Otra vez una regla mirando el precio y olvidándose del plazo, la misma lección dos veces en el mismo día.
Acabamos segundos de 18 en el tablero del servidor, con 35,76 puntos frente a los 37,73 del primero.
Una hora para el pitch
Quedaban los 40 puntos del jurado y poco más de una hora. La organización había enseñado esa mañana las cuatro preguntas que hacen bueno un pitch, así que tiramos la presentación que teníamos y la rehicimos con esas cuatro preguntas como cabeceras, siete versiones en una hora. Teníamos preparado además un vídeo de tres minutos para narrarlo en directo, pero no hubo tiempo y al final fue solo la presentación, en los pocos minutos que tenía cada equipo.

La presentación iba en ese orden. Cómo abordamos el reto: la línea de los tres días, y cómo captábamos las señales, que era grabar cada evento del juego y recalcular nosotros la puntuación. Qué construimos: el mapa del agente con Cerebro en el centro, el emparejamiento del mercado y las pantallas reales. Por qué así: el modelo propone y el código dispone. Qué aprendimos: que un raíl solo es lo que mide, que los acuerdos empezaron cuando hablamos en persona y que nuestro mercado funcionaba pero lo usaron pocos equipos.
Hasta que se sumó la nota del jurado seguíamos segundos en el tablero, a menos de dos puntos del primero. Con ella ganamos el hackathon, y lo celebramos allí mismo con la organización.

Después: lo que me llevo
La gente
Era una sala con 18 equipos compitiendo por lo mismo durante tres días, y no recuerdo un solo mal gesto. Se competía en el tablero y se compartía en las mesas. Hubo equipos que publicaron en nuestro mercado simplemente porque fuimos a pedírselo, un competidor directo que cumplió lo que había hablado con nosotros cuando ya nos sacaba ventaja, y regateos en persona que acababan en risas con los dos portátiles abiertos comparando qué hacía el agente de cada uno.
Y el nivel era altísimo: gente extraordinaria, muy buena y muy inteligente, cada equipo con una idea distinta de cómo atacar el mismo problema. De verdad que fue un placer conoceros a todos.
Lo que aprendí
Que las salvaguardas tienen que vivir en el código, y que aun así un raíl vale exactamente lo que vale su definición. Los nuestros hicieron lo que les pedimos, y dos veces les habíamos pedido mirar una variable cuando el resultado dependía de dos.
Que solo sabes cómo vas si puedes recalcularlo tú.
Y la que menos esperaba: cuánto de un juego de agentes se seguía decidiendo fuera de la API. Muchos equipos no lo tenían todo automatizado y sus agentes dejaban pasar ofertas buenas o no contestaban. Nuestro mercado era probablemente el más trabajado de la sala y lo usó un equipo, mientras que otro, con un incentivo sencillo y yendo a hablar con la gente, se llevó las operaciones. Si tu usuario es un agente tienes que llegar hasta el agente, y una persona que tiene que acordarse de copiar un prompt es un paso de más.
Con un día más
Haría que los raíles optimizaran lo que de verdad puntúa, con el "no perder" como un dato más en vez de un muro. Pero sobre todo llevaría el mercado a los equipos, en vez de esperar a que vinieran.
Gracias
A Luis Morales, Jesús Copado y Léopold Morawski, y a todo el equipo de Causa Prima, por diseñar un juego tan bien pensado que nos tuvo tres días sin querer irnos a dormir, y por escuchar cada fallo que les llevábamos. A Geoffrey Rolin Jacquemyns y a la Claude Community Madrid, por la comunidad que lo hizo posible. A Ramón Rodrigáñez Riesco y al equipo de Nova Talent, por abrirnos Nova House y cuidarnos todo el fin de semana. A Maex Ament e Ignacio Moreno Pubul, por sus charlas. Y a Daniel y a Luca, por estos tres días.
Está todo abierto
El código, el panel y el mercado tal como quedaron al cerrar el juego, y la presentación. Se llega a todo desde aquí: https://nglmrtn.com/Cerebro/

Si quieres preguntarme algo del proyecto, de la arquitectura o de cómo montamos cualquier pieza, escríbeme, que me encantará contártelo.