Lektion 08 · Nebenläufigkeit

Arbeit selbst laufen lassen — Agent nicht blockieren

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

⏱ ~10 Min · 📝 3 interaktive Widgets · 🧑‍💻 Basiert auf shareAI-lab · s08_background_tasks.py

Der Schmerz blockierender Aufrufe

Das bash-Tool aus S02 ist synchron: subprocess.run(..., timeout=120) — ein npm install mit 90 Sekunden Laufzeit friert den gesamten Agent Loop für 90 Sekunden ein. Der Nutzer starrt auf das Terminal und weiß nicht, ob der Agent hängt oder arbeitet.

s08-Lösung: Ein background_run-Tool. Es gibt sofort eine task_id zurück, der Befehl läuft in einem separaten Thread. Der Agent arbeitet im Loop weiter; wenn der Background-Task abgeschlossen ist, wird das Ergebnis in eine Notification Queue eingereiht.

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"   # sofortige Rückkehr

Wie kommt das Ergebnis zurück zum Agent?

Entscheidend ist eine thread-sichere Queue: Der Background-Thread schreibt bei Fertigstellung; der Hauptthread leert die Queue vor jedem LLM-Aufruf und injiziert Fertigstellungsmeldungen als User-Nachrichten in messages.

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

Wenn der Agent in Runde N einen Background-Task spawnt und der Task in Runde N+3 fertig wird, enthält der nächste LLM-Aufruf automatisch das Ergebnis — das Modell sieht den <background-results>-Block und weiß: "der Task ist fertig, ich mache weiter".

Zeitstrahl-Simulation

Das folgende Widget simuliert den Ablauf: Der Hauptthread tickt einmal pro Sekunde (simulierter Agent-Loop-Takt); Background-Tasks können jederzeit gespawnt werden. Beobachte, wie die beiden Stränge am Drain-Punkt zusammenführen.

Welche Befehle gehören in den Hintergrund?

Nicht jeder Befehl sollte im Hintergrund laufen. Zwei Kriterien:

  1. Laufzeit: Wenige Sekunden — synchron ist einfacher und vermeidet Queue-Overhead.
  2. Ergebnis-Dringlichkeit: Wenn der nächste Schritt das Ergebnis sofort braucht (z.B. cat file.txt gefolgt von grep), bringt Hintergrund nichts — man muss ohnehin warten.
Interaktiv

Widget 1 · Timeline · Hauptthread + 2 Background-Threads

Spawn klicken, um Background-Threads zu beauftragen. Der Hauptthread prüft bei jedem Tick die Notification Queue. Beobachte drei Swim Lanes.

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

Widget 2 · Fire & Forget · 8 Befehle — Vordergrund oder Hintergrund?

Für jeden Befehl wählen: foreground (synchron warten) oder background (asynchron spawnen). Dimensionen bedenken: Laufzeit und Ergebnis-Dringlichkeit.

0 / 8 richtig
Interaktiv

Widget 3 · Drain Timing · In welcher Runde sieht der Agent das Ergebnis?

Kernregel: Der Hauptthread leert die Queue vor jedem LLM-Aufruf. 5 Szenarien — nach wie vielen Runden wird das Background-Ergebnis für den Agent sichtbar?

0 / 5 richtig