Aula 08 · Concorrência

Deixe as tarefas rodarem sozinhas — o agent não bloqueia

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

⏱ ~10 min · 📝 3 componentes interativos · 🧑‍💻 Baseado em shareAI-lab · s08_background_tasks.py

A dor das chamadas bloqueantes

A ferramenta bash do S02 é síncrona: subprocess.run(..., timeout=120) — se o comando for npm install (uns 90 segundos), o loop inteiro do agent trava por 90 segundos. O usuário fica encarando o terminal sem saber se travou ou está trabalhando.

A solução do s08: dar ao agent uma ferramenta background_run. Ela retorna imediatamente um task_id, e o comando roda em outra thread. O agent continua seu loop e faz outras coisas; quando a bg task termina, empurra o resultado para uma fila de notificações.

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"   # retorno imediato

Como o resultado volta para o agent?

A chave é uma fila thread-safe: a bg thread insere na fila quando termina; a thread principal drena a fila antes de cada chamada à LLM e injeta as notificações de conclusão nas messages como role user.

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(...)
        ...

Assim: na rodada N o agent faz spawn de uma bg task; na rodada N+3 essa task termina; na próxima chamada à LLM, o resultado é automaticamente incluído — o modelo vê o bloco <background-results> e entende: "ah, aquela tarefa terminou, posso continuar".

Demonstração da linha do tempo

O widget abaixo simula: a thread principal faz tick uma vez por segundo (cadência do agent loop); você pode fazer spawn de bg tasks a qualquer momento. Observe como as duas linhas se encontram no "ponto de drain".

Quais comandos valem ser mandados para o background?

Nem todo comando deve ir para o background. Dois critérios:

  1. Duração: comandos de poucos segundos são mais simples de rodar de forma síncrona — manter a fila não compensa.
  2. Dependência do resultado: se o próximo passo usa imediatamente o resultado (por exemplo, cat file.txt seguido de grep), background não faz sentido — você vai ter que esperar de qualquer jeito.
Interativo

Widget 1 · Timeline · thread principal + 2 threads bg

Clique em Spawn para mandar trabalho para uma thread bg. A thread principal checa a fila de notificações a cada tick. Veja como as três swim lanes se entrelaçam.

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

Widget 2 · Fire & Forget · 8 comandos: foreground ou background?

Para cada comando, escolha entre foreground (aguarda síncronamente) ou background (spawn assíncrono). Considere as dimensões "vale a espera" + "duração".

Acertos: 0 / 8
Interativo

Widget 3 · Drain Timing · em qual rodada a notificação é vista

Regra central: a thread principal drena a fila antes de cada chamada à LLM. Cinco cenários — responda em qual rodada o resultado da bg task entra na visão do agent.

Acertos: 0 / 5