# Anti-Hallucination Cron Conversion Checklist

When a Hermes cron job produces hallucinated output (fake data, wrong timestamps, stale dates like "6月23日" when DB has recent articles), follow this checklist to permanently eliminate the problem.

## Root Cause

LLM-driven cron (`no_agent=false`) + DeepSeek API timeout during peak hours (09:00) → "Streaming failed before delivery" mechanism delivers **partial/hallucinated output** before retry succeeds → user receives TWO messages: one fake, one correct.

Loading large skills (e.g. `wecom-bot-integration`, ~38KB) onto cron jobs dramatically increases timeout risk.

## Conversion Steps

### 1. Identify if the task CAN go no_agent

| Task type | Can go no_agent? | Approach |
|-----------|:---:|----------|
| Health check / monitoring | ✅ Yes | Script checks conditions, outputs alert only on failure, silent on success |
| Data aggregation (article listing, topic grouping) | ✅ Yes | Script queries DB, does keyword matching / grouping, formats markdown |
| Simple analysis (keyword clustering, trend counting) | ✅ Yes | Same as above — Python can cluster and count |
| Creative writing (article drafting, long-form content) | ❌ No | Use two-stage pipeline: Stage 1 = no_agent data script, Stage 2 = LLM with context_from |
| Image generation, complex reasoning | ❌ No | Keep LLM but reduce prompt size, avoid peak hours, use more reliable provider |

### 2. Write the replacement script

Template:
```python
#!/home/ubuntu/project/we-mp-rss-main/venv/bin/python3
"""Purpose. Usage: python3 script.py [arg]"""
import sqlite3, sys
from datetime import datetime, timezone, timedelta

# Query DB directly (no LLM in path)
db = sqlite3.connect("file:/path/to/db.db?mode=ro", uri=True)
db.row_factory = sqlite3.Row
# ... query, process, format ...

# Decision logic
if problem_detected:
    print("## 🚨 Alert: ...")  # stdout → Hermes delivers
    sys.exit(1)
else:
    sys.exit(0)  # silent → nothing delivered
```

Key rules:
- **Shebang MUST point to venv python** (`#!/home/ubuntu/project/we-mp-rss-main/venv/bin/python3`)
- **No hardcoded webhooks** — let Hermes `deliver` handle it
- **Silent on success** — empty stdout + exit 0 = user sees nothing
- **DB query uses read-only mode** (`?mode=ro, uri=True`) to avoid write locks
- **Chinese text matching**: use topic-pattern regex matching (see `daily_topic_report.py`), not jieba (not installed)

### 3. Update the cron job

```bash
cronjob action=update job_id=<JOB_ID> \
  no_agent=true \
  script=<script_name.py> \
  skills=[] \
  enabled_toolsets=[] \
  deliver=wecom_callback:TuKeXin,wecom_callback:CaoXiaoFen
```

Remove:
- `skills` — no longer needed (causes prompt bloat)
- `prompt` — still stored but never used with no_agent=true
- `enabled_toolsets` — not used with no_agent=true

### 4. Verify

```bash
# Test with real data
~/project/we-mp-rss-main/venv/bin/python3 ~/.hermes/scripts/<script>.py

# Verify silent case (short window)
~/project/we-mp-rss-main/venv/bin/python3 ~/.hermes/scripts/<script>.py 1
# Should produce NO output if no articles in last 1h

# Manual cron run
cronjob action=run job_id=<JOB_ID>
```

## Applied Examples (2026-07-16)

| Cron | Old | New Script | What it does |
|------|-----|------------|--------------|
| `eba217dfc155` | LLM + hardcoded group webhook | `check_werss_login.py` | Redis check + DB freshness + service reachability. Silent OK. |
| `218c1e953f63` | LLM hallucinated "停更23天" | `daily_topic_report.py` | 16-pattern topic matching, keyword clustering, calendar integration |
| `533d92e64b59` | LLM hallucinated "858篇/6.23" | `daily_gdcjgk_check.py` | Article listing, staleness alert ( >48h gap) |
| `472ca7495b44` | LLM with wecom-bot-integration skill | `hot_radar.py` | Cross-source topic matching, dedup via sent_ids JSON |

## Common Pitfalls

- **Chinese word segmentation**: Without jieba, use regex-based topic patterns (e.g. `分数线|投档|录取分`), not character bigrams
- **DB path**: Always use full path `/home/ubuntu/project/we-mp-rss-data/db.db`, not relative
- **Timezone**: All timestamps must use Beijing timezone (`timezone(timedelta(hours=8))`)
- **Output size**: WeCom markdown has 4096-byte limit — keep output under 3800 bytes
