How to Build an AI Agent
How to Build an AI Agent
s08

Фоновые задачи

Параллелизм

Background Threads + Notifications

198 LOC6 инструментовBackgroundManager + notification queue
Run slow operations in the background; the agent keeps thinking ahead

s01 > s02 > s03 > s04 > s05 > s06 | s07 > [ s08 ] s09 > s10 > s11 > s12

"Запускайте медленные операции в фоне; агент продолжает думать" -- демон-потоки выполняют команды, внедряют уведомления по завершении.

Проблема

Некоторые команды занимают минуты: npm install, pytest, docker build. С блокирующим циклом модель сидит в ожидании. Если пользователь скажет "установи зависимости и, пока это выполняется, создай конфиг-файл", агент делает их последовательно, а не параллельно.

Решение

Main thread                Background thread
+-----------------+        +-----------------+
| agent loop      |        | subprocess runs |
| ...             |        | ...             |
| [LLM call] <---+------- | enqueue(result) |
|  ^drain queue  |        +-----------------+
+-----------------+

Timeline:
Agent --[spawn A]--[spawn B]--[other work]----
             |          |
             v          v
          [A runs]   [B runs]      (parallel)
             |          |
             +-- results injected before next LLM call --+

Как Это Работает

  1. BackgroundManager отслеживает задачи с потокобезопасной очередью уведомлений.
class BackgroundManager:
    def __init__(self):
        self.tasks = {}
        self._notification_queue = []
        self._lock = threading.Lock()
  1. run() запускает демон-поток и возвращается сразу.
def run(self, command: str) -> str:
    task_id = str(uuid.uuid4())[:8]
    self.tasks[task_id] = {"status": "running", "command": command}
    thread = threading.Thread(
        target=self._execute, args=(task_id, command), daemon=True)
    thread.start()
    return f"Background task {task_id} started"
  1. Когда подпроцесс завершается, его результат попадает в очередь уведомлений.
def _execute(self, task_id, command):
    try:
        r = subprocess.run(command, shell=True, cwd=WORKDIR,
            capture_output=True, text=True, timeout=300)
        output = (r.stdout + r.stderr).strip()[:50000]
    except subprocess.TimeoutExpired:
        output = "Error: Timeout (300s)"
    with self._lock:
        self._notification_queue.append({
            "task_id": task_id, "result": output[:500]})
  1. Цикл агента освобождает очередь уведомлений перед каждым вызовом LLM.
def agent_loop(messages: list):
    while True:
        notifs = BG.drain_notifications()
        if notifs:
            notif_text = "\n".join(
                f"[bg:{n['task_id']}] {n['result']}" for n in notifs)
            messages.append({"role": "user",
                "content": f"<background-results>\n{notif_text}\n"
                           f"</background-results>"})
            messages.append({"role": "assistant",
                "content": "Noted background results."})
        response = client.messages.create(...)

Цикл остаётся однопоточным. Только ввод/вывод подпроцессов параллелизуется.

Что Изменилось с s07

КомпонентДо (s07)После (s08)
Инструменты86 (base + background_run + check)
ВыполнениеТолько блокирующееБлокирующее + фоновые потоки
УведомлениеНетОчередь освобождаемая за цикл
ПараллелизмНетДемон-потоки

Попробуйте

python agents/s08_background_tasks.py
  1. Запустите "sleep 5 && echo done" в фоне, затем создайте файл, пока оно выполняется
  2. Запустите 3 фоновые задачи: "sleep 2", "sleep 4", "sleep 6". Проверьте их статус.
  3. Запустите pytest в фоне и продолжайте работать над другими задачами