작업이 스스로 실행되고 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하여 완료 알림을 user 메시지로 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(...) ...
이렇게 하면 agent가 N번째 라운드에 bg 작업을 spawn하고 N+3번째 라운드에 해당 작업이 완료되면, 다음 LLM 호출에 자동으로 결과가 포함됩니다—모델이 <background-results> 블록을 보면 "아, 그 작업이 끝났구나, 계속하자"라고 알게 됩니다.
타임라인 데모
아래 widget으로 시뮬레이션해보세요: 메인 스레드는 초마다 tick 하나 (agent 루프 박자를 시뮬레이션); 언제든 bg 작업을 spawn할 수 있습니다. 두 스레드가 "drain 지점"에서 어떻게 합쳐지는지 보세요.
어떤 명령을 백그라운드로 보내야 하나?
모든 명령을 백그라운드로 보낼 필요는 없습니다. 두 가지 기준이 있습니다:
- 소요 시간: 몇 초 이내라면 동기 실행이 더 간단합니다. 큐 관리 비용이 0.1초 기다리는 것보다 비쌉니다.
- 결과의 중요도: 다음 단계에서 이 결과가 바로 필요하다면 (예:
cat file.txt다음에 바로grep), 백그라운드는 의미가 없습니다—어차피 기다려야 합니다.