Skip to content

Commit ab2cf01

Browse files
authored
Merge pull request #429 from Bill-Billion/feat/s15-poller-background-results
Cover completed background tasks in the Lead's inbox poller, and mark when teammates finish (#46)
2 parents 17e2e12 + 647a8dc commit ab2cf01

1 file changed

Lines changed: 32 additions & 11 deletions

File tree

‎s15_agent_teams/code.py‎

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,13 @@ def collect_background_results() -> list[str]:
344344
return notifications
345345

346346

347+
def has_pending_background() -> bool:
348+
"""Non-destructive: True if any background task has completed and is
349+
waiting to be collected. The inbox poller uses this in its wake condition."""
350+
with background_lock:
351+
return any(t["status"] == "completed" for t in background_tasks.values())
352+
353+
347354
# ── Cron Scheduler (from s14, synced) ──
348355

349356
DURABLE_PATH = WORKDIR / ".scheduled_tasks.json"
@@ -911,7 +918,8 @@ def agent_loop(messages: list, context: dict):
911918
history = []
912919
context = update_context({}, [])
913920

914-
# input() and a 1s inbox poller feed one event queue (issue #291).
921+
# input() and a 1s poller (teammate inbox or background results) feed one
922+
# event queue (issues #291, #46).
915923
events = queue.Queue()
916924

917925
def input_reader():
@@ -924,17 +932,19 @@ def input_reader():
924932
events.put(("user", line))
925933

926934
def inbox_poller():
927-
# Poll ~1s and submit the Lead's inbox as a new turn. Don't gate on
935+
# Poll ~1s and wake the Lead when async results are ready: teammate
936+
# inbox messages or completed background tasks. Don't gate on
928937
# active_teammates: a teammate sends its result and then removes itself,
929938
# so the final message can outlive its registry entry.
930939
while True:
931940
time.sleep(1)
932-
if BUS.peek("lead"):
933-
events.put(("inbox", None))
941+
if BUS.peek("lead") or has_pending_background():
942+
events.put(("wake", None))
934943

935944
threading.Thread(target=input_reader, daemon=True).start()
936945
threading.Thread(target=inbox_poller, daemon=True).start()
937946

947+
had_teammates = False
938948
while True:
939949
kind, payload = events.get()
940950
if kind == "quit":
@@ -943,20 +953,31 @@ def inbox_poller():
943953
if payload.strip().lower() in ("q", "exit", ""):
944954
break
945955
history.append({"role": "user", "content": payload})
946-
else: # "inbox": a teammate message woke the Lead
956+
else: # "wake": teammate inbox or background results are ready
957+
parts = []
947958
inbox = BUS.read_inbox("lead")
948-
if not inbox:
959+
if inbox:
960+
parts.append("[Inbox]\n" + "\n".join(
961+
f"From {m['from']}: {m['content'][:200]}" for m in inbox))
962+
bg = collect_background_results()
963+
parts.extend(bg)
964+
if not parts:
949965
continue # already drained by an earlier wake (idempotent)
950-
inbox_text = "\n".join(
951-
f"From {m['from']}: {m['content'][:200]}" for m in inbox)
952-
history.append({"role": "user",
953-
"content": f"[Inbox]\n{inbox_text}"})
954-
print(f"\n\033[33m[Inbox: {len(inbox)} messages → new turn]\033[0m")
966+
history.append({"role": "user", "content": "\n".join(parts)})
967+
print(f"\n\033[33m[wake: {len(inbox)} inbox + {len(bg)} background "
968+
f"-> new turn]\033[0m")
955969

956970
# One turn for whichever source woke us.
957971
agent_loop(history, context)
958972
context = update_context(context, history)
959973
for block in history[-1]["content"]:
960974
if getattr(block, "type", None) == "text":
961975
print(block.text)
976+
977+
# Announce once when every teammate has finished and its output drained.
978+
if active_teammates:
979+
had_teammates = True
980+
elif had_teammates and not BUS.peek("lead") and not has_pending_background():
981+
print("\033[32m[all teammates done]\033[0m")
982+
had_teammates = False
962983
print()

0 commit comments

Comments
 (0)