Merge branch 'main' of github.com:steveyegge/beads
This commit is contained in:
File diff suppressed because one or more lines are too long
19
AGENTS.md
19
AGENTS.md
@@ -4,6 +4,23 @@
|
|||||||
|
|
||||||
This is **beads** (command: `bd`), an issue tracker designed for AI-supervised coding workflows. We dogfood our own tool!
|
This is **beads** (command: `bd`), an issue tracker designed for AI-supervised coding workflows. We dogfood our own tool!
|
||||||
|
|
||||||
|
## 🆕 What's New?
|
||||||
|
|
||||||
|
**New to bd or upgrading?** Run `bd info --whats-new` to see agent-relevant changes from recent versions:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bd info --whats-new # Human-readable output
|
||||||
|
bd info --whats-new --json # Machine-readable output
|
||||||
|
```
|
||||||
|
|
||||||
|
This shows the last 3 versions with workflow-impacting changes, avoiding the need to re-read all documentation. Examples:
|
||||||
|
- New commands and flags that improve agent workflows
|
||||||
|
- Breaking changes that require workflow updates
|
||||||
|
- Performance improvements and bug fixes
|
||||||
|
- Integration features (MCP, Agent Mail, git hooks)
|
||||||
|
|
||||||
|
**Why this matters:** bd releases weekly with major versions. This command helps you quickly understand what changed without parsing the full CHANGELOG.
|
||||||
|
|
||||||
## Human Setup vs Agent Usage
|
## Human Setup vs Agent Usage
|
||||||
|
|
||||||
**IMPORTANT:** If you need to initialize bd, use the `--quiet` flag:
|
**IMPORTANT:** If you need to initialize bd, use the `--quiet` flag:
|
||||||
@@ -245,7 +262,7 @@ bd close bd-42 "Done" # Updates via git sync
|
|||||||
- ✅ Infrequent updates (low collision risk)
|
- ✅ Infrequent updates (low collision risk)
|
||||||
- ✅ Simplicity preferred over latency
|
- ✅ Simplicity preferred over latency
|
||||||
|
|
||||||
See [docs/AGENT_MAIL.md](docs/AGENT_MAIL.md) for complete setup, troubleshooting, and architecture details.
|
See [docs/AGENT_MAIL_QUICKSTART.md](docs/AGENT_MAIL_QUICKSTART.md) for 5-minute setup, or [docs/AGENT_MAIL.md](docs/AGENT_MAIL.md) for complete documentation. Example code in [examples/python-agent/AGENT_MAIL_EXAMPLE.md](examples/python-agent/AGENT_MAIL_EXAMPLE.md).
|
||||||
|
|
||||||
### Issue Types
|
### Issue Types
|
||||||
|
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ Agents report that they enjoy working with Beads, and they will use it spontaneo
|
|||||||
- 🤖 **Agent-friendly** - `--json` flags for programmatic integration
|
- 🤖 **Agent-friendly** - `--json` flags for programmatic integration
|
||||||
- 📦 **Git-versioned** - JSONL records stored in git, synced across machines
|
- 📦 **Git-versioned** - JSONL records stored in git, synced across machines
|
||||||
- 🌍 **Distributed by design** - Agents on multiple machines share one logical database via git
|
- 🌍 **Distributed by design** - Agents on multiple machines share one logical database via git
|
||||||
|
- 🚀 **Optional Agent Mail** - Real-time multi-agent coordination (<100ms vs 2-5s git sync)
|
||||||
- 🔐 **Protected branch support** - Works with GitHub/GitLab protected branches via separate sync branch
|
- 🔐 **Protected branch support** - Works with GitHub/GitLab protected branches via separate sync branch
|
||||||
- 🏗️ **Extensible** - Add your own tables to the SQLite database
|
- 🏗️ **Extensible** - Add your own tables to the SQLite database
|
||||||
- 🔍 **Multi-project isolation** - Each project gets its own database, auto-discovered by directory
|
- 🔍 **Multi-project isolation** - Each project gets its own database, auto-discovered by directory
|
||||||
@@ -737,6 +738,8 @@ For advanced usage, see:
|
|||||||
- **[README.md](README.md)** - You are here! Core features and quick start
|
- **[README.md](README.md)** - You are here! Core features and quick start
|
||||||
- **[docs/INSTALLING.md](docs/INSTALLING.md)** - Complete installation guide for all platforms
|
- **[docs/INSTALLING.md](docs/INSTALLING.md)** - Complete installation guide for all platforms
|
||||||
- **[docs/QUICKSTART.md](docs/QUICKSTART.md)** - Interactive tutorial (`bd quickstart`)
|
- **[docs/QUICKSTART.md](docs/QUICKSTART.md)** - Interactive tutorial (`bd quickstart`)
|
||||||
|
- **[docs/AGENT_MAIL_QUICKSTART.md](docs/AGENT_MAIL_QUICKSTART.md)** - 5-minute Agent Mail setup guide
|
||||||
|
- **[docs/AGENT_MAIL.md](docs/AGENT_MAIL.md)** - Complete Agent Mail integration guide
|
||||||
- **[docs/MULTI_REPO_MIGRATION.md](docs/MULTI_REPO_MIGRATION.md)** - Multi-repo workflow guide (OSS, teams, multi-phase)
|
- **[docs/MULTI_REPO_MIGRATION.md](docs/MULTI_REPO_MIGRATION.md)** - Multi-repo workflow guide (OSS, teams, multi-phase)
|
||||||
- **[docs/MULTI_REPO_AGENTS.md](docs/MULTI_REPO_AGENTS.md)** - Multi-repo patterns for AI agents
|
- **[docs/MULTI_REPO_AGENTS.md](docs/MULTI_REPO_AGENTS.md)** - Multi-repo patterns for AI agents
|
||||||
- **[docs/FAQ.md](docs/FAQ.md)** - Frequently asked questions
|
- **[docs/FAQ.md](docs/FAQ.md)** - Frequently asked questions
|
||||||
|
|||||||
@@ -23,13 +23,23 @@ or daemon connection. It shows:
|
|||||||
- If using daemon: socket path, health status, version
|
- If using daemon: socket path, health status, version
|
||||||
- Database statistics (issue count)
|
- Database statistics (issue count)
|
||||||
- Schema information (with --schema flag)
|
- Schema information (with --schema flag)
|
||||||
|
- What's new in recent versions (with --whats-new flag)
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
bd info
|
bd info
|
||||||
bd info --json
|
bd info --json
|
||||||
bd info --schema --json`,
|
bd info --schema --json
|
||||||
|
bd info --whats-new
|
||||||
|
bd info --whats-new --json`,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
schemaFlag, _ := cmd.Flags().GetBool("schema")
|
schemaFlag, _ := cmd.Flags().GetBool("schema")
|
||||||
|
whatsNewFlag, _ := cmd.Flags().GetBool("whats-new")
|
||||||
|
|
||||||
|
// Handle --whats-new flag
|
||||||
|
if whatsNewFlag {
|
||||||
|
showWhatsNew()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Get database path (absolute)
|
// Get database path (absolute)
|
||||||
absDBPath, err := filepath.Abs(dbPath)
|
absDBPath, err := filepath.Abs(dbPath)
|
||||||
@@ -239,8 +249,91 @@ func extractPrefix(issueID string) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VersionChange represents agent-relevant changes for a specific version
|
||||||
|
type VersionChange struct {
|
||||||
|
Version string `json:"version"`
|
||||||
|
Date string `json:"date"`
|
||||||
|
Changes []string `json:"changes"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// versionChanges contains agent-actionable changes for recent versions
|
||||||
|
var versionChanges = []VersionChange{
|
||||||
|
{
|
||||||
|
Version: "0.22.1",
|
||||||
|
Date: "2025-11-06",
|
||||||
|
Changes: []string{
|
||||||
|
"Native `bd merge` command vendored from beads-merge - no external binary needed",
|
||||||
|
"`bd info` detects outdated git hooks - warns if version mismatch",
|
||||||
|
"Multi-workspace deletion tracking fixed - deletions now propagate correctly",
|
||||||
|
"Hash ID recognition improved - recognizes Base36 IDs without a-f letters",
|
||||||
|
"Import/export deadlock fixed - no hanging when daemon running",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Version: "0.22.0",
|
||||||
|
Date: "2025-11-05",
|
||||||
|
Changes: []string{
|
||||||
|
"Intelligent merge driver auto-configured - eliminates most JSONL conflicts",
|
||||||
|
"Onboarding wizards: `bd init --contributor` and `bd init --team`",
|
||||||
|
"New `bd migrate-issues` command - migrate issues between repos with dependencies",
|
||||||
|
"`bd show` displays blocker status - 'Blocked by N open issues' or 'Ready to work'",
|
||||||
|
"SearchIssues N+1 query fixed - batch-loads labels for better performance",
|
||||||
|
"Sync validation prevents infinite dirty loop - verifies JSONL export",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Version: "0.21.0",
|
||||||
|
Date: "2025-11-04",
|
||||||
|
Changes: []string{
|
||||||
|
"Hash-based IDs eliminate collisions - remove ID coordination workarounds",
|
||||||
|
"Event-driven daemon mode (opt-in) - set BEADS_DAEMON_MODE=events",
|
||||||
|
"Agent Mail integration - real-time multi-agent coordination (<100ms latency)",
|
||||||
|
"`bd duplicates --auto-merge` - automated duplicate detection and merging",
|
||||||
|
"Hierarchical children for epics - dotted IDs (bd-abc.1, bd-abc.2) up to 3 levels",
|
||||||
|
"`--discovered-from` inline syntax - create with dependency in one command",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// showWhatsNew displays agent-relevant changes from recent versions
|
||||||
|
func showWhatsNew() {
|
||||||
|
currentVersion := Version // from version.go
|
||||||
|
|
||||||
|
if jsonOutput {
|
||||||
|
outputJSON(map[string]interface{}{
|
||||||
|
"current_version": currentVersion,
|
||||||
|
"recent_changes": versionChanges,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Human-readable output
|
||||||
|
fmt.Printf("\n🆕 What's New in bd (Current: v%s)\n", currentVersion)
|
||||||
|
fmt.Println("=" + strings.Repeat("=", 60))
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
for _, vc := range versionChanges {
|
||||||
|
// Highlight if this is the current version
|
||||||
|
versionMarker := ""
|
||||||
|
if vc.Version == currentVersion {
|
||||||
|
versionMarker = " ← current"
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("## v%s (%s)%s\n\n", vc.Version, vc.Date, versionMarker)
|
||||||
|
|
||||||
|
for _, change := range vc.Changes {
|
||||||
|
fmt.Printf(" • %s\n", change)
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("💡 Tip: Use `bd info --whats-new --json` for machine-readable output")
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
infoCmd.Flags().Bool("schema", false, "Include schema information in output")
|
infoCmd.Flags().Bool("schema", false, "Include schema information in output")
|
||||||
|
infoCmd.Flags().Bool("whats-new", false, "Show agent-relevant changes from recent versions")
|
||||||
infoCmd.Flags().BoolVar(&jsonOutput, "json", false, "Output in JSON format")
|
infoCmd.Flags().BoolVar(&jsonOutput, "json", false, "Output in JSON format")
|
||||||
rootCmd.AddCommand(infoCmd)
|
rootCmd.AddCommand(infoCmd)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -11,3 +12,83 @@ func TestInfoCommand(t *testing.T) {
|
|||||||
func TestInfoWithNoDaemon(t *testing.T) {
|
func TestInfoWithNoDaemon(t *testing.T) {
|
||||||
t.Skip("Manual test - bd info --no-daemon command is working, see manual testing")
|
t.Skip("Manual test - bd info --no-daemon command is working, see manual testing")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestVersionChangesStructure(t *testing.T) {
|
||||||
|
// Verify versionChanges is properly structured
|
||||||
|
if len(versionChanges) == 0 {
|
||||||
|
t.Fatal("versionChanges should not be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, vc := range versionChanges {
|
||||||
|
if vc.Version == "" {
|
||||||
|
t.Errorf("versionChanges[%d] has empty Version", i)
|
||||||
|
}
|
||||||
|
if vc.Date == "" {
|
||||||
|
t.Errorf("versionChanges[%d] has empty Date", i)
|
||||||
|
}
|
||||||
|
if len(vc.Changes) == 0 {
|
||||||
|
t.Errorf("versionChanges[%d] has no changes", i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify version format (should be like "0.22.1")
|
||||||
|
if len(vc.Version) < 5 {
|
||||||
|
t.Errorf("versionChanges[%d] has invalid Version format: %s", i, vc.Version)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify date format (should be like "2025-11-06")
|
||||||
|
if len(vc.Date) != 10 {
|
||||||
|
t.Errorf("versionChanges[%d] has invalid Date format: %s", i, vc.Date)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify each change is non-empty
|
||||||
|
for j, change := range vc.Changes {
|
||||||
|
if change == "" {
|
||||||
|
t.Errorf("versionChanges[%d].Changes[%d] is empty", i, j)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVersionChangesJSON(t *testing.T) {
|
||||||
|
// Test that versionChanges can be marshaled to JSON
|
||||||
|
data, err := json.Marshal(versionChanges)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to marshal versionChanges to JSON: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that it can be unmarshaled back
|
||||||
|
var unmarshaled []VersionChange
|
||||||
|
err = json.Unmarshal(data, &unmarshaled)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to unmarshal versionChanges from JSON: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify structure is preserved
|
||||||
|
if len(unmarshaled) != len(versionChanges) {
|
||||||
|
t.Errorf("Unmarshaled length %d != original length %d", len(unmarshaled), len(versionChanges))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spot check first entry
|
||||||
|
if len(unmarshaled) > 0 && len(versionChanges) > 0 {
|
||||||
|
if unmarshaled[0].Version != versionChanges[0].Version {
|
||||||
|
t.Errorf("Version mismatch: %s != %s", unmarshaled[0].Version, versionChanges[0].Version)
|
||||||
|
}
|
||||||
|
if len(unmarshaled[0].Changes) != len(versionChanges[0].Changes) {
|
||||||
|
t.Errorf("Changes count mismatch: %d != %d", len(unmarshaled[0].Changes), len(versionChanges[0].Changes))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVersionChangesCoverage(t *testing.T) {
|
||||||
|
// Ensure we have at least 3 recent versions documented
|
||||||
|
if len(versionChanges) < 3 {
|
||||||
|
t.Errorf("Should document at least 3 recent versions, found %d", len(versionChanges))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure each version has meaningful changes (at least 3 bullet points)
|
||||||
|
for i, vc := range versionChanges {
|
||||||
|
if len(vc.Changes) < 3 {
|
||||||
|
t.Errorf("versionChanges[%d] (v%s) should have at least 3 changes, found %d", i, vc.Version, len(vc.Changes))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
478
docs/AGENT_MAIL_QUICKSTART.md
Normal file
478
docs/AGENT_MAIL_QUICKSTART.md
Normal file
@@ -0,0 +1,478 @@
|
|||||||
|
# Agent Mail Quick Start Guide
|
||||||
|
|
||||||
|
Get started with Agent Mail for multi-agent bd coordination in 5 minutes.
|
||||||
|
|
||||||
|
## What is Agent Mail?
|
||||||
|
|
||||||
|
Agent Mail is an **optional** coordination layer for bd that reduces latency from 2-5 seconds (git sync) to <100ms (HTTP API) and prevents work collisions through file reservations.
|
||||||
|
|
||||||
|
**When to use it:**
|
||||||
|
- ✅ Multiple AI agents working concurrently
|
||||||
|
- ✅ Frequent status updates (high collision risk)
|
||||||
|
- ✅ Real-time coordination needed
|
||||||
|
|
||||||
|
**When to skip it:**
|
||||||
|
- ❌ Single agent workflows
|
||||||
|
- ❌ Infrequent updates (low collision risk)
|
||||||
|
- ❌ Simplicity preferred over latency
|
||||||
|
|
||||||
|
## 5-Minute Setup
|
||||||
|
|
||||||
|
### Step 1: Install Agent Mail Server (30 seconds)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/Dicklesworthstone/mcp_agent_mail.git ~/mcp_agent_mail
|
||||||
|
cd ~/mcp_agent_mail
|
||||||
|
python3 -m venv .venv
|
||||||
|
source .venv/bin/activate # Windows: .venv\Scripts\activate
|
||||||
|
pip install -e .
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2: Start the Server (5 seconds)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -m mcp_agent_mail.cli serve-http
|
||||||
|
# ✅ Server running on http://127.0.0.1:8765
|
||||||
|
```
|
||||||
|
|
||||||
|
Leave this terminal open. Open a new terminal for Step 3.
|
||||||
|
|
||||||
|
### Step 3: Configure Your Agent (10 seconds)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Set environment variables
|
||||||
|
export BEADS_AGENT_MAIL_URL=http://127.0.0.1:8765
|
||||||
|
export BEADS_AGENT_NAME=my-agent
|
||||||
|
export BEADS_PROJECT_ID=my-project
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 4: Use bd Normally (30 seconds)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Find ready work
|
||||||
|
bd ready
|
||||||
|
|
||||||
|
# Claim an issue
|
||||||
|
bd update bd-42 --status in_progress
|
||||||
|
# ✅ Reserved bd-42 for my-agent in <100ms
|
||||||
|
|
||||||
|
# Complete work
|
||||||
|
bd close bd-42 "Done"
|
||||||
|
# ✅ Reservation released automatically
|
||||||
|
```
|
||||||
|
|
||||||
|
**That's it!** bd now uses Agent Mail for coordination.
|
||||||
|
|
||||||
|
### Step 5: Test Multi-Agent (1 minute)
|
||||||
|
|
||||||
|
Open a second terminal:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Terminal 2 - Different agent
|
||||||
|
export BEADS_AGENT_MAIL_URL=http://127.0.0.1:8765
|
||||||
|
export BEADS_AGENT_NAME=other-agent
|
||||||
|
export BEADS_PROJECT_ID=my-project
|
||||||
|
|
||||||
|
# Try claiming same issue
|
||||||
|
bd update bd-42 --status in_progress
|
||||||
|
# ❌ Error: bd-42 already reserved by my-agent
|
||||||
|
```
|
||||||
|
|
||||||
|
**Success!** Agent Mail prevented collision.
|
||||||
|
|
||||||
|
## Common Use Cases
|
||||||
|
|
||||||
|
### Use Case 1: Claude Desktop + Command Line Agent
|
||||||
|
|
||||||
|
**Terminal 1 - Agent Mail Server:**
|
||||||
|
```bash
|
||||||
|
cd ~/mcp_agent_mail
|
||||||
|
source .venv/bin/activate
|
||||||
|
python -m mcp_agent_mail.cli serve-http
|
||||||
|
```
|
||||||
|
|
||||||
|
**Terminal 2 - Command Line:**
|
||||||
|
```bash
|
||||||
|
export BEADS_AGENT_MAIL_URL=http://127.0.0.1:8765
|
||||||
|
export BEADS_AGENT_NAME=cli-user
|
||||||
|
export BEADS_PROJECT_ID=my-project
|
||||||
|
|
||||||
|
bd ready
|
||||||
|
bd update bd-100 --status in_progress
|
||||||
|
```
|
||||||
|
|
||||||
|
**Claude Desktop:**
|
||||||
|
```
|
||||||
|
# In Claude's MCP settings, add env vars:
|
||||||
|
{
|
||||||
|
"beads": {
|
||||||
|
"command": "beads-mcp",
|
||||||
|
"env": {
|
||||||
|
"BEADS_AGENT_MAIL_URL": "http://127.0.0.1:8765",
|
||||||
|
"BEADS_AGENT_NAME": "claude",
|
||||||
|
"BEADS_PROJECT_ID": "my-project"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Now Claude and your command line won't step on each other!
|
||||||
|
|
||||||
|
### Use Case 2: Multiple Python Agents
|
||||||
|
|
||||||
|
**Terminal 1 - Server:**
|
||||||
|
```bash
|
||||||
|
cd ~/mcp_agent_mail
|
||||||
|
source .venv/bin/activate
|
||||||
|
python -m mcp_agent_mail.cli serve-http
|
||||||
|
```
|
||||||
|
|
||||||
|
**Terminal 2 - Agent A:**
|
||||||
|
```bash
|
||||||
|
cd ~/myproject/examples/python-agent
|
||||||
|
./agent_with_mail.py \
|
||||||
|
--agent-name alice \
|
||||||
|
--project-id myproject \
|
||||||
|
--agent-mail-url http://127.0.0.1:8765
|
||||||
|
```
|
||||||
|
|
||||||
|
**Terminal 3 - Agent B:**
|
||||||
|
```bash
|
||||||
|
cd ~/myproject/examples/python-agent
|
||||||
|
./agent_with_mail.py \
|
||||||
|
--agent-name bob \
|
||||||
|
--project-id myproject \
|
||||||
|
--agent-mail-url http://127.0.0.1:8765
|
||||||
|
```
|
||||||
|
|
||||||
|
Watch them coordinate in real-time!
|
||||||
|
|
||||||
|
### Use Case 3: Team Workflow
|
||||||
|
|
||||||
|
**Shared Server (runs on dev machine):**
|
||||||
|
```bash
|
||||||
|
# Machine 192.168.1.100
|
||||||
|
python -m mcp_agent_mail.cli serve-http --host 0.0.0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
**Team Member 1:**
|
||||||
|
```bash
|
||||||
|
export BEADS_AGENT_MAIL_URL=http://192.168.1.100:8765
|
||||||
|
export BEADS_AGENT_NAME=alice
|
||||||
|
export BEADS_PROJECT_ID=team-project
|
||||||
|
|
||||||
|
bd ready
|
||||||
|
```
|
||||||
|
|
||||||
|
**Team Member 2:**
|
||||||
|
```bash
|
||||||
|
export BEADS_AGENT_MAIL_URL=http://192.168.1.100:8765
|
||||||
|
export BEADS_AGENT_NAME=bob
|
||||||
|
export BEADS_PROJECT_ID=team-project
|
||||||
|
|
||||||
|
bd ready
|
||||||
|
```
|
||||||
|
|
||||||
|
Entire team shares one coordination server!
|
||||||
|
|
||||||
|
### Use Case 4: CI/CD Pipeline
|
||||||
|
|
||||||
|
**GitHub Actions Example:**
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
name: AI Agent Workflow
|
||||||
|
on: [push]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
agent-work:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
services:
|
||||||
|
agent-mail:
|
||||||
|
image: ghcr.io/dicklesworthstone/mcp_agent_mail:latest
|
||||||
|
ports:
|
||||||
|
- 8765:8765
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
agent: [agent-1, agent-2, agent-3]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Run agent
|
||||||
|
env:
|
||||||
|
BEADS_AGENT_MAIL_URL: http://localhost:8765
|
||||||
|
BEADS_AGENT_NAME: ${{ matrix.agent }}
|
||||||
|
BEADS_PROJECT_ID: ${{ github.repository }}
|
||||||
|
run: |
|
||||||
|
bd ready | head -1 | xargs -I {} bd update {} --status in_progress
|
||||||
|
# ... do work ...
|
||||||
|
bd close {} "Completed by CI"
|
||||||
|
```
|
||||||
|
|
||||||
|
Three agents run in parallel without collisions!
|
||||||
|
|
||||||
|
## Verification Checklist
|
||||||
|
|
||||||
|
After setup, verify everything works:
|
||||||
|
|
||||||
|
**✅ Server is running:**
|
||||||
|
```bash
|
||||||
|
curl http://127.0.0.1:8765/health
|
||||||
|
# Expected: {"status": "healthy"}
|
||||||
|
```
|
||||||
|
|
||||||
|
**✅ Environment variables are set:**
|
||||||
|
```bash
|
||||||
|
echo $BEADS_AGENT_MAIL_URL
|
||||||
|
# Expected: http://127.0.0.1:8765
|
||||||
|
|
||||||
|
echo $BEADS_AGENT_NAME
|
||||||
|
# Expected: my-agent
|
||||||
|
|
||||||
|
echo $BEADS_PROJECT_ID
|
||||||
|
# Expected: my-project
|
||||||
|
```
|
||||||
|
|
||||||
|
**✅ bd sees Agent Mail:**
|
||||||
|
```bash
|
||||||
|
bd info --json | grep agent_mail
|
||||||
|
# Expected: JSON with agent_mail config
|
||||||
|
```
|
||||||
|
|
||||||
|
**✅ Reservations work:**
|
||||||
|
```bash
|
||||||
|
# Terminal 1
|
||||||
|
bd update bd-test --status in_progress
|
||||||
|
# Expected: Success
|
||||||
|
|
||||||
|
# Terminal 2 (different agent)
|
||||||
|
export BEADS_AGENT_NAME=other-agent
|
||||||
|
bd update bd-test --status in_progress
|
||||||
|
# Expected: Error - reservation conflict
|
||||||
|
```
|
||||||
|
|
||||||
|
**✅ Graceful degradation works:**
|
||||||
|
```bash
|
||||||
|
# Stop server (Ctrl+C in server terminal)
|
||||||
|
|
||||||
|
# Try bd command
|
||||||
|
bd ready
|
||||||
|
# Expected: Warning about Agent Mail unavailable, but command succeeds
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Problem: "Agent Mail unavailable" warnings
|
||||||
|
|
||||||
|
**Symptoms:**
|
||||||
|
```
|
||||||
|
WARN Agent Mail unavailable, falling back to git-only mode
|
||||||
|
```
|
||||||
|
|
||||||
|
**Quick Fix:**
|
||||||
|
1. Check server is running: `curl http://127.0.0.1:8765/health`
|
||||||
|
2. Verify URL is correct: `echo $BEADS_AGENT_MAIL_URL`
|
||||||
|
3. Restart server if needed
|
||||||
|
|
||||||
|
### Problem: Agents don't see each other's reservations
|
||||||
|
|
||||||
|
**Cause:** Different `BEADS_PROJECT_ID` values
|
||||||
|
|
||||||
|
**Quick Fix:**
|
||||||
|
```bash
|
||||||
|
# All agents MUST use same project ID!
|
||||||
|
export BEADS_PROJECT_ID=same-project-name
|
||||||
|
```
|
||||||
|
|
||||||
|
### Problem: Reservation stuck after agent crashes
|
||||||
|
|
||||||
|
**Quick Fix:**
|
||||||
|
```bash
|
||||||
|
# Option 1: Release via API
|
||||||
|
curl -X DELETE http://127.0.0.1:8765/api/reservations/bd-stuck
|
||||||
|
|
||||||
|
# Option 2: Restart server (clears all reservations)
|
||||||
|
pkill -f mcp_agent_mail
|
||||||
|
python -m mcp_agent_mail.cli serve-http
|
||||||
|
```
|
||||||
|
|
||||||
|
### Problem: Port 8765 already in use
|
||||||
|
|
||||||
|
**Quick Fix:**
|
||||||
|
```bash
|
||||||
|
# Find what's using port
|
||||||
|
lsof -i :8765 # macOS/Linux
|
||||||
|
netstat -ano | findstr :8765 # Windows
|
||||||
|
|
||||||
|
# Kill old server
|
||||||
|
pkill -f mcp_agent_mail
|
||||||
|
|
||||||
|
# Or use different port
|
||||||
|
python -m mcp_agent_mail.cli serve-http --port 8766
|
||||||
|
export BEADS_AGENT_MAIL_URL=http://127.0.0.1:8766
|
||||||
|
```
|
||||||
|
|
||||||
|
## Monitoring
|
||||||
|
|
||||||
|
### Web UI
|
||||||
|
|
||||||
|
View all reservations in real-time:
|
||||||
|
```bash
|
||||||
|
open http://127.0.0.1:8765/mail
|
||||||
|
```
|
||||||
|
|
||||||
|
### API
|
||||||
|
|
||||||
|
Check reservations programmatically:
|
||||||
|
```bash
|
||||||
|
# List all reservations
|
||||||
|
curl http://127.0.0.1:8765/api/reservations | jq
|
||||||
|
|
||||||
|
# Check specific reservation
|
||||||
|
curl http://127.0.0.1:8765/api/reservations/bd-42 | jq
|
||||||
|
|
||||||
|
# Release reservation manually
|
||||||
|
curl -X DELETE http://127.0.0.1:8765/api/reservations/bd-42
|
||||||
|
```
|
||||||
|
|
||||||
|
### Logs
|
||||||
|
|
||||||
|
Agent Mail logs to stdout. Redirect to file if needed:
|
||||||
|
```bash
|
||||||
|
python -m mcp_agent_mail.cli serve-http > agent-mail.log 2>&1 &
|
||||||
|
tail -f agent-mail.log
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
### 1. Use Descriptive Agent Names
|
||||||
|
|
||||||
|
**Bad:**
|
||||||
|
```bash
|
||||||
|
export BEADS_AGENT_NAME=agent1
|
||||||
|
export BEADS_AGENT_NAME=agent2
|
||||||
|
```
|
||||||
|
|
||||||
|
**Good:**
|
||||||
|
```bash
|
||||||
|
export BEADS_AGENT_NAME=claude-frontend
|
||||||
|
export BEADS_AGENT_NAME=gpt4-backend
|
||||||
|
export BEADS_AGENT_NAME=alice-laptop
|
||||||
|
```
|
||||||
|
|
||||||
|
Makes debugging much easier!
|
||||||
|
|
||||||
|
### 2. Set Environment Variables Globally
|
||||||
|
|
||||||
|
**Option 1: Shell Profile**
|
||||||
|
```bash
|
||||||
|
# Add to ~/.bashrc or ~/.zshrc
|
||||||
|
export BEADS_AGENT_MAIL_URL=http://127.0.0.1:8765
|
||||||
|
export BEADS_AGENT_NAME=$(whoami)-$(hostname)
|
||||||
|
export BEADS_PROJECT_ID=$(basename $(pwd))
|
||||||
|
```
|
||||||
|
|
||||||
|
**Option 2: Project Config**
|
||||||
|
```bash
|
||||||
|
# .env file in project root
|
||||||
|
BEADS_AGENT_MAIL_URL=http://127.0.0.1:8765
|
||||||
|
BEADS_AGENT_NAME=my-agent
|
||||||
|
BEADS_PROJECT_ID=my-project
|
||||||
|
|
||||||
|
# Load in scripts
|
||||||
|
source .env
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Use Same Project ID Across Team
|
||||||
|
|
||||||
|
Create a shared config:
|
||||||
|
```bash
|
||||||
|
# team-config.sh
|
||||||
|
export BEADS_PROJECT_ID=our-team-project
|
||||||
|
export BEADS_AGENT_MAIL_URL=http://agent-mail.internal:8765
|
||||||
|
|
||||||
|
# Each team member sources it
|
||||||
|
source team-config.sh
|
||||||
|
export BEADS_AGENT_NAME=alice # Only this differs per person
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Monitor Reservations in Long-Running Agents
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Check reservation health periodically
|
||||||
|
import requests
|
||||||
|
|
||||||
|
def check_reservations():
|
||||||
|
resp = requests.get(f"{agent_mail_url}/api/reservations")
|
||||||
|
my_reservations = [r for r in resp.json() if r["agent_id"] == agent_name]
|
||||||
|
|
||||||
|
for res in my_reservations:
|
||||||
|
# Release if work completed
|
||||||
|
if is_done(res["resource_id"]):
|
||||||
|
requests.delete(f"{agent_mail_url}/api/reservations/{res['resource_id']}")
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Handle Graceful Degradation
|
||||||
|
|
||||||
|
Always assume Agent Mail might be unavailable:
|
||||||
|
```python
|
||||||
|
try:
|
||||||
|
bd_update(issue_id, status="in_progress")
|
||||||
|
except ReservationConflict:
|
||||||
|
# Expected - try different issue
|
||||||
|
pass
|
||||||
|
except Exception:
|
||||||
|
# Agent Mail down - falls back to git
|
||||||
|
# Continue normally
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
1. **Read the full guide**: [AGENT_MAIL.md](AGENT_MAIL.md)
|
||||||
|
2. **Try the Python example**: [examples/python-agent/AGENT_MAIL_EXAMPLE.md](../examples/python-agent/AGENT_MAIL_EXAMPLE.md)
|
||||||
|
3. **Review the ADR**: [adr/002-agent-mail-integration.md](adr/002-agent-mail-integration.md)
|
||||||
|
4. **Check out benchmarks**: [../latency_results.md](../latency_results.md)
|
||||||
|
|
||||||
|
## Getting Help
|
||||||
|
|
||||||
|
**Documentation:**
|
||||||
|
- [AGENT_MAIL.md](AGENT_MAIL.md) - Complete integration guide
|
||||||
|
- [TROUBLESHOOTING.md](TROUBLESHOOTING.md) - General bd troubleshooting
|
||||||
|
- [FAQ.md](FAQ.md) - Frequently asked questions
|
||||||
|
|
||||||
|
**Issues:**
|
||||||
|
- [bd issues](https://github.com/steveyegge/beads/issues) - Integration bugs
|
||||||
|
- [Agent Mail issues](https://github.com/Dicklesworthstone/mcp_agent_mail/issues) - Server bugs
|
||||||
|
|
||||||
|
**Community:**
|
||||||
|
- [Discussions](https://github.com/steveyegge/beads/discussions) - Ask questions
|
||||||
|
- [Examples](../examples/) - Learn from working code
|
||||||
|
|
||||||
|
## TL;DR - Copy-Paste Setup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Install Agent Mail
|
||||||
|
git clone https://github.com/Dicklesworthstone/mcp_agent_mail.git ~/mcp_agent_mail
|
||||||
|
cd ~/mcp_agent_mail
|
||||||
|
python3 -m venv .venv
|
||||||
|
source .venv/bin/activate
|
||||||
|
pip install -e .
|
||||||
|
|
||||||
|
# 2. Start server (leave running)
|
||||||
|
python -m mcp_agent_mail.cli serve-http &
|
||||||
|
|
||||||
|
# 3. Configure agent (in new terminal)
|
||||||
|
export BEADS_AGENT_MAIL_URL=http://127.0.0.1:8765
|
||||||
|
export BEADS_AGENT_NAME=my-agent
|
||||||
|
export BEADS_PROJECT_ID=my-project
|
||||||
|
|
||||||
|
# 4. Use bd normally - coordination happens automatically!
|
||||||
|
bd ready
|
||||||
|
bd update bd-42 --status in_progress
|
||||||
|
bd close bd-42 "Done"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Done!** You're now using Agent Mail for sub-100ms coordination.
|
||||||
@@ -5,6 +5,7 @@ This directory contains examples of how to integrate bd with AI agents and workf
|
|||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
- **[python-agent/](python-agent/)** - Simple Python agent that discovers ready work and completes tasks
|
- **[python-agent/](python-agent/)** - Simple Python agent that discovers ready work and completes tasks
|
||||||
|
- **[AGENT_MAIL_EXAMPLE.md](python-agent/AGENT_MAIL_EXAMPLE.md)** - Multi-agent coordination with Agent Mail
|
||||||
- **[bash-agent/](bash-agent/)** - Bash script showing the full agent workflow
|
- **[bash-agent/](bash-agent/)** - Bash script showing the full agent workflow
|
||||||
- **[monitor-webui/](monitor-webui/)** - Standalone web interface for real-time issue monitoring and visualization
|
- **[monitor-webui/](monitor-webui/)** - Standalone web interface for real-time issue monitoring and visualization
|
||||||
- **[markdown-to-jsonl/](markdown-to-jsonl/)** - Convert markdown planning docs to bd issues
|
- **[markdown-to-jsonl/](markdown-to-jsonl/)** - Convert markdown planning docs to bd issues
|
||||||
|
|||||||
418
examples/python-agent/AGENT_MAIL_EXAMPLE.md
Normal file
418
examples/python-agent/AGENT_MAIL_EXAMPLE.md
Normal file
@@ -0,0 +1,418 @@
|
|||||||
|
# Agent Mail Integration Example
|
||||||
|
|
||||||
|
This example demonstrates using bd with **Agent Mail** for multi-agent coordination. It shows how to handle reservation conflicts, graceful degradation, and best practices for real-time collaboration.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
1. **Install bd** (0.21.0+):
|
||||||
|
```bash
|
||||||
|
go install github.com/steveyegge/beads/cmd/bd@latest
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Install Agent Mail server**:
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/Dicklesworthstone/mcp_agent_mail.git
|
||||||
|
cd mcp_agent_mail
|
||||||
|
python3 -m venv .venv
|
||||||
|
source .venv/bin/activate
|
||||||
|
pip install -e .
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Initialize beads database**:
|
||||||
|
```bash
|
||||||
|
bd init --prefix bd
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Create some test issues**:
|
||||||
|
```bash
|
||||||
|
bd create "Implement login feature" -t feature -p 1
|
||||||
|
bd create "Add database migrations" -t task -p 1
|
||||||
|
bd create "Fix bug in auth flow" -t bug -p 0
|
||||||
|
bd create "Write integration tests" -t task -p 2
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage Scenarios
|
||||||
|
|
||||||
|
### Scenario 1: Single Agent (Git-Only Mode)
|
||||||
|
|
||||||
|
No Agent Mail server required. The agent works in traditional git-sync mode:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run agent without Agent Mail
|
||||||
|
./agent_with_mail.py --agent-name alice --project-id myproject
|
||||||
|
```
|
||||||
|
|
||||||
|
**What happens:**
|
||||||
|
- Agent finds ready work using `bd ready`
|
||||||
|
- Claims issues by updating status to `in_progress`
|
||||||
|
- Completes work and closes issues
|
||||||
|
- All coordination happens via git (2-5 second latency)
|
||||||
|
|
||||||
|
### Scenario 2: Multi-Agent with Agent Mail
|
||||||
|
|
||||||
|
Start the Agent Mail server and run multiple agents:
|
||||||
|
|
||||||
|
**Terminal 1 - Start Agent Mail server:**
|
||||||
|
```bash
|
||||||
|
cd ~/mcp_agent_mail
|
||||||
|
source .venv/bin/activate
|
||||||
|
python -m mcp_agent_mail.cli serve-http
|
||||||
|
# Server runs on http://127.0.0.1:8765
|
||||||
|
```
|
||||||
|
|
||||||
|
**Terminal 2 - First agent:**
|
||||||
|
```bash
|
||||||
|
./agent_with_mail.py \
|
||||||
|
--agent-name alice \
|
||||||
|
--project-id myproject \
|
||||||
|
--agent-mail-url http://127.0.0.1:8765 \
|
||||||
|
--max-iterations 5
|
||||||
|
```
|
||||||
|
|
||||||
|
**Terminal 3 - Second agent:**
|
||||||
|
```bash
|
||||||
|
./agent_with_mail.py \
|
||||||
|
--agent-name bob \
|
||||||
|
--project-id myproject \
|
||||||
|
--agent-mail-url http://127.0.0.1:8765 \
|
||||||
|
--max-iterations 5
|
||||||
|
```
|
||||||
|
|
||||||
|
**Terminal 4 - Monitor (optional):**
|
||||||
|
```bash
|
||||||
|
# Watch reservations in real-time
|
||||||
|
open http://127.0.0.1:8765/mail
|
||||||
|
```
|
||||||
|
|
||||||
|
**What happens:**
|
||||||
|
- Both agents query for ready work
|
||||||
|
- First agent to claim an issue gets exclusive reservation
|
||||||
|
- Second agent gets reservation conflict and tries different work
|
||||||
|
- Coordination happens in <100ms via Agent Mail
|
||||||
|
- No duplicate work, no git collisions
|
||||||
|
|
||||||
|
### Scenario 3: Environment Variables
|
||||||
|
|
||||||
|
Set Agent Mail configuration globally:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# In your shell profile (~/.bashrc, ~/.zshrc)
|
||||||
|
export BEADS_AGENT_MAIL_URL=http://127.0.0.1:8765
|
||||||
|
export BEADS_AGENT_NAME=my-agent
|
||||||
|
export BEADS_PROJECT_ID=my-project
|
||||||
|
|
||||||
|
# Now all bd commands use Agent Mail automatically
|
||||||
|
./agent_with_mail.py --max-iterations 3
|
||||||
|
```
|
||||||
|
|
||||||
|
### Scenario 4: Graceful Degradation
|
||||||
|
|
||||||
|
Start an agent with Agent Mail enabled, then stop the server mid-run:
|
||||||
|
|
||||||
|
**Terminal 1:**
|
||||||
|
```bash
|
||||||
|
# Start server
|
||||||
|
cd ~/mcp_agent_mail
|
||||||
|
source .venv/bin/activate
|
||||||
|
python -m mcp_agent_mail.cli serve-http
|
||||||
|
```
|
||||||
|
|
||||||
|
**Terminal 2:**
|
||||||
|
```bash
|
||||||
|
# Start agent
|
||||||
|
./agent_with_mail.py \
|
||||||
|
--agent-name charlie \
|
||||||
|
--agent-mail-url http://127.0.0.1:8765 \
|
||||||
|
--max-iterations 10
|
||||||
|
```
|
||||||
|
|
||||||
|
**Terminal 1 (after a few iterations):**
|
||||||
|
```bash
|
||||||
|
# Stop server (Ctrl+C)
|
||||||
|
^C
|
||||||
|
```
|
||||||
|
|
||||||
|
**What happens:**
|
||||||
|
- Agent starts in Agent Mail mode (<100ms latency)
|
||||||
|
- After server stops, agent automatically falls back to git-only mode
|
||||||
|
- No errors, no crashes - work continues normally
|
||||||
|
- Only difference is increased latency (2-5 seconds)
|
||||||
|
|
||||||
|
## Example Output
|
||||||
|
|
||||||
|
### With Agent Mail (Successful Reservation)
|
||||||
|
|
||||||
|
```
|
||||||
|
✨ Agent Mail enabled: alice @ http://127.0.0.1:8765
|
||||||
|
|
||||||
|
🚀 Agent 'alice' starting...
|
||||||
|
Project: myproject
|
||||||
|
Agent Mail: Enabled
|
||||||
|
|
||||||
|
============================================================
|
||||||
|
Iteration 1/5
|
||||||
|
============================================================
|
||||||
|
|
||||||
|
📋 Claiming issue: bd-42
|
||||||
|
✅ Successfully claimed bd-42
|
||||||
|
🤖 Working on: Implement login feature (bd-42)
|
||||||
|
Priority: 1, Type: feature
|
||||||
|
💡 Creating discovered issue: Follow-up work for Implement login feature
|
||||||
|
✅ Created bd-43
|
||||||
|
🔗 Linked bd-43 ← discovered-from ← bd-42
|
||||||
|
✅ Completing issue: bd-42
|
||||||
|
✅ Issue bd-42 completed
|
||||||
|
```
|
||||||
|
|
||||||
|
### With Agent Mail (Reservation Conflict)
|
||||||
|
|
||||||
|
```
|
||||||
|
✨ Agent Mail enabled: bob @ http://127.0.0.1:8765
|
||||||
|
|
||||||
|
🚀 Agent 'bob' starting...
|
||||||
|
Project: myproject
|
||||||
|
Agent Mail: Enabled
|
||||||
|
|
||||||
|
============================================================
|
||||||
|
Iteration 1/5
|
||||||
|
============================================================
|
||||||
|
|
||||||
|
📋 Claiming issue: bd-42
|
||||||
|
⚠️ Reservation conflict: Error: bd-42 already reserved by alice
|
||||||
|
⚠️ Issue bd-42 already claimed by another agent
|
||||||
|
📋 Claiming issue: bd-44
|
||||||
|
✅ Successfully claimed bd-44
|
||||||
|
🤖 Working on: Write integration tests (bd-44)
|
||||||
|
Priority: 2, Type: task
|
||||||
|
```
|
||||||
|
|
||||||
|
### Git-Only Mode (No Agent Mail)
|
||||||
|
|
||||||
|
```
|
||||||
|
📝 Git-only mode: charlie
|
||||||
|
|
||||||
|
🚀 Agent 'charlie' starting...
|
||||||
|
Project: myproject
|
||||||
|
Agent Mail: Disabled (git-only mode)
|
||||||
|
|
||||||
|
============================================================
|
||||||
|
Iteration 1/5
|
||||||
|
============================================================
|
||||||
|
|
||||||
|
📋 Claiming issue: bd-42
|
||||||
|
✅ Successfully claimed bd-42
|
||||||
|
🤖 Working on: Implement login feature (bd-42)
|
||||||
|
Priority: 1, Type: feature
|
||||||
|
```
|
||||||
|
|
||||||
|
## Code Walkthrough
|
||||||
|
|
||||||
|
### Key Methods
|
||||||
|
|
||||||
|
**`__init__`**: Configure Agent Mail environment variables
|
||||||
|
```python
|
||||||
|
if self.agent_mail_url:
|
||||||
|
os.environ["BEADS_AGENT_MAIL_URL"] = self.agent_mail_url
|
||||||
|
os.environ["BEADS_AGENT_NAME"] = self.agent_name
|
||||||
|
os.environ["BEADS_PROJECT_ID"] = self.project_id
|
||||||
|
```
|
||||||
|
|
||||||
|
**`run_bd`**: Execute bd commands with error handling
|
||||||
|
```python
|
||||||
|
result = subprocess.run(["bd"] + list(args) + ["--json"], ...)
|
||||||
|
if "already reserved" in result.stderr:
|
||||||
|
return {"error": "reservation_conflict"}
|
||||||
|
```
|
||||||
|
|
||||||
|
**`claim_issue`**: Try to claim an issue, handle conflicts
|
||||||
|
```python
|
||||||
|
result = self.run_bd("update", issue_id, "--status", "in_progress")
|
||||||
|
if result["error"] == "reservation_conflict":
|
||||||
|
return False # Try different issue
|
||||||
|
```
|
||||||
|
|
||||||
|
**`complete_issue`**: Close issue and release reservation
|
||||||
|
```python
|
||||||
|
self.run_bd("close", issue_id, "--reason", reason)
|
||||||
|
# Agent Mail automatically releases reservation
|
||||||
|
```
|
||||||
|
|
||||||
|
### Error Handling
|
||||||
|
|
||||||
|
The agent handles three types of failures:
|
||||||
|
|
||||||
|
1. **Reservation conflicts** - Expected in multi-agent workflows:
|
||||||
|
```python
|
||||||
|
if "reservation_conflict" in result:
|
||||||
|
print("⚠️ Issue already claimed by another agent")
|
||||||
|
return False # Try different work
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Agent Mail unavailable** - Graceful degradation:
|
||||||
|
```python
|
||||||
|
# bd automatically falls back to git-only mode
|
||||||
|
# No special handling needed!
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Command failures** - General errors:
|
||||||
|
```python
|
||||||
|
if returncode != 0:
|
||||||
|
print(f"❌ Command failed: {stderr}")
|
||||||
|
return {"error": "command_failed"}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Integration Tips
|
||||||
|
|
||||||
|
### Real LLM Agents
|
||||||
|
|
||||||
|
To integrate with Claude, GPT-4, or other LLMs:
|
||||||
|
|
||||||
|
1. **Replace `simulate_work()` with LLM calls**:
|
||||||
|
```python
|
||||||
|
def simulate_work(self, issue: Dict[str, Any]) -> None:
|
||||||
|
# Call LLM with issue context
|
||||||
|
prompt = f"Implement: {issue['title']}\nDescription: {issue['description']}"
|
||||||
|
response = llm_client.generate(prompt)
|
||||||
|
|
||||||
|
# Parse response for new issues/bugs
|
||||||
|
if "TODO" in response or "BUG" in response:
|
||||||
|
self.create_discovered_issue(
|
||||||
|
"Found during work",
|
||||||
|
issue["id"]
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Use issue IDs for conversation context**:
|
||||||
|
```python
|
||||||
|
# Track conversation history per issue
|
||||||
|
conversation_history[issue["id"]].append({
|
||||||
|
"role": "user",
|
||||||
|
"content": issue["description"]
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Export state after each iteration**:
|
||||||
|
```python
|
||||||
|
# Ensure git state is synced
|
||||||
|
subprocess.run(["bd", "sync"])
|
||||||
|
```
|
||||||
|
|
||||||
|
### CI/CD Integration
|
||||||
|
|
||||||
|
Run agents in GitHub Actions with Agent Mail:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
jobs:
|
||||||
|
agent-workflow:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
services:
|
||||||
|
agent-mail:
|
||||||
|
image: ghcr.io/dicklesworthstone/mcp_agent_mail:latest
|
||||||
|
ports:
|
||||||
|
- 8765:8765
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
agent: [alice, bob, charlie]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Run agent
|
||||||
|
env:
|
||||||
|
BEADS_AGENT_MAIL_URL: http://localhost:8765
|
||||||
|
BEADS_AGENT_NAME: ${{ matrix.agent }}
|
||||||
|
BEADS_PROJECT_ID: ${{ github.repository }}
|
||||||
|
run: |
|
||||||
|
./examples/python-agent/agent_with_mail.py --max-iterations 3
|
||||||
|
```
|
||||||
|
|
||||||
|
### Monitoring & Debugging
|
||||||
|
|
||||||
|
**View reservations in real-time:**
|
||||||
|
```bash
|
||||||
|
# Web UI
|
||||||
|
open http://127.0.0.1:8765/mail
|
||||||
|
|
||||||
|
# API
|
||||||
|
curl http://127.0.0.1:8765/api/reservations | jq
|
||||||
|
```
|
||||||
|
|
||||||
|
**Check Agent Mail connectivity:**
|
||||||
|
```bash
|
||||||
|
# Health check
|
||||||
|
curl http://127.0.0.1:8765/health
|
||||||
|
|
||||||
|
# Test reservation
|
||||||
|
curl -X POST http://127.0.0.1:8765/api/reservations \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"resource_id": "bd-test", "agent_id": "test-agent", "project_id": "test"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Debug agent behavior:**
|
||||||
|
```bash
|
||||||
|
# Increase verbosity
|
||||||
|
./agent_with_mail.py --agent-name debug-agent --max-iterations 1
|
||||||
|
|
||||||
|
# Check bd Agent Mail status
|
||||||
|
bd info --json | grep -A5 agent_mail
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common Issues
|
||||||
|
|
||||||
|
### "Agent Mail unavailable" warnings
|
||||||
|
|
||||||
|
**Cause:** Server not running or wrong URL
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
```bash
|
||||||
|
# Verify server is running
|
||||||
|
curl http://127.0.0.1:8765/health
|
||||||
|
|
||||||
|
# Check environment variables
|
||||||
|
echo $BEADS_AGENT_MAIL_URL
|
||||||
|
echo $BEADS_AGENT_NAME
|
||||||
|
echo $BEADS_PROJECT_ID
|
||||||
|
```
|
||||||
|
|
||||||
|
### Reservations not released after crash
|
||||||
|
|
||||||
|
**Cause:** Agent crashed before calling `bd close`
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
```bash
|
||||||
|
# Manual release via API
|
||||||
|
curl -X DELETE http://127.0.0.1:8765/api/reservations/bd-42
|
||||||
|
|
||||||
|
# Or restart server (clears all ephemeral state)
|
||||||
|
pkill -f mcp_agent_mail
|
||||||
|
python -m mcp_agent_mail.cli serve-http
|
||||||
|
```
|
||||||
|
|
||||||
|
### Agents don't see each other's reservations
|
||||||
|
|
||||||
|
**Cause:** Different `BEADS_PROJECT_ID` values
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
```bash
|
||||||
|
# Ensure all agents use SAME project ID
|
||||||
|
export BEADS_PROJECT_ID=my-project # All agents must use this!
|
||||||
|
|
||||||
|
# Verify
|
||||||
|
./agent_with_mail.py --agent-name alice &
|
||||||
|
./agent_with_mail.py --agent-name bob &
|
||||||
|
# Both should coordinate on same namespace
|
||||||
|
```
|
||||||
|
|
||||||
|
## See Also
|
||||||
|
|
||||||
|
- [../../docs/AGENT_MAIL.md](../../docs/AGENT_MAIL.md) - Complete Agent Mail integration guide
|
||||||
|
- [../../docs/adr/002-agent-mail-integration.md](../../docs/adr/002-agent-mail-integration.md) - Architecture decision record
|
||||||
|
- [agent.py](agent.py) - Original agent example (git-only mode)
|
||||||
|
- [Agent Mail Repository](https://github.com/Dicklesworthstone/mcp_agent_mail)
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Apache 2.0 (same as beads)
|
||||||
@@ -84,5 +84,6 @@ tree = agent.run_bd("dep", "tree", "bd-1")
|
|||||||
|
|
||||||
## See Also
|
## See Also
|
||||||
|
|
||||||
|
- [AGENT_MAIL_EXAMPLE.md](AGENT_MAIL_EXAMPLE.md) - Multi-agent coordination with Agent Mail
|
||||||
- [../bash-agent/](../bash-agent/) - Bash version of this example
|
- [../bash-agent/](../bash-agent/) - Bash version of this example
|
||||||
- [../claude-desktop-mcp/](../claude-desktop-mcp/) - MCP server for Claude Desktop
|
- [../claude-desktop-mcp/](../claude-desktop-mcp/) - MCP server for Claude Desktop
|
||||||
|
|||||||
288
examples/python-agent/agent_with_mail.py
Executable file
288
examples/python-agent/agent_with_mail.py
Executable file
@@ -0,0 +1,288 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Beads Agent with Agent Mail Integration Example
|
||||||
|
|
||||||
|
Demonstrates how to use bd with optional Agent Mail coordination for multi-agent workflows.
|
||||||
|
Shows collision handling, graceful degradation, and best practices.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
from typing import Optional, Dict, Any, List
|
||||||
|
|
||||||
|
|
||||||
|
class BeadsAgent:
|
||||||
|
"""A simple agent that uses bd with optional Agent Mail coordination."""
|
||||||
|
|
||||||
|
def __init__(self, agent_name: str, project_id: str, agent_mail_url: Optional[str] = None):
|
||||||
|
"""
|
||||||
|
Initialize the agent.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
agent_name: Unique identifier for this agent (e.g., "assistant-alpha")
|
||||||
|
project_id: Project namespace for Agent Mail
|
||||||
|
agent_mail_url: Agent Mail server URL (optional, e.g., "http://127.0.0.1:8765")
|
||||||
|
"""
|
||||||
|
self.agent_name = agent_name
|
||||||
|
self.project_id = project_id
|
||||||
|
self.agent_mail_url = agent_mail_url
|
||||||
|
|
||||||
|
# Configure environment for Agent Mail if URL provided
|
||||||
|
if self.agent_mail_url:
|
||||||
|
os.environ["BEADS_AGENT_MAIL_URL"] = self.agent_mail_url
|
||||||
|
os.environ["BEADS_AGENT_NAME"] = self.agent_name
|
||||||
|
os.environ["BEADS_PROJECT_ID"] = self.project_id
|
||||||
|
print(f"✨ Agent Mail enabled: {agent_name} @ {agent_mail_url}")
|
||||||
|
else:
|
||||||
|
print(f"📝 Git-only mode: {agent_name}")
|
||||||
|
|
||||||
|
def run_bd(self, *args) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Run a bd command and return parsed JSON output.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
*args: Command arguments (e.g., "ready", "--json")
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Parsed JSON output from bd
|
||||||
|
"""
|
||||||
|
cmd = ["bd"] + list(args)
|
||||||
|
if "--json" not in args:
|
||||||
|
cmd.append("--json")
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
cmd,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
check=False # Don't raise on non-zero exit
|
||||||
|
)
|
||||||
|
|
||||||
|
# Handle reservation conflicts gracefully
|
||||||
|
if result.returncode != 0:
|
||||||
|
# Check if it's a reservation conflict
|
||||||
|
if "already reserved" in result.stderr or "reservation conflict" in result.stderr:
|
||||||
|
print(f"⚠️ Reservation conflict: {result.stderr.strip()}")
|
||||||
|
return {"error": "reservation_conflict", "stderr": result.stderr}
|
||||||
|
else:
|
||||||
|
print(f"❌ Command failed: {' '.join(cmd)}")
|
||||||
|
print(f" Error: {result.stderr}")
|
||||||
|
return {"error": "command_failed", "stderr": result.stderr}
|
||||||
|
|
||||||
|
# Parse JSON output
|
||||||
|
if result.stdout.strip():
|
||||||
|
return json.loads(result.stdout)
|
||||||
|
else:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
print(f"❌ Failed to parse JSON from bd: {e}")
|
||||||
|
print(f" Output: {result.stdout}")
|
||||||
|
return {"error": "json_parse_failed"}
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Failed to run bd: {e}")
|
||||||
|
return {"error": str(e)}
|
||||||
|
|
||||||
|
def get_ready_work(self) -> List[Dict[str, Any]]:
|
||||||
|
"""Get list of unblocked issues ready to work on."""
|
||||||
|
result = self.run_bd("ready", "--json")
|
||||||
|
|
||||||
|
if "error" in result:
|
||||||
|
return []
|
||||||
|
|
||||||
|
# bd ready returns array of issues
|
||||||
|
if isinstance(result, list):
|
||||||
|
return result
|
||||||
|
else:
|
||||||
|
return []
|
||||||
|
|
||||||
|
def claim_issue(self, issue_id: str) -> bool:
|
||||||
|
"""
|
||||||
|
Claim an issue by setting status to in_progress.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if successful, False if reservation conflict or error
|
||||||
|
"""
|
||||||
|
print(f"📋 Claiming issue: {issue_id}")
|
||||||
|
result = self.run_bd("update", issue_id, "--status", "in_progress")
|
||||||
|
|
||||||
|
if "error" in result:
|
||||||
|
if result["error"] == "reservation_conflict":
|
||||||
|
print(f" ⚠️ Issue {issue_id} already claimed by another agent")
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
print(f" ❌ Failed to claim {issue_id}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
print(f" ✅ Successfully claimed {issue_id}")
|
||||||
|
return True
|
||||||
|
|
||||||
|
def complete_issue(self, issue_id: str, reason: str = "Completed") -> bool:
|
||||||
|
"""
|
||||||
|
Complete an issue and release reservation.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if successful, False otherwise
|
||||||
|
"""
|
||||||
|
print(f"✅ Completing issue: {issue_id}")
|
||||||
|
result = self.run_bd("close", issue_id, "--reason", reason)
|
||||||
|
|
||||||
|
if "error" in result:
|
||||||
|
print(f" ❌ Failed to complete {issue_id}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
print(f" ✅ Issue {issue_id} completed")
|
||||||
|
return True
|
||||||
|
|
||||||
|
def create_discovered_issue(
|
||||||
|
self,
|
||||||
|
title: str,
|
||||||
|
parent_id: str,
|
||||||
|
priority: int = 2,
|
||||||
|
issue_type: str = "task"
|
||||||
|
) -> Optional[str]:
|
||||||
|
"""
|
||||||
|
Create an issue discovered during work on another issue.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
title: Issue title
|
||||||
|
parent_id: ID of the issue this was discovered from
|
||||||
|
priority: Priority level (0-4)
|
||||||
|
issue_type: Issue type (bug, feature, task, etc.)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
New issue ID if successful, None otherwise
|
||||||
|
"""
|
||||||
|
print(f"💡 Creating discovered issue: {title}")
|
||||||
|
result = self.run_bd(
|
||||||
|
"create",
|
||||||
|
title,
|
||||||
|
"-t", issue_type,
|
||||||
|
"-p", str(priority),
|
||||||
|
"--deps", f"discovered-from:{parent_id}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if "error" in result or "id" not in result:
|
||||||
|
print(f" ❌ Failed to create issue")
|
||||||
|
return None
|
||||||
|
|
||||||
|
new_id = result["id"]
|
||||||
|
print(f" ✅ Created {new_id}")
|
||||||
|
return new_id
|
||||||
|
|
||||||
|
def simulate_work(self, issue: Dict[str, Any]) -> None:
|
||||||
|
"""Simulate working on an issue."""
|
||||||
|
print(f"🤖 Working on: {issue['title']} ({issue['id']})")
|
||||||
|
print(f" Priority: {issue['priority']}, Type: {issue['issue_type']}")
|
||||||
|
time.sleep(1) # Simulate work
|
||||||
|
|
||||||
|
def run(self, max_iterations: int = 10) -> None:
|
||||||
|
"""
|
||||||
|
Main agent loop: find work, claim it, complete it.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
max_iterations: Maximum number of issues to process
|
||||||
|
"""
|
||||||
|
print(f"\n🚀 Agent '{self.agent_name}' starting...")
|
||||||
|
print(f" Project: {self.project_id}")
|
||||||
|
print(f" Agent Mail: {'Enabled' if self.agent_mail_url else 'Disabled (git-only mode)'}\n")
|
||||||
|
|
||||||
|
for iteration in range(1, max_iterations + 1):
|
||||||
|
print("=" * 60)
|
||||||
|
print(f"Iteration {iteration}/{max_iterations}")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
# Get ready work
|
||||||
|
ready_issues = self.get_ready_work()
|
||||||
|
|
||||||
|
if not ready_issues:
|
||||||
|
print("📭 No ready work available. Stopping.")
|
||||||
|
break
|
||||||
|
|
||||||
|
# Sort by priority (lower number = higher priority)
|
||||||
|
ready_issues.sort(key=lambda x: x.get("priority", 99))
|
||||||
|
|
||||||
|
# Try to claim the highest priority issue
|
||||||
|
claimed = False
|
||||||
|
for issue in ready_issues:
|
||||||
|
if self.claim_issue(issue["id"]):
|
||||||
|
claimed = True
|
||||||
|
|
||||||
|
# Simulate work
|
||||||
|
self.simulate_work(issue)
|
||||||
|
|
||||||
|
# Randomly discover new work (33% chance)
|
||||||
|
import random
|
||||||
|
if random.random() < 0.33:
|
||||||
|
discovered_title = f"Follow-up work for {issue['title']}"
|
||||||
|
new_id = self.create_discovered_issue(
|
||||||
|
discovered_title,
|
||||||
|
issue["id"],
|
||||||
|
priority=issue.get("priority", 2)
|
||||||
|
)
|
||||||
|
if new_id:
|
||||||
|
print(f"🔗 Linked {new_id} ← discovered-from ← {issue['id']}")
|
||||||
|
|
||||||
|
# Complete the issue
|
||||||
|
self.complete_issue(issue["id"], "Implemented successfully")
|
||||||
|
break
|
||||||
|
|
||||||
|
if not claimed:
|
||||||
|
print("⚠️ All ready issues are reserved by other agents. Waiting...")
|
||||||
|
time.sleep(2) # Wait before retrying
|
||||||
|
|
||||||
|
print()
|
||||||
|
|
||||||
|
print(f"🏁 Agent '{self.agent_name}' finished after {iteration} iterations.")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Main entry point."""
|
||||||
|
# Parse command line arguments
|
||||||
|
import argparse
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="Beads agent with optional Agent Mail coordination"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--agent-name",
|
||||||
|
default=os.getenv("BEADS_AGENT_NAME", f"agent-{os.getpid()}"),
|
||||||
|
help="Unique agent identifier (default: agent-<pid>)"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--project-id",
|
||||||
|
default=os.getenv("BEADS_PROJECT_ID", "default"),
|
||||||
|
help="Project namespace for Agent Mail"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--agent-mail-url",
|
||||||
|
default=os.getenv("BEADS_AGENT_MAIL_URL"),
|
||||||
|
help="Agent Mail server URL (optional, e.g., http://127.0.0.1:8765)"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--max-iterations",
|
||||||
|
type=int,
|
||||||
|
default=10,
|
||||||
|
help="Maximum number of issues to process (default: 10)"
|
||||||
|
)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# Create and run agent
|
||||||
|
agent = BeadsAgent(
|
||||||
|
agent_name=args.agent_name,
|
||||||
|
project_id=args.project_id,
|
||||||
|
agent_mail_url=args.agent_mail_url
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
agent.run(max_iterations=args.max_iterations)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\n\n⚠️ Agent interrupted by user. Exiting...")
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user