Files
ajarbot/docs/PULSE_BRAIN.md
Jordan Ramos ab3a5afd59 Documentation cleanup and consolidation
Removed redundant/outdated files:
- docs/HEARTBEAT_HOOKS.md (legacy, disabled by default)
- docs/QUICK_START_PULSE.md (merged into PULSE_BRAIN.md)
- docs/MONITORING_COMPARISON.md (merged into PULSE_BRAIN.md)

Consolidated monitoring docs:
- Merged 3 monitoring files into comprehensive PULSE_BRAIN.md
- Added Quick Start section to PULSE_BRAIN.md
- Added "Why Pulse & Brain?" comparison section
- Added deprecation notices for Heartbeat system

Updated README.md:
- Clarified Haiku is default model (12x cheaper)
- Added prompt caching info (90% savings on Sonnet)
- Removed duplicate setup instructions
- Linked to SETUP.md for detailed instructions
- Added model switching commands section

Simplified WINDOWS_QUICK_REFERENCE.md:
- Reduced from 224 lines to ~160 lines
- Removed redundant development/deployment sections
- Kept essential quick commands
- Added model switching commands

Updated docs/README.md navigation:
- Removed references to deleted files
- Added deprecation notice for Heartbeat
- Updated learning paths
- Cleaned up file organization section

Result:
- Removed 300+ lines of redundant documentation
- Consolidated 3 monitoring files into 1
- Improved accuracy and clarity
- Easier navigation and maintenance

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-13 21:06:15 -07:00

19 KiB
Raw Blame History

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)

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

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)

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:

# ❌ 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
# ✅ 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)

# 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)

# 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:

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):

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):

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):

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

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

# 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:

# ❌ 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:

# ❌ 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:

# 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

# 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:

# 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:

# 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

For maximum efficiency:

# 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

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

    pb = PulseBrain(agent, pulse_interval=300)  # Every 5 min instead of 60s
    
  2. Use conditional brain tasks instead of scheduled

    # ❌ 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

    # ❌ 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

    # 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:

# 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. 🫀🧠