讓活自己跑,agent 不阻塞
「Fire and forget — the agent doesn't block while the command runs.」
阻塞式呼叫的痛
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 點」匯合。
哪些指令該放後台?
並不是所有指令都該丟後台。判據兩個:
- 耗時:幾秒以內的同步跑更簡單,省得維護隊列。
- 結果重要程度:如果下一步緊接著要用這個結果(例如
cat file.txt之後立刻grep),後台沒意義——你還是得等它。