Lesson 08 · 並行

讓活自己跑,agent 不阻塞

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

⏱ 約 10 分鐘 · 📝 3 個可互動元件 · 🧑‍💻 基於 shareAI-lab · s08_background_tasks.py

阻塞式呼叫的痛

S02 裡的 bash 工具是同步的:subprocess.run(..., timeout=120),跑 npm install 這個要 90 秒的指令,整個 agent loop 卡住 90 秒。使用者盯著終端,不知道自己掛了還是在工作。

s08 的解法:給 agent 一個 background_run 工具。它立刻返回一個 task_id,命令在另一個線程上跑。 agent 繼續循環、做別的事;bg 任務完成時往通知佇列裡塞結果。

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"   # 立即返回

結果怎麼回到 agent?

關鍵是一個執行緒安全的佇列:bg 執行緒完成時往佇列 append;主執行緒每次 LLM 呼叫前把佇列 drain 空,把完成通知塞進 messages 作為 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(...)
        ...

這樣 agent 在第 N 輪 spawn 了一個 bg 任務,第 N+3 輪該任務完成時,下一次 LLM 呼叫會自動帶上結果——模型看到 <background-results> 塊就知道:“哦那個任務跑完了,我繼續”。

時間軸演示

下面 widget 讓你模擬:主線程每秒 tick 一下(模擬 agent 循環節拍);你可以隨時 spawn bg 任務。看看兩條線索如何在「drain 點」匯合。

哪些指令該放後台?

並不是所有指令都該丟後台。判據兩個:

  1. 耗時:幾秒以內的同步跑更簡單,省得維護隊列。
  2. 結果重要程度:如果下一步緊接著要用這個結果(例如 cat file.txt 之後立刻 grep),後台沒意義——你還是得等它。
Interactive

Widget 1 · Timeline · 主線程 + 2 條 bg 線程

點 Spawn 給 bg 線程派活。主線程每 tick 會檢查 notification queue。看三條 swim lane 如何交織。

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

Widget 2 · Fire & Forget 判斷 · 8 個指令,哪些值得後台?

每個指令選 foreground(同步等)或 background(非同步 spawn)。注意思考「要不要等結果」+「耗時」這兩個維度。

答对 0 / 8
Interactive

Widget 3 · Drain Timing · 通知在哪一輪被看到

關鍵規則:主執行緒在每次 LLM 呼叫前 drain 佇列。給你 5 個場景,答 bg 任務完成後,結果會在第幾輪進入 agent 視野。

答对 0 / 5