Lección 08 · Concurrencia

Las tareas corren solas; el agent no se bloquea

«Fire and forget — the agent doesn't block while the command runs.»

⏱ ~10 min · 📝 3 widgets interactivos · 🧑‍💻 Basado en shareAI-lab · s08_background_tasks.py

El dolor de las llamadas síncronas

La herramienta bash de s02 es síncrona: subprocess.run(..., timeout=120). Si el comando tarda 90 segundos como npm install, todo el agent loop se detiene esos 90 segundos. El usuario mira la terminal sin saber si el proceso está colgado o simplemente trabajando.

La solución de s08: darle al agent una herramienta background_run. Devuelve de inmediato un task_id y el comando se ejecuta en otro hilo. El agent continúa el bucle y hace otras cosas; cuando el proceso termina, mete el resultado en una notification queue.

def run(self, command: str) -> str:
    task_id = str(uuid.uuid4())[:8]
    self.tasks[task_id] = {"status":"running", ...}
    thread = threading.Thread(target=self._execute, args=(task_id, command), daemon=True)
    thread.start()
    return f"Background task {task_id} started"   # devuelve inmediatamente

¿Cómo vuelve el resultado al agent?

La clave es una queue thread-safe: el hilo en background hace append al terminar; el hilo principal drena la queue antes de cada llamada LLM y mete las notificaciones como mensajes de usuario.

def agent_loop(messages):
    while True:
        # Drain bg notifications before each LLM call
        notifs = BG.drain_notifications()
        if notifs:
            messages.append({
                "role": "user",
                "content": f"<background-results>{notif_text}</background-results>",
            })
        response = client.messages.create(...)
        ...

Así, si el agent lanza una tarea en background en el turno N y esta termina durante el turno N+3, la próxima llamada LLM incluirá automáticamente los resultados — el modelo ve el bloque <background-results> y sabe que «aquella tarea terminó, puedo continuar».

Demostración de la línea de tiempo

El widget siguiente simula el proceso: el hilo principal hace tick cada segundo (simula el ritmo del bucle); puedes lanzar tareas en background en cualquier momento. Observa cómo las dos líneas se encuentran en el «punto de drain».

¿Qué comandos vale la pena lanzar en background?

No todos los comandos deben ir al background. Dos criterios:

  1. Duración: los que tardan pocos segundos es más simple ejecutarlos síncronamente; el overhead de mantener la queue no compensa esperar 0,1 s.
  2. Dependencia inmediata del resultado: si el siguiente paso necesita ese resultado de inmediato (por ejemplo, cat file.txt seguido de un grep), ponerlo en background no tiene sentido — igual tienes que esperar.
Interactivo

Widget 1 · Timeline · hilo principal + 2 hilos en background

Pulsa Spawn para asignar trabajo a un hilo en background. El hilo principal comprueba la notification queue en cada tick. Observa cómo se entrelazan los tres swim lanes.

🧠 Main (agent loop)
⚙ Background thread A
(idle)
⚙ Background thread B
(idle)
queue: []
Interactivo

Widget 2 · Fire & Forget · 8 comandos, ¿cuáles merecen ir al background?

Para cada comando elige foreground (síncrono) o background (asíncrono). Considera los dos ejes: «¿cuánto tarda?» y «¿necesito el resultado inmediatamente?»

Correctas 0 / 8
Interactivo

Widget 3 · Drain Timing · ¿en qué turno verá el agent el resultado bg?

Regla clave: el hilo principal drena la queue antes de cada llamada LLM. Dado cada escenario, responde en qué turno entra el resultado en el contexto del agent.

Correctas 0 / 5