Add multi-agent race condition test (bd-zo7o)

- Created test_agent_race.py with 3 test scenarios
- Tests collision prevention with Agent Mail reservations
- Validates that only one agent claims an issue when reservations active
- Demonstrates collision problem when Agent Mail disabled
- Includes stress test with 10 agents
- Non-interactive mode support for CI/automation

Amp-Thread-ID: https://ampcode.com/threads/T-2fb10899-490f-4d41-b003-8bc4d467cc54
Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
Steve Yegge
2025-11-08 00:36:21 -08:00
parent 7c6a7fc382
commit ef94641541
4 changed files with 538 additions and 27 deletions

View File

@@ -8,19 +8,34 @@ This demonstrates how an agent can:
3. Discover new issues during work
4. Link discoveries back to parent tasks
5. Complete work and move on
6. Coordinate with other agents via Agent Mail (optional)
"""
import json
import subprocess
import sys
import os
from pathlib import Path
from typing import Optional
# Add lib directory to path for beads_mail_adapter
lib_path = Path(__file__).parent.parent.parent / "lib"
sys.path.insert(0, str(lib_path))
from beads_mail_adapter import AgentMailAdapter
class BeadsAgent:
"""Simple agent that manages tasks using bd."""
def __init__(self):
self.current_task = None
self.mail = AgentMailAdapter()
if self.mail.enabled:
print(f"📬 Agent Mail enabled (agent: {self.mail.agent_name})")
else:
print("📭 Agent Mail disabled (Beads-only mode)")
def run_bd(self, *args) -> dict:
"""Run bd command and parse JSON output."""
@@ -32,7 +47,20 @@ class BeadsAgent:
return {}
def find_ready_work(self) -> Optional[dict]:
"""Find the highest priority ready work."""
"""Find the highest priority ready work.
Integration Point 1: Check inbox before finding work.
"""
# Check inbox for notifications from other agents
messages = self.mail.check_inbox()
if messages:
print(f"📨 Received {len(messages)} messages:")
for msg in messages:
event_type = msg.get("event_type", "unknown")
payload = msg.get("payload", {})
from_agent = msg.get("from_agent", "unknown")
print(f"{event_type} from {from_agent}: {payload}")
ready = self.run_bd("ready", "--limit", "1")
if isinstance(ready, list) and len(ready) > 0:
@@ -40,9 +68,27 @@ class BeadsAgent:
return None
def claim_task(self, issue_id: str) -> dict:
"""Claim a task by setting status to in_progress."""
"""Claim a task by setting status to in_progress.
Integration Point 2: Reserve issue before claiming.
Integration Point 3: Notify other agents of status change.
"""
# Reserve the issue to prevent conflicts with other agents
if not self.mail.reserve_issue(issue_id):
print(f"⚠️ Failed to reserve {issue_id} - already claimed by another agent")
return {}
print(f"📋 Claiming task: {issue_id}")
return self.run_bd("update", issue_id, "--status", "in_progress")
result = self.run_bd("update", issue_id, "--status", "in_progress")
# Notify other agents of status change
self.mail.notify("status_changed", {
"issue_id": issue_id,
"status": "in_progress",
"agent": self.mail.agent_name
})
return result
def create_issue(self, title: str, description: str = "",
priority: int = 2, issue_type: str = "task") -> dict:
@@ -62,9 +108,24 @@ class BeadsAgent:
)
def complete_task(self, issue_id: str, reason: str = "Completed"):
"""Mark task as complete."""
"""Mark task as complete.
Integration Point 4: Release reservation and notify completion.
"""
print(f"✅ Completing task: {issue_id} - {reason}")
return self.run_bd("close", issue_id, "--reason", reason)
result = self.run_bd("close", issue_id, "--reason", reason)
# Notify other agents of completion
self.mail.notify("issue_completed", {
"issue_id": issue_id,
"reason": reason,
"agent": self.mail.agent_name
})
# Release the reservation
self.mail.release_issue(issue_id)
return result
def simulate_work(self, issue: dict) -> bool:
"""Simulate doing work on an issue.