Files
beads/examples/bash-agent/agent.sh
Steve Yegge c3856bb140 Add Agent Mail documentation and bash-agent integration
- Added Agent Mail section to QUICKSTART.md with benefits and setup
- Integrated Agent Mail into bash-agent example with reservation/notification
- Added multi-agent usage instructions to bash-agent README
- Closed bd-eimz (QUICKSTART), bd-fkdw (bash-agent), bd-sc57 (production)
- Completed bd-nl8z documentation epic

Amp-Thread-ID: https://ampcode.com/threads/T-5b0d67ff-5eb2-41b3-bc9b-7f33719e0c85
Co-authored-by: Amp <amp@ampcode.com>
2025-11-08 01:10:20 -08:00

290 lines
7.7 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
#
# Simple AI agent workflow using bd (Beads issue tracker).
#
# This demonstrates the full lifecycle of an agent managing tasks:
# - Find ready work
# - Claim and execute (with optional Agent Mail reservation)
# - Discover new issues
# - Link discoveries
# - Complete and move on
#
# Optional Agent Mail integration:
# export BEADS_AGENT_MAIL_URL=http://127.0.0.1:8765
# export BEADS_AGENT_NAME=bash-agent-1
# export BEADS_PROJECT_ID=my-project
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
log_info() {
echo -e "${BLUE} ${NC}$1"
}
log_success() {
echo -e "${GREEN}${NC} $1"
}
log_warning() {
echo -e "${YELLOW}${NC} $1"
}
log_error() {
echo -e "${RED}${NC} $1"
}
# Agent Mail integration (optional, graceful degradation)
AGENT_MAIL_ENABLED=false
AGENT_MAIL_URL="${BEADS_AGENT_MAIL_URL:-}"
AGENT_NAME="${BEADS_AGENT_NAME:-bash-agent-$$}"
PROJECT_ID="${BEADS_PROJECT_ID:-default}"
# Check Agent Mail health
check_agent_mail() {
if [[ -z "$AGENT_MAIL_URL" ]]; then
return 1
fi
if ! command -v curl &> /dev/null; then
log_warning "curl not available, Agent Mail disabled"
return 1
fi
if curl -s -f "$AGENT_MAIL_URL/health" > /dev/null 2>&1; then
return 0
fi
return 1
}
# Reserve an issue (prevents other agents from claiming)
reserve_issue() {
local issue_id="$1"
if ! $AGENT_MAIL_ENABLED; then
return 0 # Skip if disabled
fi
local response=$(curl -s -X POST "$AGENT_MAIL_URL/api/reserve" \
-H "Content-Type: application/json" \
-d "{\"file_path\": \"$issue_id\", \"agent_name\": \"$AGENT_NAME\", \"project_id\": \"$PROJECT_ID\", \"ttl_seconds\": 300}" \
2>/dev/null || echo "")
if [[ -z "$response" ]] || echo "$response" | grep -q "error"; then
log_warning "Failed to reserve $issue_id (may be claimed by another agent)"
return 1
fi
return 0
}
# Release an issue reservation
release_issue() {
local issue_id="$1"
if ! $AGENT_MAIL_ENABLED; then
return 0 # Skip if disabled
fi
curl -s -X POST "$AGENT_MAIL_URL/api/release" \
-H "Content-Type: application/json" \
-d "{\"file_path\": \"$issue_id\", \"agent_name\": \"$AGENT_NAME\", \"project_id\": \"$PROJECT_ID\"}" \
> /dev/null 2>&1 || true
}
# Send notification to other agents
notify() {
local event_type="$1"
local issue_id="$2"
local message="$3"
if ! $AGENT_MAIL_ENABLED; then
return 0 # Skip if disabled
fi
curl -s -X POST "$AGENT_MAIL_URL/api/notify" \
-H "Content-Type: application/json" \
-d "{\"event_type\": \"$event_type\", \"from_agent\": \"$AGENT_NAME\", \"project_id\": \"$PROJECT_ID\", \"payload\": {\"issue_id\": \"$issue_id\", \"message\": \"$message\"}}" \
> /dev/null 2>&1 || true
}
# Initialize Agent Mail
if check_agent_mail; then
AGENT_MAIL_ENABLED=true
log_success "Agent Mail enabled (agent: $AGENT_NAME)"
else
log_info "Agent Mail disabled (Beads-only mode)"
fi
# Check if bd is installed
if ! command -v bd &> /dev/null; then
log_error "bd is not installed"
echo "Install with: go install github.com/steveyegge/beads/cmd/bd@latest"
exit 1
fi
# Check if we're in a beads-initialized directory
if ! bd list &> /dev/null; then
log_error "Not in a beads-initialized directory"
echo "Run: bd init"
exit 1
fi
# Find ready work
find_ready_work() {
bd ready --json --limit 1 2>/dev/null | jq -r '.[0] // empty'
}
# Extract field from JSON
get_field() {
local json="$1"
local field="$2"
echo "$json" | jq -r ".$field"
}
# Claim a task
claim_task() {
local issue_id="$1"
# Try to reserve first (Agent Mail)
if ! reserve_issue "$issue_id"; then
log_error "Could not reserve $issue_id - skipping"
return 1
fi
log_info "Claiming task: $issue_id"
bd update "$issue_id" --status in_progress --json > /dev/null
# Notify other agents
notify "status_changed" "$issue_id" "Claimed by $AGENT_NAME"
log_success "Task claimed"
return 0
}
# Simulate doing work (in real agent, this would call LLM/execute code)
do_work() {
local issue="$1"
local issue_id=$(get_field "$issue" "id")
local title=$(get_field "$issue" "title")
local priority=$(get_field "$issue" "priority")
echo ""
log_info "Working on: $title ($issue_id)"
echo " Priority: $priority"
# Simulate work delay
sleep 1
# Simulate discovering new work (50% chance)
if [[ $((RANDOM % 2)) -eq 0 ]]; then
log_warning "Discovered issue while working!"
# Create new issue
local new_issue=$(bd create "Follow-up: $title" \
-d "Discovered while working on $issue_id" \
-p 2 \
-t task \
--json)
local new_id=$(echo "$new_issue" | jq -r '.id')
log_success "Created issue: $new_id"
# Link it back to parent
bd dep add "$new_id" "$issue_id" --type discovered-from
log_success "Linked $new_id ← discovered-from ← $issue_id"
return 0 # Discovered new work
fi
return 1 # No new work discovered
}
# Complete a task
complete_task() {
local issue_id="$1"
local reason="${2:-Completed successfully}"
log_info "Completing task: $issue_id"
bd close "$issue_id" --reason "$reason" --json > /dev/null
# Notify completion and release reservation
notify "issue_completed" "$issue_id" "$reason"
release_issue "$issue_id"
log_success "Task completed: $issue_id"
}
# Show statistics
show_stats() {
echo ""
echo "═══════════════════════════════════════════════════"
echo " Beads Statistics"
echo "═══════════════════════════════════════════════════"
bd stats
echo ""
}
# Main agent loop
run_agent() {
local max_iterations="${1:-10}"
local iteration=0
echo ""
echo "🚀 Beads Agent starting..."
echo " Max iterations: $max_iterations"
show_stats
while [[ $iteration -lt $max_iterations ]]; do
iteration=$((iteration + 1))
echo ""
echo "═══════════════════════════════════════════════════"
echo " Iteration $iteration/$max_iterations"
echo "═══════════════════════════════════════════════════"
# Find ready work
log_info "Looking for ready work..."
ready_work=$(find_ready_work)
if [[ -z "$ready_work" ]]; then
log_warning "No ready work found. Agent idle."
break
fi
issue_id=$(get_field "$ready_work" "id")
# Claim it (may fail if another agent reserved it)
if ! claim_task "$issue_id"; then
log_warning "Skipping already-claimed task, trying next iteration"
continue
fi
# Do the work
if do_work "$ready_work"; then
log_info "New work discovered, will process in next iteration"
fi
# Complete it
complete_task "$issue_id"
# Brief pause between iterations
sleep 0.5
done
echo ""
log_success "Agent finished after $iteration iterations"
show_stats
}
# Handle Ctrl-C gracefully
trap 'echo ""; log_warning "Agent interrupted by user"; exit 130' INT
# Run the agent
run_agent "${1:-10}"