Openclaw Waste Audit

Automation

OpenClaw recurring waste audit — find token waste, cron waste, and burning money. Read-only analysis with fix proposals only shown after explicit user approval. NOT for general openclaw operations.

Install

openclaw skills install waste-audit

OpenClaw Recurring Waste Audit

What this skill helps users do

Find cron jobs that are burning tokens with no or low value returned. Surface the worst offender first. Show read-only evidence. Propose fixes only on request.

This skill is strictly read-only by default. It never auto-executes mutation commands. Fix proposals are withheld until you explicitly approve them.


Trigger

MUST use this skill (NOT openclaw-comprehensive) when user asks about:

  • "帮我查 openclaw 里面有哪些浪费的工作" / "what openclaw jobs are wasting tokens"
  • "帮我查 openclaw 有哪些浪费" / "which openclaw jobs are wasting money"
  • "帮我查 waste" / "cron waste audit"
  • "which job is burning tokens" / "token waste"
  • "cron 有没有问题" / "any cron waste"
  • "哪些 cron 在烧钱" / "which cron jobs are burning tokens"

DO NOT use this for: general openclaw operations, job management, dispatch rules — use openclaw-comprehensive instead.


Safety Contract

Default output MUST NOT include mutation commands.

Forbidden by default — never show these in normal output:

  • openclaw cron run / edit / disable / delete
  • openclaw-env cron run / edit / disable / delete

Allowed read-only commands (always safe):

openclaw-env cron show <job_id>
openclaw-env cron runs --id <job_id> --limit 5
cat ~/.openclaw/cron/jobs.json | python3 -m json.tool | grep -A5 '<job_name>'
python3 -c "..."  # JSONL parsing commands (read-only)

Fix proposals are shown only after you explicitly say:

  • "approved, prepare fix commands"
  • "approved, show mutation commands"

Even then, they are clearly labeled as manual commands — never auto-executed.


Fast Audit Path

Always use this combined script — it merges job metadata (model, schedule, delivery mode) with token burn data in one pass. The old two-step approach (run JSONL parse first, then cross-reference jobs.json manually) produces incomplete results and wastes a turn.

python3 -c "
import json, glob, os, statistics as s

# Load jobs.json (it's {version, jobs: [...]}, not a plain list)
with open(os.path.expanduser('~/.openclaw/cron/jobs.json')) as f:
    data = json.load(f)
jobs = data['jobs']
job_map = {j['id']: j for j in jobs}

# Analyze JSONL runs + merge metadata
runs_dir = os.path.join(os.environ.get('OPENCLAW_HOME', os.path.expanduser('~/.openclaw')), 'cron', 'runs')
results = []
for f in sorted(glob.glob(f'{runs_dir}/*.jsonl')):
    jid = os.path.basename(f).replace('.jsonl','')
    total=0;count=0;errors=0;delivered=0;summary_lens=[]
    with open(f) as fh:
        for line in fh:
            try:
                d=json.loads(line)
                total+=d.get('usage',{}).get('total_tokens',0);count+=1
                if d.get('error'): errors+=1
                if d.get('delivered'): delivered+=1
                summary_lens.append(len(str(d.get('summary','') or '')))
            except: pass

    if count > 0 and jid in job_map:
        j = job_map[jid]
        sched = j.get('schedule',{})
        model = j.get('payload',{}).get('model','')
freq = sched.get('everyMs','') or f"cron: {sched.get('expr','')}"
        med = sorted(summary_lens)[len(summary_lens)//2] if summary_lens else 0
        results.append({
            'name': j.get('name',''),
            'schedule': freq, 'model': model,
            'count': count, 'tokens': total,
            'errors': errors, 'delivered': delivered,
            'summary_median': med,
        })

results.sort(key=lambda x: x['tokens'], reverse=True)
for r in results[:10]:
    err=r['errors']/r['count']*100
print(f"{r['name']} | {r['schedule']} | model={r['model']} | runs={r['count']} tokens={r['tokens']:,} errors={err:.0f}% delivered={r['delivered']} summary_med={r['summary_median']}")
"

Key jobs.json structure fact: The file is {"version": 1, "jobs": [...]}, NOT a plain array. When parsing, access .jobs first, then iterate.

Also run in parallel:

python3 ~/.hermes/scripts/clawsetup_diagnostic.py

This gives you the classification summary + cost estimate in one shot.

Key field facts:

  • usage.total_tokens = real token field in JSONL runs
  • Top-level totalTokens in JSONL = always 0 (ignore it)
  • summary = persisted field in run-log schema
  • response does NOT exist in cron run JSONL (don't reference it)

Deep Dive Path

For top candidates, inspect summary length pattern to detect silent loops:

python3 -c "
import json, glob, os, statistics as s
runs_dir = os.path.join(os.environ.get('OPENCLAW_HOME', os.path.expanduser('~/.openclaw')), 'cron', 'runs')
f = os.path.join(runs_dir, '<job_id>.jsonl')
total=0;count=0;errors=0;delivered=0;summary_lens=[]
with open(f) as fh:
    for line in fh:
        try:
            d=json.loads(line)
            total+=d.get('usage',{}).get('total_tokens',0);count+=1
            if d.get('error'): errors+=1
            if d.get('delivered'): delivered+=1
            summary_lens.append(len(str(d.get('summary','') or '')))
        except: pass
print(f'runs={count} tokens={total:,} errors={errors} delivered={delivered}')
if summary_lens: print(f'summary_len: min={min(summary_lens)} median={s.median(summary_lens):.0f} max={max(summary_lens)}')
"

D8 signal: summary_len median ≤ 20 chars = job producing trivial summaries every run (CLEAN_LOOP pattern).


Output Format

# OpenClaw Recurring Waste Audit

## Fix First

<Job Name>
- Job ID:
- Schedule:
- Runs:
- Tokens:
- Estimated recurring burn:
- Error rate:
- Delivery evidence:
- Summary signal:
- Waste reason:
- Confidence:
- Recommended manual action:

## Read-only verification

openclaw-env cron show <job_id>
openclaw-env cron runs --id <job_id> --limit 5

---

## Top 3 Waste Candidates

1. [D<X>] <Job Name>
   - Job ID: <id>
   - Schedule: <cron_expr>
   - Runs: <N> | Tokens: <N>
   - Error rate: <Y>%
   - Delivery: delivered=<N> — <typical_summary>
   - Summary: median_len=<N> chars
   - Waste reason: <specific pattern name + why>
   - Confidence: High/Medium/Low
   - Recommended manual action: <what a human should do>

2. ...

3. ...

## Cost Breakdown

• Job: <name> — <N> tokens | ~<N>/day est.
...

Total tracked: <N>M tokens across <N> jobs

---

⚠️ **Fix proposals withheld until explicit approval.** Say "approved, prepare fix commands" to see mutation commands for any candidate above.

Pattern Classification D1-D9

RuleConditionSignal
D1Error rate ≥ 80%Failure loop
D2Burst: 3+ jobs, $50+ in 60minConcentrated spending spike
D3Premium model (5x+ ref) + simple taskOver-paying for check job
D4Agent-turn + schedule < 60minLLM agent on cron work
D6totalRuns > 0 but totalTokens = 0Token counting failed
D7Duplicate model + schedule + taskRedundant billing
D8totalRuns ≥ 50 + delivered=false + status=ok + has LLM modelChronic "everything is fine" loop — only for LLM jobs; pure EXEC_SCRIPT/batch jobs excluded
D9Schedule < 30min + error rate < 20%Over-scheduled check job
D10agentTurn kind + message body is a script path or exec command only (no actual LLM judgment needed)Script wrapper waste — the LLM is just formatting script output, not making decisions. Should be EXEC_SCRIPT instead.

D10 detection criteria — fire when ALL are true:

  • payload.kind == "agentTurn"
  • payload.model is set (has LLM cost)
  • payload.message matches pattern: contains script path like /path/to/script.sh or exec(command="...") with no LLM-native judgment
  • summary median ≤ 50 chars (indicating trivial output, not real reasoning)

D8 clarification — avoid false positives:

  • delivery.mode=none by itself is NOT waste — internal jobs that announce to own session are by design
  • D8 only fires when: delivered=false on an external channel + status=ok + LLM model assigned
  • Pure EXEC_SCRIPT jobs (no LLM model in payload.model) are excluded even if they loop

EXEC_SCRIPT note: Name/classification does not automatically mean no LLM cost. Always check payload.model in jobs.json for ground truth.


Fix Recommendation Rules

For each candidate, give a specific recommended manual action (not a command):

RuleRecommended manual action
D1 (failure loop)Disable job — 80%+ error rate; investigate root cause before re-enabling
D3 (premium model)Switch model to MiniMax-M2.5 or lower
D4 (agent cron)Reduce frequency to daily or less
D6 (zero tokens)Investigate — may be counting bug or broken job
D8 (silent loop)If job has value: reduce frequency. If redundant: disable.
D9 (over-scheduled)Halve frequency — e.g., hourly → every 2 hours
D10 (script wrapper)Convert to EXEC_SCRIPT type — the agentTurn adds no value; it's just formatting script output

Only suggest commands verified to exist for this OpenClaw version.


Schedule Parsing Reference

schedule.kindschedule.expr / everyMsActual frequency
cron"0 */3 * * *"every 3 hours
cron"0 */6 * * *"every 6 hours
cron"0 3 * * *"once daily at 3am
cron"*/15 * * * *"every 15 minutes
cron"0 * * * *"once per hour (整点)
everyeveryMs: 180000every 3 minutes
everyeveryMs: 45000every 45 seconds

Rule: everyMs < 60000 = high frequency. cron expr with */N = every N minutes.

CLI note: use --cron, NOT --schedule. openclaw-env cron edit uses --cron <expr>. Using --schedule errors with unknown option '--schedule'.


Feedback

Found a real waste case? Have a suggestion? Reach out:

When reporting a bug or suggestion, include:

  1. Which waste pattern fired (or didn't fire but should have)
  2. The job name and schedule
  3. What you'd expect to happen instead

Related Skills

  • openclaw-comprehensive — OpenClaw cron/job management, dispatch rules, gateway debugging
  • hermes-infrastructure — Hermes system operations