Correr agentes de código en local
Un recorrido para poner un agente de código a funcionar enteramente en tu propia máquina —sin nube, sin API key, sin factura por token—. Las piezas que se ensamblan (modelo, servidor, harness), los pasos exactos para conectarlas en un portátil con Linux, los límites del hardware con los que chocas, y hasta dónde pueden empujar una GPU integrada y una NPU. Mitad tutorial, mitad lección sobre de qué están hechos de verdad estos sistemas.

IA local: un modelo en su propia caja, asomándose de tu lado del cable.
Hay una pregunta que conviene hacerse de vez en cuando sobre cualquier herramienta de la que dependes: ¿podrías ejecutarla, entera, en la máquina que tienes delante —con la red desenchufada, sin cuenta, sin que nadie te cobre por token?
Es, más o menos, la pregunta que se hizo Linus Torvalds sobre el control de versiones en 2005. Cuando el kernel de Linux perdió el acceso a la herramienta propietaria de la que venía dependiendo, escribió —en cuestión de días— un sistema en el que cada desarrollador guarda el repositorio entero en su propio disco. Distribuido por diseño, sin servidor central cuyo permiso necesites. Git fue, en el fondo, una negativa a depender de la máquina de otro.
Apunta esa misma pregunta a los agentes de código y sale un proyecto pequeño pero revelador: poner un agente a escribir código en tu propio portátil, con la nube apagada. Sebastian Raschka publicó hace poco una guía detallada para correr agentes de código en local sobre hardware potente —un Mac Mini, una caja DGX—. Lo que sigue recorre el mismo camino en un portátil deliberadamente corriente, donde las limitaciones acaban enseñando casi todo: las piezas que se ensamblan, los pasos para conectarlas, los límites de hardware con los que chocas, y hasta dónde se puede empujar la máquina para superarlos.
¿Por qué en local?
Hay cuatro razones que aparecen una y otra vez, y conviene tenerlas separadas:
- Privacidad. El código, los ficheros y los prompts no salen de la máquina —nada se envía a un tercero, se cachea ni se registra en otra parte—. Importa en el momento en que el trabajo toca algo sensible.
- Coste. Una vez descargado el modelo, ejecutarlo es gratis salvo la electricidad —sin factura por token, sin API medida, sin sorpresas cuando un loop corre toda la noche.
- Control y transparencia. Todo el stack es abierto e inspeccionable: puedes ver, y cambiar, cómo sirve, qué envía y qué guarda. Ni caja negra, ni un modelo que te deprecan por debajo.
- Sin conexión. Funciona con el cable fuera —en un tren, un avión, una cabaña en el bosque.
Y luego la razón que de verdad me metió en esto, la que no menciona ninguna lista de características: entender. Nada te enseña las piezas de una máquina como tener que ensamblarla tú mismo.
What I cannot create, I do not understand.
— Richard Feynman, escrito en su pizarra, 1988
Un portátil corriente
Antes de nada, una advertencia para que nadie llegue con las expectativas equivocadas: nada de esto significa que lo local gane hoy. Para el trabajo más difícil sigo acudiendo a los grandes modelos alojados —son mejores, y en la frontera no hay color—. Lo local se gana su sitio en las tareas y los loops donde la privacidad, el coste o el control pesan más que el último incremento de capacidad —y, en mi caso, como la vía más rápida para entender de verdad el stack—. Lee lo que sigue como cómo hacer que lo local sea genuinamente útil, no como cómo sustituir la nube.
Lo segundo que conviene dejar claro —y lo que importa si quieres reproducir esto— es que la máquina es deliberadamente corriente. No es una estación de trabajo con un rack de GPUs. Ni siquiera es un sobremesa. Es simplemente uno de mis portátiles: un Framework con Linux (Ubuntu 24.04), un AMD Ryzen AI 7 350, su gráfica integrada Radeon 860M —sin memoria de vídeo dedicada, así que toma prestada la RAM del sistema— y 32 GB de RAM compartidos por todo. Ese último número gobierna casi todas las decisiones posteriores: qué tamaño de modelo cabe, cuánto contexto puedes sostener, si acaba trabajando la GPU integrada o la CPU. Donde un paso dependa de este hardware concreto lo diré; la forma del proceso vale para cualquier portátil, pero los números —y los muros— serán los tuyos, no los míos.
Y un aviso, porque es la parte honesta de la historia: sacarle el máximo a una máquina así no se queda en un apt install. Al final acabo instalando firmware más nuevo, añadiendo mi usuario al grupo render y arrancando un kernel distinto para llegar a un chip que la configuración estándar ignora sin más. Eso no es un desvío: es el punto en que "correr IA en local" deja de ser un eslogan y se convierte en trabajo de sistemas. Si eso ya te suena a más de lo que buscas, buenas noticias: los tres primeros pasos te dan un asistente local funcionando sin nada de eso. El yak-shaving a nivel de kernel solo empieza cuando vas a por velocidad de verdad.
Las piezas que en realidad estás ensamblando
La mayoría llevamos en la cabeza un único objeto borroso etiquetado como "la IA que escribe código": entra un prompt, sale código, en medio hay magia. Lo primero que hace el montaje local es romper esa niebla en cuatro piezas separables.
Las capas que la niebla funde en una: la CLI en la que escribes, el harness que actúa, el servidor de modelos y el modelo en sí.
Abajo del todo está el modelo —los pesos, lo que razona y predice el siguiente token, y nada más—. Encima, un servidor de modelos (un runtime de inferencia como Ollama) carga esos pesos y los expone tras una API HTTP. Por encima, el harness —el agente de código— es la parte que de verdad hace cosas: lee y edita tus ficheros, ejecuta comandos y comprueba su propio trabajo. Y envolviéndolo todo, un modelo de permisos: qué puede tocar el agente.
La constatación más útil de todo el ejercicio es que el modelo no es el harness. El modelo solo predice tokens; todo lo que lo hace parecer un agente pertenece al harness que lo rodea. Rutinariamente le atribuimos al modelo trabajo que hace el harness, y le echamos la culpa al modelo por fallos que son del harness. Tenerlos separados en la cabeza es la diferencia entre usar una caja negra y entender un sistema.
Y esa separación no es solo conceptual. Cada uno de estos harness —Qwen Code, el Codex de OpenAI, Claude Code— no es más que un cliente de una API compatible con OpenAI. Así que levantas un servidor de modelos y apuntas a él el harness que prefieras, cambiando de agente sin tocar nunca el modelo de debajo.
Un servidor, N agentes: el modelo razona, el harness orquesta las herramientas; cambias uno sin tocar el otro.
Con ese mapa en la mano, el montaje son tres pasos: levantar un servidor de modelos, apuntarle un harness, y luego pelearte con tu hardware hasta que sea lo bastante rápido para ser útil.
Paso 1 — Levantar un servidor de modelos
Ollama es el sitio más sencillo para empezar: un binario, un endpoint compatible con OpenAI, y cambio de modelo fácil.
curl -fsSL https://ollama.com/install.sh | sh
ollama pull qwen3-coder:30b # MoE de código, ~18 GB
ollama serve # sirve en :11434
Una nota sobre qué modelo, porque importa más de lo que parece. qwen3-coder:30b es un modelo Mixture-of-Experts: unos 30.000 millones de parámetros en total, pero solo ~3.000 millones activos para cualquier token dado. Quédate con ese número —los parámetros activos—; vuelve en cuanto hablemos de velocidad. En cuanto el servidor está en pie, ya tienes un asistente local al que hacer preguntas sueltas por curl. Lo difícil es convertirlo en un agente.
Paso 2 — Apuntarle un harness
Instala un harness y apúntalo al endpoint local. Qwen Code lee variables de entorno al estilo OpenAI:
npm install -g @qwen-code/qwen-code@latest
# ~/.qwen/.env
OPENAI_API_KEY=ollama # cualquier cadena no vacía
OPENAI_BASE_URL=http://localhost:11434/v1
OPENAI_MODEL=qwen3-coder:30b
Después ejecuta qwen —sin asistente interactivo; el .env de arriba basta para arrancarlo headless—. Codex también funciona, apuntado al mismo servidor, pero aquí te topas con la primera fricción real, y conviene decirlo claro porque desmonta una suposición común: "compatible con OpenAI" es una promesa, no una garantía. En el Codex actual el proveedor va en su propio fichero de perfil; el antiguo wire_api = "chat" tiene que pasar a "responses"; el id ollama está reservado, así que renombras el tuyo; y como qwen3-coder no expone modo "thinking", debes poner model_reasoning_effort = "none" o toda petición falla. Este es el perfil que acabó funcionando, en ~/.codex/ollama.config.toml:
model = "qwen3-coder:30b"
model_provider = "ollamavk"
model_reasoning_effort = "none"
[model_providers.ollamavk]
name = "Ollama"
base_url = "http://localhost:11434/v1"
wire_api = "responses"
Se lanza con:
codex --profile ollama
Nada de esto es difícil; todo está lo bastante poco documentado como para costarte media hora —y esa media hora es parte de la lección: las capas son reales, y todavía no acaban de ponerse de acuerdo entre ellas.
En este punto tienes un asistente de código local funcionando. Que sea un agente usable es enteramente una cuestión de velocidad —y ahí es donde el hardware empieza a empujar.
Paso 3 — Entender por qué va lento (prefill vs decode)
Un modelo generando texto hace dos trabajos muy distintos, y un portátil suele ser bueno en uno y malo en el otro.
Dos fases, cuellos de botella opuestos: el prefill lee todo el prompt de golpe (compute-bound); el decode emite un token cada vez (bandwidth-bound).
El primero es el prefill: el modelo ingiere el prompt entero de golpe —tus ficheros, el historial, las instrucciones del harness— en una gran pasada paralela. Es compute-bound, limitado por el rendimiento aritmético bruto; aquí brilla una GPU. El segundo es el decode: produce la respuesta token a token, y cada token nuevo debe leer los pesos activos del modelo desde la memoria. Es bandwidth-bound, limitado por lo rápido que muevas bytes, no por cuántas operaciones hagas.
Esta distinción es la clave para un agente, porque un harness de código reenvía un system-prompt grande más un historial creciente en cada turno. Domina el prefill. Y en una GPU integrada que comparte el bus de memoria con la CPU, la aceleración ayuda en la fase que menos esperarías: en este portátil, encender el backend Vulkan de Ollama casi triplicó el prefill y apenas movió el decode —porque el cuello del decode, el ancho de banda de memoria, es el mismo tanto si lee la CPU como la GPU integrada—. Beben por la misma pajita.
Activarlo en este hardware pide dos flags, no una —Ollama detecta la GPU integrada y la descarta a propósito salvo que insistas—:
OLLAMA_VULKAN=1 OLLAMA_IGPU_ENABLE=1 ollama serve
# palancas de memoria para más contexto:
# OLLAMA_CONTEXT_LENGTH=16384 (4096 es muy poco)
# OLLAMA_FLASH_ATTENTION=1
# OLLAMA_KV_CACHE_TYPE=q8_0 (~mitad del KV-cache)
Una medición dejó concreto lo de los parámetros activos. Un modelo denso de 14.000 millones —lo bastante pequeño para caber entero en la GPU integrada— decodificó más lento que el MoE de 30.000 millones, porque un denso lee sus 14B por token mientras que el MoE lee solo sus ~3B activos. En hardware limitado por ancho de banda, la velocidad de decode la gobiernan los parámetros activos, no el tamaño total. La regla contraintuitiva para un portátil: tira de un MoE grande con pocos parámetros activos, no de un denso pequeño.
El muro, y cómo elegir modelo a su alrededor
Entonces chocas con el muro. Un agente quiere una ventana de contexto grande —de 16 a 32 mil tokens—. Una ventana grande necesita un KV-cache grande, y ese caché más los pesos tienen que caber en la porción de memoria que la GPU integrada puede direccionar (unos 16 GB aquí). Sube la ventana y el conjunto se sale de esa porción, se desploma de vuelta a la CPU y casi agota la RAM del sistema. En esta máquina no puedes tener a la vez contexto grande y aceleración por GPU.
Eso monta una pinza cuando intentas ejecutar una tarea de verdad:
- El modelo pequeño de 8B cabe de sobra en un contexto grande —pero no es lo bastante listo, y alucina en cualquier cosa de varios pasos.
- El MoE capaz de 35B razona bien —pero un fichero grande más el propio system-prompt del harness (~16k tokens) desborda la ventana de 32k antes de que pueda actuar.
Demasiado pequeño para pensar; demasiado grande para caber. La solución práctica es el tercer miembro de una tríada a la que volveremos: mantén la tarea pequeña —ficheros acotados, un harness lo bastante ligero— para que un modelo capaz tenga sitio donde trabajar.
Paso 4 (ir más allá) — Llevarlo a la NPU
En CPU y GPU integrada, el veredicto honesto es que los loops de agente autónomos son impracticables: solo el system-prompt del harness puede tardar minutos en prefillear, y una tarea trivial da timeout antes de que el modelo actúe una sola vez. El muro del prefill pide hardware de verdad. En un chip AMD Ryzen AI ese hardware ya está en el portátil: la NPU (su motor neuronal XDNA), construida precisamente para el trabajo compute-bound que es el prefill.
Llegar hasta ella, en Linux hoy, es la parte menos "enchufar y listo" de todo esto —y conviene detallarla, porque es tanto un aviso como una receta—. Usar FastFlowLM (un runtime pensado para NPU) supuso, por orden: arrancar un kernel HWE más nuevo (el 7.0) que trae el driver nativo amdxdna; instalar un firmware lo bastante reciente para él (≥1.1, sacado del repositorio del proyecto —el de serie era demasiado viejo—); el userspace de XRT; subir el límite de memoria bloqueada (memlock) a ilimitado; añadir mi usuario al grupo render para que pudiera abrir el dispositivo de la NPU; y, como Secure Boot estaba activo, inscribir a mano una clave de firma (un MOK) en el siguiente arranque. Dos reinicios en total. Muy lejos de un ollama pull.
Pero funciona, y el resultado no es sutil. FastFlowLM expone el mismo tipo de endpoint compatible con OpenAI, así que el harness no nota la diferencia:
flm serve qwen3:8b --pmode turbo # en la NPU
| Backend | Prefill | Mismo prompt de 5.978 tokens |
|---|---|---|
| CPU (Zen 5) | ≲11 tok/s | > 560 s (dio timeout) |
| Vulkan · GPU integrada | 32 tok/s | 184 s |
| NPU (XDNA) | ~390 tok/s | 15 s |
Unas 35× la CPU y 12× la GPU integrada, en prefill —justo el cuello que hacía impracticables a los agentes—. Un prompt que antes daba timeout se digiere en quince segundos. El decode se queda en unos 11 tokens por segundo, sin cambios, porque es bandwidth-bound y la NPU no cambia el bus de memoria —pero el decode nunca fue el problema—. Alimentado con un turno de agente realista —system-prompt, esquemas de herramientas, una tarea, cerca de diez mil tokens—, el modelo lo prefillea en segundos y emite una llamada a herramienta correcta. El loop ha pasado de imposible a posible.
Qué puede tocar el agente
La cuarta pieza —el modelo de permisos— es la que da pereza y la que muerde. Un harness de código no es un chatbot: lee y edita tus ficheros y ejecuta comandos de shell. Ese es todo el sentido, y también todo el riesgo, así que hay dos cosas que conviene dejar zanjadas antes de dejarlo correr solo.
Primero, saber qué sale de la máquina. Los harness abiertos (Qwen Code, Codex) se pueden leer de verdad —puedes ver qué envían— y resulta que algunos llaman a casa por defecto: Qwen Code manda telemetría de uso a los servidores de su fabricante salvo que digas lo contrario. Un fichero lo zanja:
// ~/.qwen/settings.json
{
"privacy": { "usageStatisticsEnabled": false },
"telemetry": { "enabled": false, "logPrompts": false }
}
Segundo, decidir dónde debe parar. Cuando un agente corre sin supervisión, la línea que importa está entre lo reversible y lo irreversible. La regla para la prueba de abajo es exactamente esa: el agente puede hacer todo hasta abrir un pull request —rama, edición, commit, git push, todo deshacible— pero el merge se queda con un humano, porque en nuestro repositorio un merge a main despliega a producción automáticamente. Esa frontera es la diferencia entre un loop autónomo que puedes usar y uno al que no puedes dar la espalda.
El resultado: un recorrido real de principio a fin
Con esa frontera puesta, la prueba que merece la pena es el loop completo: dejar que el agente local edite documentación y abra un pull request por su cuenta —todo hasta el PR, nada más allá.
La pinza de antes decide el desenlace. Apunta el modelo capaz de 35B a un fichero de docs grande y razona bien, pero desborda la ventana antes de poder actuar. Apúntalo a un fichero pequeño —y prohíbele abrir los grandes— y cierra el loop, verificado en git y no narrado: un working tree real, una rama real empujada a origin, un commit real de una línea afinando una frase, y un pull request real dejado abierto, esperando a un humano, exactamente donde se le dijo que parara.
El resultado en git y no en prosa: un único pull request abierto —el #7807— que abrió el agente local, entre más de mil que no abrió.
Un commit en su propia rama, con la descripción escrita por el propio modelo ("Generated with Qwen Code"), parado en "Review required" —el merge queda para un humano, por diseño.
Así que la receta para un agente de código local que de verdad cierra el loop es una tríada: prefill rápido (aquí, la NPU), un modelo capaz (un MoE lo bastante grande) y un presupuesto de contexto que quepa (un harness ligero y tareas acotadas). Falla una y se rompe. Lo que también significa que el "muro" nunca fue absoluto —era un presupuesto—, y con la pieza correcta en cada hueco, lo superas.
| Configuración | Loop de agente |
|---|---|
| CPU / GPU integrada · 30B | imposible — un simple echo dio timeout a los 3 min |
| NPU · 8B | rápido, pero alucinó la tarea de varios pasos |
| NPU · 35B MoE · fichero grande | rápido y correcto, pero desbordó la ventana de contexto |
| NPU · 35B MoE · fichero pequeño | ✅ un pull request real, de principio a fin |
Para qué hacer todo esto
Nada del aprendizaje de verdad estaba en los ficheros de configuración. Estaba en ver cómo una idea borrosa se descomponía en un modelo, un servidor, un harness y una frontera de permisos —y en descubrir que a estos sistemas no les especificas lo que pueden hacer, sino que lo averiguas, evaluándolos—. Esa es la habilidad transferible: ver el sistema por capas, y saber comprobar si de verdad funciona.
Hay además un punto más callado bajo los benchmarks. Durante una década, más o menos, los desarrolladores dejaron de pensar en las prestaciones de sus máquinas —todo iba lo bastante rápido, y las limitaciones se habían mudado a la red—. Correr un modelo en local devuelve las viejas preguntas a la mesa: cuánta memoria, cuánto ancho de banda, qué acelerador. Mucha gente está redescubriendo, con cierta sorpresa, que esto importa otra vez —y que su frontera, en Linux, sigue siendo territorio de pioneros, donde el premio por el yak-shaving es un ×35 y el precio son dos reinicios.
En un texto anterior sostuve que un modelo es una especie de disco de vinilo —un objeto que puedes poseer pero no leer—. Este recorrido es el mismo gesto una capa más arriba: coger eso que todos tratan como una niebla única y nombrar sus partes hasta que se convierte en una máquina sobre la que razonar. Corre más lento que la nube, y más tosco, y por ahora solo cierra el loop en tareas pequeñas. Pero corre en el ordenador que tengo delante, con un cable que podría arrancar de la pared —y, como Torvalds llevando el control de versiones al disco de cada desarrollador, resulta ser muy buen sitio para empezar a entender algo.