Your Pi as a Self-Backing-Up AI Agent — Skills in Git, Scripts on GitHub
Your Pi as a Self-Backing-Up
AI Agent — Skills in Git,
Scripts on GitHub
Every skill, script, and memory file version-controlled in a private GitHub repo. Edit on your PC, push, and the Pi pulls automatically. Midnight auto-backup keeps everything safe. Plus: fixing a cron that fired at the wrong time and staying on the Gemini free tier.
Automated Backup to GitHub over SSH
Repo Structure
├── .gitignore
├── MEMORY.md
├── sync-to-github.sh ← Pi → GitHub (midnight cron)
├── pull-from-github.sh ← GitHub → Pi (on demand)
├── memory/
│ └── my-portfolio.json
└── skills/
├── home-assistant/
├── memory/
├── github-sync/
├── portfolio-tracker/
└── safe-exec/
The Google Sheets service account JSON contains a private key. Even in a private repo, add it to .gitignore and keep a manual backup. One accidental repo visibility change would expose it.
Setup Steps
mkdir -p /config/.ssh && chmod 700 /config/.ssh ssh-keygen -t ed25519 -C "openclaw-pi" -f /config/.ssh/id_ed25519 -N "" cat /config/.ssh/id_ed25519.pub # Copy this output → GitHub repo → Settings → Deploy keys → Add # ✅ Check "Allow write access"
In the OpenClaw HAOS container, ~ resolves to /config, not /root. More importantly, /config is HAOS persistent storage — SSH keys survive container restarts. /root/.ssh would be wiped on every restart.
git config --global user.email "openclaw@pi" git config --global user.name "OpenClaw Pi" git config --global core.sshCommand "ssh -i /config/.ssh/id_ed25519" ssh -T git@github.com -i /config/.ssh/id_ed25519 # Should say: Hi username/repo! You've successfully authenticated git clone git@github.com:YOUR_USER/YOUR_REPO.git /config/clawd-backup
cp -r /config/clawd/skills /config/clawd-backup/
cp -r /config/clawd/memory /config/clawd-backup/
cp /config/clawd/MEMORY.md /config/clawd-backup/
cat > /config/clawd-backup/.gitignore << 'EOF'
memory/gsheets-credentials.json
memory/*.bak.json
__pycache__/
*.pyc
EOF
cd /config/clawd-backup
git add . && git commit -m "initial: openclaw skills + memory"
git push -u origin main
openclaw cron add \ --name "GitHub Backup Midnight" \ --cron "0 21 * * *" \ --tz "Your/Timezone" \ --wake next-heartbeat \ --timeout-seconds 120 \ --session isolated \ --light-context \ --announce \ --to YOUR_TELEGRAM_ID \ --channel telegram \ --message "Run: bash /config/clawd-backup/sync-to-github.sh" # Adjust cron expression to match your local midnight in UTC
The Two Scripts
#!/bin/bash cd /config/clawd-backup git pull --rebase origin main # pull PC edits first cp -r /config/clawd/skills/* skills/ cp -r /config/clawd/memory/* memory/ 2>/dev/null cp /config/clawd/MEMORY.md . git diff --quiet && git diff --staged --quiet && exit 0 git add . git commit -m "auto: $(date '+%Y-%m-%d %H:%M') — scheduled backup" git push origin main
#!/bin/bash cd /config/clawd-backup git pull --rebase origin main || exit 1 cp -r skills/* /config/clawd/skills/ cp memory/my-portfolio.json /config/clawd/memory/ cp MEMORY.md /config/clawd/ echo "$(date): Sync complete — active skills updated"
Trigger the pull from Telegram by saying "pull scripts" or "sync from github" — the github-sync skill handles it.
The 11:30 AM Mystery
Problem 1 — wakeMode: now
OpenClaw's wakeMode: now fires a missed job immediately on container restart instead of waiting for its next scheduled time. The add-on restarted sometime in the morning, saw it hadn't run since yesterday, and fired immediately.
// fires immediately on restart
// if last run was missed
// waits for next heartbeat
// cycle before firing
Manual JSON edits get overwritten when OpenClaw saves its own state. Use the CLI — openclaw cron edit <id> --wake next-heartbeat — so changes persist properly.
Problem 2 — Wrong cron expression
The original expression 30 11 * * 0-4 with your local timezone was always 11:30 AM local time — not 2:30 PM. The intent was 2:30 PM but the expression was never corrected from when the job was first created.
"tz": "Your/Timezone"
"tz": "Your/Timezone"
Problem 3 — Timezone disappeared after CLI edit
When we first fixed the cron expression via CLI without specifying --tz, OpenClaw dropped the timezone field entirely — and the UI defaulted it to an incorrect timezone. 30 14 * * 0-4 in LA time = wrong time entirely.
When editing a cron job via CLI, always include --tz "Your/Timezone" even if you're not changing the timezone. OpenClaw may drop the field otherwise and the UI will silently default to a different timezone.
The Fix — One CLI Command
# Fix expression + timezone + wakeMode in one shot openclaw cron edit <job-id> \ --cron "30 14 * * 0-4" \ --tz "Your/Timezone" \ --wake next-heartbeat # Verify in the UI — check timezone shows your local timezone, not a default
30 14 * * 0-4 @ Your/Timezone · wakeMode: next-heartbeat · timeout: 300s · lightContext: true · next run: Thursday 2:30 PM
Renaming a skill directory or memory file means updating every reference — scripts, SKILL.md, MEMORY.md, and the cron payload. Missing one causes a silent failure at the next scheduled run. Always follow a rename with grep -r "old-name" /config/clawd/ to confirm zero remaining references.
Stretching the Gemini Free Tier
What Costs Tokens
| Action | LLM Calls | Approx Tokens |
|---|---|---|
| Telegram trigger (pf, top signals) | 2 | ~3,500 |
| Cron job run (full context) | 2 | ~4,000 |
| Cron job run (lightContext) | 2 | ~1,800 |
| SSH script test | 0 | 0 |
| Repeated testing via Telegram | 2 per test | adds up fast |
lightContext Flag
Added to both crons — reduces the bootstrap context loaded per isolated session. Safe for cron jobs since they have a specific task in the payload and don't need full memory/skill context.
openclaw cron edit <job-id> --light-context # Adds "lightContext": true to the payload # Reduces tokens per cron run by ~55%
Testing Strategy
Test scripts directly via SSH — python3 signals.py --top uses zero API calls. Only use Telegram to verify the final trigger works end-to-end, not for iterative testing.
Free tier resets hourly. If you hit the limit, wait 10-15 minutes. Daily limit resets at midnight Pacific time. Don't switch providers just because of a temporary limit.
Keep MEMORY.md and SKILL.md concise. Every line loaded into context costs tokens on every turn. Remove redundant rules and consolidate where possible.
The two daily crons (2:30 PM portfolio + midnight backup) consume roughly 11,600 tokens/day on lightContext. Well within Gemini free tier limits for regular usage.
5 Lessons from a Debugging Session
Editing /config/.openclaw/cron/jobs.json directly gets overwritten when OpenClaw saves state. Always use openclaw cron edit <id> --flag value. Run openclaw cron edit --help first — the available flags are not what you'd guess.
The OpenClaw web UI is the ground truth for what the scheduler actually sees. After any CLI edit, open the cron settings and verify every field — especially timezone. A missing --tz flag silently defaults to an incorrect timezone in the UI.
When you rename a file, grep for every reference: scripts, SKILL.md, MEMORY.md, cron payloads, and the actual filesystem. Missing one means a silent failure at the worst possible time — during a scheduled cron run.
In the OpenClaw HAOS add-on container, ~ is /config anyway, but explicitly using /config/.ssh makes the intent clear: this is persistent storage. Container restarts don't wipe /config. They do wipe everything else.
A single git push from your PC + "pull scripts" on Telegram is faster and safer than SSH + nano + copy-paste. Version history means every mistake is recoverable. The midnight auto-push means you always have yesterday's working version.
| Job | Schedule | wakeMode | lightContext |
|---|---|---|---|
| Portfolio Update 2:30 PM | 30 14 * * 0-4 Your/Timezone | next-heartbeat | ✓ |
| GitHub Backup Midnight | 0 21 * * * Your/Timezone | next-heartbeat | ✓ |
Thursday 2:30 PM is the first real test of the fixed cron — portfolio.py output via lightContext Gemini. If the format is clean, the system is stable. Part 6 will cover signal refinement and potentially a direct Telegram webhook to bypass the LLM entirely for price checks.
Comments
Post a Comment