Get Pulse running in production — from localhost testing to 24/7 cloud deployment.
- Local Development
- Production Setup
- Docker Deployment
- Systemd Service (Linux)
- LaunchAgent (macOS)
- Cloud Deployment
- Monitoring
- Troubleshooting
- Python 3.11+
- OpenClaw installed and configured
- Webhook enabled in OpenClaw config
# Clone or install
git clone https://github.com/astra-ventures/pulse.git
cd pulse
# Install
pip install -e .
# Or via pip (when published)
pip install pulse-agent# Copy example config
mkdir -p ~/.pulse/config
cp config/pulse.example.yaml ~/.pulse/config/pulse.yaml
# Edit required fields
nano ~/.pulse/config/pulse.yamlMinimum required:
openclaw:
webhook_url: "http://localhost:8080/hooks/agent"
webhook_token: "your-webhook-token" # from OpenClaw gateway config
workspace:
root: "/Users/you/.openclaw/workspace"# Foreground (for testing)
python3 -m pulse
# Or use the helper script
./bin/run.shCheck it's working:
# In another terminal
curl http://localhost:9720/health
# Should return:
# {"status": "healthy", "uptime": 123, ...}# Manually spike a drive
python3 -m pulse spike goals 5.0 "Testing manual trigger"
# Watch the logs — should trigger an agent turn within 30ssudo useradd -r -m -s /bin/bash pulse
sudo su - pulsecd ~
git clone https://github.com/astra-ventures/pulse.git
cd pulse
pip install --user -e .cp config/pulse.example.yaml ~/.pulse/config/pulse.yaml
nano ~/.pulse/config/pulse.yamlProduction config recommendations:
daemon:
loop_interval_seconds: 30
log_level: "INFO" # not DEBUG (too verbose)
logging:
output: "/var/log/pulse/pulse.log" # or ~/pulse/logs/pulse.log
format: "structured" # JSON for log aggregation
openclaw:
min_trigger_interval: 1800 # 30 min cooldown
max_turns_per_hour: 10 # rate limit
drives:
trigger_threshold: 5.0# Add to ~/.bashrc or ~/.profile
export PULSE_HOOK_TOKEN="your-secret-token"
export PULSE_CONFIG=~/.pulse/config/pulse.yaml
export PULSE_STATE_DIR=~/.pulsepython3 -m pulse
# Let it run for 5 minutes, watch for errors
# Ctrl+C to stopdocker build -t pulse:latest .docker run -d \
--name pulse \
--restart unless-stopped \
-e PULSE_HOOK_TOKEN=your-token \
-v ~/.pulse:/root/.pulse \
-v /path/to/openclaw/workspace:/workspace:ro \
-p 9720:9720 \
pulse:latestWhat this does:
-d— run in background--restart unless-stopped— auto-restart on crash-e PULSE_HOOK_TOKEN— pass webhook token-v ~/.pulse:/root/.pulse— persist state-v /workspace:/workspace:ro— mount workspace read-only-p 9720:9720— expose health endpoint
# docker-compose.yml
version: '3.8'
services:
pulse:
build: .
container_name: pulse
restart: unless-stopped
environment:
- PULSE_HOOK_TOKEN=${PULSE_HOOK_TOKEN}
volumes:
- ~/.pulse:/root/.pulse
- /path/to/openclaw/workspace:/workspace:ro
ports:
- "9720:9720"
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"# Start
docker-compose up -d
# Logs
docker-compose logs -f pulse
# Stop
docker-compose downCreate /etc/systemd/system/pulse.service:
[Unit]
Description=Pulse — Autonomous cognition engine for OpenClaw
After=network.target
[Service]
Type=simple
User=pulse
Group=pulse
WorkingDirectory=/home/pulse/pulse
Environment="PULSE_HOOK_TOKEN=your-token"
Environment="PULSE_CONFIG=/home/pulse/.pulse/config.yaml"
ExecStart=/usr/bin/python3 -m pulse
Restart=on-failure
RestartSec=10
StandardOutput=journal
StandardError=journal
# Security hardening
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=read-only
ReadWritePaths=/home/pulse/.pulse
[Install]
WantedBy=multi-user.targetEnable and start:
sudo systemctl daemon-reload
sudo systemctl enable pulse
sudo systemctl start pulseMonitor:
# Status
sudo systemctl status pulse
# Logs
sudo journalctl -u pulse -f
# Restart
sudo systemctl restart pulseCreate ~/Library/LaunchAgents/ai.openclaw.pulse.plist:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>ai.openclaw.pulse</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/python3</string>
<string>-m</string>
<string>src</string>
</array>
<key>EnvironmentVariables</key>
<dict>
<key>PULSE_HOOK_TOKEN</key>
<string>your-token</string>
<key>PULSE_CONFIG</key>
<string>/Users/you/.pulse/config.yaml</string>
</dict>
<key>WorkingDirectory</key>
<string>/Users/you/pulse</string>
<key>StandardOutPath</key>
<string>/Users/you/.pulse/logs/pulse.log</string>
<key>StandardErrorPath</key>
<string>/Users/you/.pulse/logs/pulse-error.log</string>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<dict>
<key>SuccessfulExit</key>
<false/>
</dict>
<key>ThrottleInterval</key>
<integer>60</integer>
</dict>
</plist>Load and start:
# Create log directory
mkdir -p ~/.pulse/logs
# Load the agent
launchctl load ~/Library/LaunchAgents/ai.openclaw.pulse.plist
# Check status
launchctl list | grep pulse
# View logs
tail -f ~/.pulse/logs/pulse.logUnload (to stop):
launchctl unload ~/Library/LaunchAgents/ai.openclaw.pulse.plistRequirements:
- 512 MB RAM minimum (1 GB recommended)
- 5 GB disk
- Ubuntu 22.04 or Debian 11+
Setup:
# SSH into VPS
ssh root@your-vps-ip
# Install dependencies
apt update && apt upgrade -y
apt install -y python3 python3-pip git
# Create pulse user
useradd -r -m -s /bin/bash pulse
su - pulse
# Install Pulse
git clone https://github.com/astra-ventures/pulse.git
cd pulse
pip3 install --user -e .
# Configure
cp config/pulse.example.yaml ~/.pulse/config/pulse.yaml
nano ~/.pulse/config/pulse.yaml
# Set webhook URL to OpenClaw instance
# If OpenClaw is on the same machine: http://localhost:8080/hooks/agent
# If remote: https://your-openclaw.example.com/hooks/agent
# Test
python3 -m pulseDaemonize with systemd (see above).
# Install flyctl
curl -L https://fly.io/install.sh | sh
# Launch
fly launch
# Follow prompts, select smallest machine (256 MB)
# Set secrets
fly secrets set PULSE_HOOK_TOKEN=your-token
# Deploy
fly deployNote: Pulse needs persistent storage for state. Add a volume:
fly volumes create pulse_state --size 1Update fly.toml:
[mounts]
source = "pulse_state"
destination = "/root/.pulse"curl http://localhost:9720/healthResponse:
{
"status": "healthy",
"uptime": 3600,
"version": "0.2.3",
"triggers": {
"total": 12,
"last": {
"timestamp": 1708234567,
"reason": "goals pressure 6.2",
"success": true
}
},
"drives": {
"goals": {"pressure": 2.1, "weight": 1.0},
"curiosity": {"pressure": 1.3, "weight": 0.8}
},
"rate_limit": {
"turns_last_hour": 3,
"max_per_hour": 10,
"cooldown_remaining": 0
}
}Pulse will expose /metrics endpoint with:
pulse_uptime_secondspulse_triggers_totalpulse_drive_pressure{drive="goals"}pulse_evaluation_duration_seconds
# Check config validity
python3 -m pulse doctor
# Check PID lock
rm ~/.pulse/pulse.pid
# Check logs
tail -f ~/.pulse/logs/pulse.log1. Check webhook reachability:
curl -X POST http://localhost:8080/hooks/agent \
-H "Authorization: Bearer your-token" \
-H "Content-Type: application/json" \
-d '{"message": "Test"}'2. Check OpenClaw webhook config:
# In OpenClaw gateway config
hooks:
enabled: true
routes:
- path: "/hooks/agent"
token: "your-token"3. Check Pulse logs for errors:
grep "webhook" ~/.pulse/logs/pulse.logCheck feedback:
# After a trigger, agent should send:
curl -X POST http://localhost:9720/feedback \
-H "Content-Type: application/json" \
-d '{"drives_addressed": ["goals"], "outcome": "success"}'
# Check Pulse received it:
grep "feedback" ~/.pulse/logs/pulse.logLower sensitivity:
drives:
trigger_threshold: 7.0 # was 5.0
pressure_rate: 0.005 # was 0.01
openclaw:
min_trigger_interval: 3600 # 1 hour cooldownIncrease sensitivity:
drives:
trigger_threshold: 3.0
pressure_rate: 0.02
openclaw:
min_trigger_interval: 900 # 15 min cooldown# Check Python process
ps aux | grep pulse
# If > 200 MB, possible memory leak
# Check for large state files:
ls -lh ~/.pulse/Prune history:
state:
max_history_entries: 100 # was 1000Check watchdog:
pip show watchdog
# Should be >= 2.0.0Increase debounce:
sensors:
filesystem:
debounce_seconds: 5.0 # was 2.0# On old machine
tar czf pulse-backup.tar.gz ~/.pulse/
# Copy to new machine
scp pulse-backup.tar.gz newhost:~/
# On new machine
tar xzf pulse-backup.tar.gz -C ~/
# Update webhook URL
nano ~/.pulse/config/pulse.yaml
# Change openclaw.webhook_url if needed
# Start
python3 -m pulseYour agent picks up exactly where it left off.
- Webhook token is secret (not in git)
- Health endpoint not exposed to internet (or firewall port 9720)
- Pulse runs as non-root user
- State directory has restrictive permissions (
chmod 700 ~/.pulse) - Logs don't contain secrets
- Rate limits configured (
max_turns_per_hour) - Guardrails enabled (default)
- Architecture — understand how it works
- Configuration — tuning reference
- Examples — sample configs for different use cases