# Pulse & Brain Architecture ⭐ **The most efficient way to run an agent with proactive monitoring.** > **Note:** The old Heartbeat system is now **legacy** and disabled by default. Use Pulse & Brain for all new deployments. --- ## πŸš€ Quick Start Copy-paste ready setup in under 50 lines: ### Basic Monitoring (Zero-cost pulse, conditional agent) ```python from agent import Agent from pulse_brain import PulseBrain # Initialize agent (disable old heartbeat) agent = Agent(provider="claude", enable_heartbeat=False) # Create Pulse & Brain with 60-second pulse interval pb = PulseBrain(agent, pulse_interval=60) # Start monitoring pb.start() ``` ### With Slack/Telegram Integration ```python from adapters.runtime import AdapterRuntime from adapters.slack.adapter import SlackAdapter from pulse_brain import PulseBrain agent = Agent(provider="claude", enable_heartbeat=False) # Set up messaging adapters slack = SlackAdapter(bot_token="xoxb-...", channel="C_MONITORING") runtime = AdapterRuntime(agent) runtime.add_adapter(slack) # Create Pulse & Brain with adapter support pb = PulseBrain(agent, pulse_interval=60) pb.add_adapter("slack", slack) # Start both systems await runtime.start() pb.start() ``` ### Custom Pulse Check (Zero Cost) ```python from pulse_brain import PulseCheck, BrainTask, CheckType def check_my_server(): """Pure Python check - no agent, no cost.""" import requests try: r = requests.get("http://localhost:8000/health", timeout=5) return {"status": "ok" if r.status_code == 200 else "error"} except: return {"status": "error", "message": "Server down"} # Add to Pulse & Brain pb = PulseBrain(agent) pb.add_pulse_check(PulseCheck("my-server", check_my_server, interval_seconds=60)) # Only invoke agent when server is down pb.add_brain_task(BrainTask( name="server-fixer", check_type=CheckType.CONDITIONAL, prompt_template="Server is down! What should I check?", condition_func=lambda data: data.get("status") == "error" )) pb.start() ``` **That's it!** Your agent now monitors your system 24/7 at ~$1-2/month. --- ## 🎯 The Problem Running an agent in a loop is expensive: ```python # ❌ EXPENSIVE: Agent asks "What should I do?" every loop while True: response = agent.chat("What should I do?") # Costs tokens! time.sleep(60) ``` **Cost:** If you check every minute for 24 hours: - 1,440 API calls/day - ~50,000 tokens/day - ~$0.50/day just to ask "nothing to do" ## βœ… The Solution: Pulse & Brain Think of it like a **security guard**: - **Pulse (Guard)**: Walks the perimeter every 60 seconds. Checks doors (pure Python). **Cost: $0** - **Brain (Manager)**: Only called when guard sees a problem or it's time for the morning report. **Cost: Only when needed** ```python # βœ… EFFICIENT: Agent only invoked when needed while True: # Pulse: Pure Python checks (zero cost) disk_check = check_disk_space() # $0 log_check = check_for_errors() # $0 task_check = check_stale_tasks() # $0 # Brain: Only if something needs attention if disk_check.status == "error": agent.chat("Disk space critical!") # Costs tokens (but only when needed) if current_time == "08:00": agent.chat("Morning briefing") # Costs tokens (scheduled) time.sleep(60) ``` ## πŸ“Š Cost Comparison ### Old Heartbeat System (Always Uses Agent) ```python # Every 30 minutes, agent processes checklist while True: response = agent.chat(checklist) # ~1000 tokens time.sleep(1800) # 30 min ``` **Cost per day:** - 48 checks/day - ~48,000 tokens/day - ~$0.48/day ### Pulse & Brain (Conditional Agent) ```python # Every 60 seconds, pure Python checks (zero cost) # Agent only invoked when: # 1. Error detected (~2x/day) # 2. Scheduled briefings (2x/day) # = ~4 agent calls/day ``` **Cost per day:** - 1,440 pulse checks (pure Python) = **$0** - 4 brain invocations (~4,000 tokens) = **$0.04/day** **Savings: 92%** πŸ’° --- ## πŸ—οΈ Architecture ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ PULSE LOOP β”‚ β”‚ (Pure Python, $0 cost) β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ Disk Spaceβ”‚ β”‚ Log Errorsβ”‚ β”‚ Tasks β”‚ β”‚ β”‚ β”‚ Check β”‚ β”‚ Check β”‚ β”‚ Check β”‚ ... β”‚ β”‚ β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ Conditions? β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β–Όβ”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β–Όβ”€β”€β”€β”€β” β”‚ β”‚ β”‚ Error? β”‚ β”‚ 8:00 AM?β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ YES β”‚ YES β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ BRAIN β”‚ β”‚ (Agent/SDK) β”‚ β”‚ COSTS TOKENS β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` --- ## πŸ“ Default Checks ### Pulse Checks (Zero Cost) | Check | Interval | What It Does | |-------|----------|--------------| | `disk-space` | 5 min | Checks disk usage, warns >80% | | `memory-tasks` | 10 min | Counts pending tasks | | `log-errors` | 1 min | Scans logs for errors | ### Brain Tasks (Uses Tokens) | Task | Type | Trigger | |------|------|---------| | `disk-space-advisor` | Conditional | Disk >90% used | | `error-analyst` | Conditional | Errors found in logs | | `morning-briefing` | Scheduled | Daily at 8:00 AM | | `evening-summary` | Scheduled | Daily at 6:00 PM | --- ## 🎨 Custom Configuration Create `config/pulse_brain_config.py`: ```python from pulse_brain import PulseCheck, BrainTask, CheckType def check_my_server() -> dict: """Pure Python check (zero cost).""" import requests try: r = requests.get("http://localhost:8000/health") return { "status": "ok" if r.status_code == 200 else "error", "message": f"Server: {r.status_code}" } except: return {"status": "error", "message": "Server down"} CUSTOM_PULSE_CHECKS = [ PulseCheck("my-server", check_my_server, interval_seconds=60) ] CUSTOM_BRAIN_TASKS = [ BrainTask( name="server-medic", check_type=CheckType.CONDITIONAL, prompt_template="Server is down! {message}\n\nWhat should I check?", condition_func=lambda data: data.get("status") == "error" ) ] ``` --- ## 🌟 Real-World Examples ### Example 1: Homelab Monitoring (from Gemini) **The "Morning Briefing"** (Scheduled Brain): ```python BrainTask( name="homelab-morning", check_type=CheckType.SCHEDULED, schedule_time="08:00", prompt_template="""Good morning Jordan! Overnight summary: - Plex: {plex_status} - Star Citizen: {game_status} - UniFi: {network_status} Any restarts or patches detected?""", send_to_platform="slack", send_to_channel="C_HOMELAB" ) ``` **Cost:** 1 API call/day = ~$0.01 **The "Medic"** (Conditional Brain): ```python def check_logs(): """Pure Python log scanner.""" with open("/var/log/syslog") as f: recent = f.readlines()[-100:] errors = [line for line in recent if "ERROR" in line] return { "status": "error" if errors else "ok", "error_lines": errors } BrainTask( name="error-medic", check_type=CheckType.CONDITIONAL, prompt_template="""Errors detected in logs: {error_lines} What does this mean and should I fix it?""", condition_func=lambda data: data.get("status") == "error" ) ``` **Cost:** Only when errors found = ~$0.01 per error **The "Resource Manager"** (Conditional Brain): ```python BrainTask( name="disk-cleanup", check_type=CheckType.CONDITIONAL, prompt_template="""Disk space is low: {gb_free:.1f} GB free. Please: 1. Scan temp folders 2. Recommend what to delete (>7 days old) 3. Provide cleanup commands""", condition_func=lambda data: data.get("gb_free", 100) < 10 ) ``` **Cost:** Only when disk < 10GB = ~$0.02 per trigger ### Example 2: Docker Monitoring ```python def check_docker(): import subprocess result = subprocess.run( ["docker", "ps", "--format", "{{.Status}}"], capture_output=True, text=True ) unhealthy = sum(1 for line in result.stdout.split("\n") if "unhealthy" in line) return { "status": "error" if unhealthy > 0 else "ok", "unhealthy_count": unhealthy } PULSE_CHECK = PulseCheck("docker", check_docker, interval_seconds=60) BRAIN_TASK = BrainTask( name="docker-fixer", check_type=CheckType.CONDITIONAL, prompt_template="{unhealthy_count} containers unhealthy. What should I do?", condition_func=lambda data: data.get("unhealthy_count", 0) > 0 ) ``` **Pulse runs every 60s:** $0 **Brain only when unhealthy:** ~$0.01 per incident --- ## πŸ’‘ Why Pulse & Brain? ### The Evolution of Monitoring Ajarbot has had **three different monitoring systems**. Here's how they compare: | Feature | Pulse & Brain ⭐ | TaskScheduler | Old Heartbeat ⚠️ | |---------|-----------------|---------------|------------------| | **Cost per day** | ~$0.04 | ~$0.10-0.30 | ~$0.48 | | **Cost per month** | ~$1.20 | ~$3-9 | ~$14.40 | | **Agent usage** | Only when needed | Every scheduled task | Every interval | | **Scheduling** | Cron + Conditional | Cron only | Interval only | | **Monitoring** | βœ… Zero-cost pulse | ❌ None | ❌ Uses agent | | **Messaging** | βœ… Slack/Telegram | βœ… Slack/Telegram | ❌ None | | **Best for** | Production monitoring | Content generation | ⚠️ Legacy (deprecated) | | **Status** | βœ… Recommended | βœ… Active | ⚠️ Disabled by default | ### Why Pulse & Brain Wins **1. Zero-Cost Monitoring** ```python # Pulse checks run constantly at zero cost Pulse (60s intervals, pure Python): β”œβ”€ Check disk space $0 β”œβ”€ Check log errors $0 β”œβ”€ Check stale tasks $0 β”œβ”€ Check server health $0 └─ ... (add infinite checks, still $0) # Brain only invoked when needed Brain (Agent/SDK): β”œβ”€ Condition: disk > 90% β†’ $0.01 (only if triggered) β”œβ”€ Condition: errors found β†’ $0.01 (only if triggered) β”œβ”€ Scheduled: 8:00 AM briefing β†’ $0.01 (once per day) └─ Scheduled: 6:00 PM summary β†’ $0.01 (once per day) ``` **2. Smarter Than TaskScheduler** TaskScheduler always invokes the agent, even if there's nothing to report: ```python # ❌ TaskScheduler: Always uses agent - 08:00 Weather report β†’ Agent ($0.01) even if no change - 12:00 Midday standup β†’ Agent ($0.01) even if no updates - 18:00 Evening summary β†’ Agent ($0.01) even if nothing happened # βœ… Pulse & Brain: Conditional intelligence - Pulse checks for changes β†’ Python ($0) - Brain only if updates β†’ Agent ($0.01) only when needed ``` **3. More Flexible Than Old Heartbeat** Old Heartbeat was simple but wasteful: ```python # ❌ Old Heartbeat: Every 30 minutes, always uses agent while True: agent.chat("Check everything") # ~$0.01 time.sleep(1800) # 48 calls/day = $0.48/day # βœ… Pulse & Brain: Smart triggers while True: # 1,440 pulse checks/day (pure Python) = $0 # Only 4 brain calls/day (when needed) = $0.04/day ``` ### Decision Tree: Which System to Use? ``` Start here: ↓ Do you need real-time monitoring? (disk, logs, health checks) β”œβ”€ YES β†’ Use Pulse & Brain ⭐ └─ NO β†’ Go to next question ↓ Do you need scheduled content? (weather, summaries, reports) β”œβ”€ YES β†’ Use TaskScheduler └─ NO β†’ Go to next question ↓ Do you need simple periodic checks? └─ YES β†’ Migrate from old Heartbeat to Pulse & Brain Most users should: Use Pulse & Brain (+ optionally TaskScheduler for content) ``` ### Hybrid Approach (Best of Both) For maximum efficiency: ```python # Pulse & Brain handles: # - Health monitoring (disk, logs, tasks) # - Morning briefing with system status # - Evening summary # - Error alerts pb = PulseBrain(agent, pulse_interval=60) pb.start() # TaskScheduler handles ONLY: # - Weekly newsletter (Friday 5pm) # - Monthly metrics report (1st of month) # - Custom scheduled content (unique reports) scheduler = TaskScheduler(agent) scheduler.tasks = [weekly_newsletter, monthly_report] scheduler.start() ``` **Cost: ~$2-3/month** (vs $15/month with old heartbeat) πŸ’° ### Real-World Cost Examples | Use Case | System | Monthly Cost | |----------|--------|--------------| | **Homelab monitoring** | Pulse & Brain only | ~$1-2 | | **Dev team bot** | Pulse & Brain + TaskScheduler | ~$4-6 | | **Solo developer** | Pulse & Brain only | ~$0.50-1 | | **Content bot** | TaskScheduler only | ~$4-8 | | **Old heartbeat** | ⚠️ Legacy system | ~$15 | ### Migration Guide **From Old Heartbeat β†’ Pulse & Brain** ```python # Old (heartbeat.py) ❌ agent = Agent(enable_heartbeat=True) # New (pulse_brain.py) βœ… agent = Agent(enable_heartbeat=False) pb = PulseBrain(agent) pb.start() ``` **Benefit:** 92% cost reduction **From TaskScheduler β†’ Pulse & Brain** If your "scheduled tasks" are really monitoring checks: ```python # Old (scheduled_tasks.yaml) ❌ - name: health-check schedule: "hourly" prompt: "Check system health" # New (pulse_brain.py) βœ… def check_health(): # Pure Python, zero cost return {"status": "ok", "message": "Healthy"} PulseCheck("health", check_health, interval_seconds=3600) ``` **Benefit:** 96% cost reduction ### Why Not Just Use TaskScheduler? TaskScheduler is great for **content generation**, but wasteful for monitoring: ```python # Example: Check disk space every hour with TaskScheduler # Cost: 24 calls/day Γ— 30 days = 720 calls/month = ~$7/month # Same with Pulse & Brain: # Pulse checks: Unlimited ($0) # Brain only if disk > 90%: ~2 calls/month = ~$0.02/month # Savings: $6.98/month (99.7% reduction) ``` ### Why Not Just Use Old Heartbeat? Old Heartbeat was the original system, but it's: - **Expensive**: Uses agent for every check - **Inflexible**: Only interval-based, no conditionals - **Limited**: No messaging platform integration - **Deprecated**: Disabled by default, legacy code **Pulse & Brain replaces it entirely with 92% cost savings.** --- ## 🎯 When to Use What | System | Best For | Cost | |--------|----------|------| | **Pulse & Brain** | Production monitoring | ~$1-2/month | | **TaskScheduler** | Scheduled content | ~$3-5/month | | **Old Heartbeat** | ⚠️ Legacy (don't use) | ~$15/month | ### Recommended Stack For maximum efficiency: ```python # Pulse & Brain for monitoring (cheapest) pb = PulseBrain(agent, pulse_interval=60) pb.start() # TaskScheduler for scheduled content only scheduler = TaskScheduler(agent) # Only enable specific scheduled tasks scheduler.start() ``` --- ## πŸ“Š Monitoring Your Costs ```python pb = PulseBrain(agent) pb.start() # After running for a while status = pb.get_status() print(f"Brain invoked {status['brain_invocations']} times") # Estimate cost tokens_per_invocation = 1000 # Average total_tokens = status['brain_invocations'] * tokens_per_invocation cost = total_tokens * 0.000003 # Claude Sonnet pricing print(f"Estimated cost: ${cost:.4f}") ``` --- ## πŸ’° Cost Optimization Tips 1. **Increase pulse interval** if checks don't need to be frequent ```python pb = PulseBrain(agent, pulse_interval=300) # Every 5 min instead of 60s ``` 2. **Use conditional brain tasks** instead of scheduled ```python # ❌ Expensive: Always runs BrainTask(schedule="daily 08:00", ...) # βœ… Cheap: Only if there's news BrainTask(condition=lambda: has_updates(), ...) ``` 3. **Batch briefings** instead of multiple schedules ```python # ❌ Expensive: 3 calls/day - morning-briefing (08:00) - midday-update (12:00) - evening-summary (18:00) # βœ… Cheaper: 2 calls/day - morning-briefing (08:00) - evening-summary (18:00) ``` 4. **Make pulse checks do more** before invoking brain ```python # Pulse checks can filter, aggregate, and pre-process # Brain only gets invoked with actionable data ``` --- ## πŸš€ Getting Started 1. **Edit** `config/pulse_brain_config.py` with your checks 2. **Test** your pulse checks (they should return `{"status": "ok|warn|error"}`) 3. **Configure** brain tasks (conditional or scheduled) 4. **Run** `python -m pulse_brain` 5. **Monitor** brain invocation count --- ## πŸ”₯ Pro Tips 1. **Make pulse checks fast** (<1 second each) 2. **Use conditional brain tasks** for errors/warnings 3. **Use scheduled brain tasks** for daily summaries 4. **Test pulse checks** without brain first 5. **Monitor brain invocations** to track costs --- ## ⚠️ Legacy System Notice ### Old Heartbeat (Deprecated) The original Heartbeat system is now **disabled by default**. It has been superseded by Pulse & Brain. **Why it's deprecated:** - Uses agent for every check (expensive) - No conditional logic (always runs) - No messaging platform integration - Replaced entirely by Pulse & Brain **If you're still using it:** ```python # Old (don't use) ❌ agent = Agent(enable_heartbeat=True) # New (migrate to this) βœ… agent = Agent(enable_heartbeat=False) pb = PulseBrain(agent) pb.start() ``` **Migration benefits:** - 92% cost reduction - Conditional intelligence - Messaging platform support - More flexible scheduling --- ## πŸŽ‰ Summary **Pulse & Brain is the most cost-effective way to run a proactive agent:** βœ… **Pulse runs constantly** - Zero cost βœ… **Brain only when needed** - Pay for value βœ… **92% cost savings** vs always-on agent βœ… **Smart monitoring** - Python checks + Agent analysis βœ… **Scalable** - Add more checks without increasing cost **Perfect for:** - Homelab monitoring - Server health checks - Log analysis - Resource management - Scheduled briefings **Result:** An agent that's always watching but only speaks when it has something important to say. πŸ«€πŸ§