File-based message bus. Each agent has a .jsonl inbox. Read is destructive: read_text + unlink (consumes messages). Teaching version: no file locking; real CC uses proper-lockfile.
| 600 | |
| 601 | |
| 602 | class MessageBus: |
| 603 | """File-based message bus. Each agent has a .jsonl inbox. |
| 604 | Read is destructive: read_text + unlink (consumes messages). |
| 605 | Teaching version: no file locking; real CC uses proper-lockfile.""" |
| 606 | |
| 607 | def send(self, from_agent: str, to_agent: str, content: str, |
| 608 | msg_type: str = "message"): |
| 609 | msg = {"from": from_agent, "to": to_agent, |
| 610 | "content": content, "type": msg_type, |
| 611 | "ts": time.time()} |
| 612 | inbox = MAILBOX_DIR / f"{to_agent}.jsonl" |
| 613 | with open(inbox, "a") as f: |
| 614 | f.write(json.dumps(msg) + "\n") |
| 615 | print(f" \033[33m[bus] {from_agent} → {to_agent}: " |
| 616 | f"{content[:50]}\033[0m") |
| 617 | |
| 618 | def read_inbox(self, agent: str) -> list[dict]: |
| 619 | inbox = MAILBOX_DIR / f"{agent}.jsonl" |
| 620 | if not inbox.exists(): |
| 621 | return [] |
| 622 | msgs = [json.loads(line) for line in inbox.read_text().splitlines() |
| 623 | if line.strip()] |
| 624 | inbox.unlink() # consume: read + delete |
| 625 | return msgs |
| 626 | |
| 627 | def peek(self, agent: str) -> bool: |
| 628 | """Non-destructive: True if the agent has unread inbox messages. |
| 629 | The Lead's inbox poller uses this to decide whether to wake a turn |
| 630 | without consuming the mailbox.""" |
| 631 | inbox = MAILBOX_DIR / f"{agent}.jsonl" |
| 632 | return inbox.exists() and inbox.stat().st_size > 0 |
| 633 | |
| 634 | |
| 635 | BUS = MessageBus() |