diff --git a/agents/s08_background_tasks.py b/agents/s08_background_tasks.py index 390a77780..7b13cc550 100644 --- a/agents/s08_background_tasks.py +++ b/agents/s08_background_tasks.py @@ -28,6 +28,7 @@ import os import subprocess import threading +import time import uuid from pathlib import Path @@ -44,6 +45,7 @@ MODEL = os.environ["MODEL_ID"] SYSTEM = f"You are a coding agent at {WORKDIR}. Use background_run for long-running commands." +MAX_BG_WAIT = 30 # seconds to wait for background completions before exit # -- BackgroundManager: threaded execution + notification queue -- @@ -52,6 +54,7 @@ def __init__(self): self.tasks = {} # task_id -> {status, result, command} self._notification_queue = [] # completed task results self._lock = threading.Lock() + self._cond = threading.Condition(self._lock) def run(self, command: str) -> str: """Start a background thread, return task_id immediately.""" @@ -80,13 +83,14 @@ def _execute(self, task_id: str, command: str): status = "error" self.tasks[task_id]["status"] = status self.tasks[task_id]["result"] = output or "(no output)" - with self._lock: + with self._cond: self._notification_queue.append({ "task_id": task_id, "status": status, "command": command[:80], "result": (output or "(no output)")[:500], }) + self._cond.notify_all() def check(self, task_id: str = None) -> str: """Check status of one task or list all.""" @@ -107,6 +111,16 @@ def drain_notifications(self) -> list: self._notification_queue.clear() return notifs + def has_running(self) -> bool: + return any(t["status"] == "running" for t in self.tasks.values()) + + def wait_for_notification(self, timeout: float) -> bool: + with self._cond: + if self._notification_queue: + return True + self._cond.wait(timeout=timeout) + return bool(self._notification_queue) + BG = BackgroundManager() @@ -200,6 +214,35 @@ def agent_loop(messages: list): ) messages.append({"role": "assistant", "content": response.content}) if response.stop_reason != "tool_use": + pending = BG.drain_notifications() + if pending and messages: + notif_text = "\n".join( + f"[bg:{n['task_id']}] {n['status']}: {n['result']}" for n in pending + ) + messages.append({"role": "user", "content": f"\n{notif_text}\n"}) + continue + if BG.has_running(): + deadline = time.monotonic() + MAX_BG_WAIT + delivered = False + while time.monotonic() < deadline and BG.has_running(): + BG.wait_for_notification(timeout=0.5) + pending = BG.drain_notifications() + if pending and messages: + notif_text = "\n".join( + f"[bg:{n['task_id']}] {n['status']}: {n['result']}" for n in pending + ) + messages.append({"role": "user", "content": f"\n{notif_text}\n"}) + delivered = True + break + if delivered: + continue + pending = BG.drain_notifications() + if pending and messages: + notif_text = "\n".join( + f"[bg:{n['task_id']}] {n['status']}: {n['result']}" for n in pending + ) + messages.append({"role": "user", "content": f"\n{notif_text}\n"}) + continue return results = [] for block in response.content: