184 lines
7.0 KiB
Markdown
184 lines
7.0 KiB
Markdown
---
|
||
name: cron-ops
|
||
version: "1.0.1"
|
||
date: 2026-09-10
|
||
description: "Use when managing cron jobs or configuring fallback chains."
|
||
metadata:
|
||
version: "1.0.1"
|
||
author: "小唯"
|
||
category: "devops"
|
||
tags: ["cron", "fallback", "retry", "scheduled-tasks", "运维"]
|
||
---
|
||
|
||
# Cron 运维 Skill
|
||
|
||
Hermes 定时任务的配置、fallback、重试和故障排查。
|
||
|
||
## 配置结构
|
||
|
||
`~/.hermes/config.yaml` 中 cron 段:
|
||
|
||
```yaml
|
||
cron:
|
||
model: glm-4-flash # 主模型
|
||
model_provider: zhipu # 主 provider
|
||
fallback_model: agnes-2.5-flash # fallback 模型
|
||
fallback_provider: agnes # fallback provider
|
||
provider: auto # auto = 按 model_provider 走
|
||
gateway_required: true # gateway 在才 fire
|
||
wrap_response: true
|
||
```
|
||
|
||
**Fallback 链**: 主模型失败 → fallback_model → 全局 fallback_providers(config.yaml 顶层)
|
||
|
||
## Job 配置
|
||
|
||
`~/.hermes/cron/jobs.json` 中每个 job:
|
||
|
||
```json
|
||
{
|
||
"id": "5b108ad99991",
|
||
"name": "蒸馏模型看门狗",
|
||
"script": "distill-model-watchdog.py",
|
||
"no_agent": true,
|
||
"schedule": "every 30m",
|
||
"model": null,
|
||
"provider": null
|
||
}
|
||
```
|
||
|
||
**关键**: `model`/`provider` 为 null 时继承 cron 默认配置;设置具体值会覆盖全局 fallback。
|
||
|
||
## 两种模式
|
||
|
||
| 模式 | no_agent | 执行方式 | 适用场景 |
|
||
|------|----------|----------|----------|
|
||
| **脚本模式** | true | 直接执行脚本,stdout 投递 | 看门狗、数据采集、健康检查 |
|
||
| **Agent 模式** | false | LLM 执行 prompt,可用工具 | 分析、报告、需要推理的任务 |
|
||
|
||
## 脚本路径规则(重要 Pitfall)
|
||
|
||
1. **必须用绝对路径**: `/home/muc/.hermes/scripts/xxx.sh`,不能 `~/.hermes/scripts/xxx.sh`
|
||
2. **不支持参数**: script 字段不能 `script.sh arg1 arg2`,cron 系统会把整个字符串当文件路径
|
||
3. **脚本必须在 scripts 目录**: `~/.hermes/scripts/` 下,否则报 "Script not found"
|
||
4. **Python 脚本自动用 sys.executable**: `.py` 文件用当前 Python 解释器执行
|
||
5. **Shell 脚本自动用 bash**: `.sh`/`.bash` 文件用 `/bin/bash` 执行
|
||
|
||
## 投递目标(deliver)语义(2026-09-05 实测)
|
||
|
||
| 值 | 实际去向 | 注意 |
|
||
|----|----------|------|
|
||
| `origin` | **创建该 job 时的 chat** | 在 DM 里创建 → 通知回私聊!不是自动去群 |
|
||
| `feishu`(裸值) | Home channel(= 当前 profile 的 Home,可能是 DM) | 同样可能落私聊 |
|
||
| `local` | 仅存 `~/.hermes/cron/output/`,无消息投递 | watchdog 静默模式 |
|
||
| `feishu:<chat_id>` | 显式指定 chat | 群通知必须显式写群 ID |
|
||
|
||
- **⛔ 飞书 DM 的 chat_id 也以 `oc_` 开头**(如 `oc_cd14...`)——不能凭前缀判断私聊/群。判定靠 MEMORY 映射:群=`oc_81f6df701c872a1122f32080e366543f`;小唯↔牧尘 DM=`oc_cd14ec7518926e57d26c5e339ebba3b3`(user `ou_f20eb15b3a76639fed35977c01ddcbb4`)。
|
||
- **⛔ 牧尘铁律:任务通知一律发群,不发私聊**。新建带通知的 cron 时 deliver 直接写 `feishu:oc_81f6df701c872a1122f32080e366543f`,别依赖 origin 默认。
|
||
- 检查存量:`jobs.json` 里 `deliver: origin` 或裸 `feishu` 的 job 若创建于 DM = 通知发私聊 → 逐个 update 为群 ID。
|
||
|
||
## Fallback 配置最佳实践
|
||
|
||
### Agent 模式 jobs
|
||
- 设置 `model: null`, `provider: null` → 继承 cron 默认
|
||
- 默认配置已有 fallback: zhipu → agnes
|
||
- 全局 fallback: mimo → deepseek → opencode-free
|
||
|
||
### 脚本模式 jobs
|
||
- 脚本内部实现重试逻辑(Hermes 不自动重试脚本)
|
||
- 推荐模式:
|
||
|
||
```python
|
||
import subprocess, sys, time
|
||
|
||
def run_with_retry(cmd, max_retries=3, delay=5):
|
||
for i in range(max_retries):
|
||
result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
|
||
if result.returncode == 0:
|
||
print(result.stdout)
|
||
return 0
|
||
if i < max_retries - 1:
|
||
time.sleep(delay)
|
||
return result.returncode
|
||
|
||
if __name__ == "__main__":
|
||
sys.exit(run_with_retry([sys.executable, "target_script.py"]))
|
||
```
|
||
|
||
## 常用命令
|
||
|
||
```bash
|
||
# 列出所有 jobs
|
||
hermes cron list
|
||
|
||
# 手动触发
|
||
hermes cron run <job_id>
|
||
|
||
# 查看 job 详情
|
||
hermes cron show <job_id>
|
||
|
||
# 创建 job
|
||
hermes cron create "every 30m" --name "任务名" --script "script.py" --no-agent
|
||
|
||
# 暂停/恢复
|
||
hermes cron pause <job_id>
|
||
hermes cron resume <job_id>
|
||
```
|
||
|
||
## 故障排查
|
||
|
||
### Job 状态为 error
|
||
1. 检查 `last_fire_error` 字段
|
||
2. 脚本路径是否正确(绝对路径)
|
||
3. 脚本是否有执行权限
|
||
4. 手动执行脚本测试
|
||
5. **先验上游依赖是否运行**(非脚本问题占多数):
|
||
- llama-server cron → 查 `pgrep -fa llama-server` 或 `systemctl --user is-active llama-server-7b`
|
||
- 织忆 cron → 查 `curl -s http://127.0.0.1:7821/health`
|
||
- 股票 cron → 查网络/API key
|
||
- **41 次连续失败先看失败时间是否集中在某服务未启动的时间窗口**
|
||
|
||
### Fallback 不生效
|
||
1. 检查 job 的 `model`/`provider` 是否覆盖了全局
|
||
2. 确认 fallback_provider 的 API key 在 .env 中
|
||
3. 检查 fallback 模型是否可用
|
||
|
||
### 脚本静默失败
|
||
- no_agent 模式下,空 stdout = 静默(不投递)
|
||
- 检查脚本是否有输出
|
||
- 查看 journalctl --user -u hermes-gateway
|
||
|
||
### 飞书 TTS 静默失败
|
||
- MiMo quota 429 exhausted → 飞书 TTS 消息被吞不报错
|
||
- 验证:直接 curl MiMo TTS 端点看是否返回 quota error
|
||
- 备选:切 edge-tts(内置,无 quota 限制)
|
||
|
||
## Pitfalls
|
||
|
||
1. **不要覆盖 job 级 model/provider**: 除非特殊需求,让 job 继承 cron 默认配置
|
||
2. **脚本重试在脚本内部实现**: Hermes 不自动重试 no_agent 脚本
|
||
3. **绝对路径**: 所有脚本路径必须是绝对路径
|
||
4. **不支持参数**: 脚本不能带参数,需要参数时写 wrapper 脚本
|
||
|
||
### 模式 8 复发实例 + 全库扫描根治(2026-09-05)
|
||
|
||
同一天两个 no_agent 看门狗踩同一坑(各自脚本带 `--apply` 被当文件名):
|
||
- `backup-cleanup.py --apply`(备份自动清理 6a87615b5155)→ Script not found → 备份清理停摆数天
|
||
- `resource-watchdog.py --apply`(文件资源看门狗 c336c1c3b39f)→ 同上
|
||
|
||
**根治修复模板**(wrapper 放 ~/.hermes/scripts/,chmod +x,`hermes cron edit <id> --script <wrapper>`):
|
||
```bash
|
||
#!/bin/bash
|
||
# <name> cron wrapper(参数放这里)
|
||
exec python3 /home/muc/.hermes/scripts/<script>.py --apply
|
||
```
|
||
|
||
**全库扫描根治检查**(新建/修改 no_agent job 后必跑):
|
||
```bash
|
||
python3 -c "import json; d=json.load(open('/home/muc/.hermes/cron/jobs.json')); jobs=d.get('jobs',d) if isinstance(d,dict) else d; items=jobs if isinstance(jobs,list) else list(jobs.values()); [print('❌', j['id'][:12], j.get('name'), repr(j.get('script'))) for j in items if j.get('script') and ' ' in str(j.get('script'))]"
|
||
# 无输出 = 全部干净;有 ❌ = 还有带参 script 待修
|
||
```
|
||
|
||
**症状识别**:cron 输出 "Script not found: /path/to/xxx.py --apply"(把参数拼进路径找)→ 100% 模式 8。修复后 `hermes cron run <id>` 验证 succeeded。
|
||
5. **状态文件保护**: 脚本的状态文件(如 rsshub_ai_seen.json)要防并发写入
|