Files
2026-09-06 13:51:14 +00:00

142 lines
4.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Cron Workflows for Email Archive
## Architecture overview
Two independent cron pipelines that share the same email storage:
```
mail-archive (every 5 min, no_agent=True)
↓
INBOX/UID/email.md ←── contacts-extractor (every 30 min, script-based, LLM)
↓
contacts.vcf + contacts.json
```
Both use **script-based cron** (the `script=` parameter) with the script in
`~/.hermes/scripts/`. The archive job is `no_agent=True` (pure shell — no LLM
tokens consumed). The contacts job uses the agent loop because it calls LLM
internally via Python.
## Mail archive cron (bulk sync)
```bash
# Script: ~/.hermes/scripts/mail-archive.sh
#!/usr/bin/env bash
set -euo pipefail
cd /opt/hermes/email-assistant
exec python3 scripts/mail_archive.py --all --limit 10
```
Cron creation:
```bash
hermes cron create \
--name "mail-archive-every-5min" \
--schedule "every 5m" \
--script mail-archive.sh \
--no-agent \
--deliver local
```
Key details:
- `no_agent=True` → pure script mode, zero LLM cost per tick
- `deliver=local` → output saved, no notification (noisy at 5min intervals)
- Script path MUST be relative in `~/.hermes/scripts/` — absolute paths rejected
- `--all --limit 10` processes all 22 folders with 10 emails per folder per tick
### Shell wrapper with per-folder timeouts
When using `--all`, one slow IMAP folder can stall the entire run. The
shell-embedded approach handles this natively — each folder gets its own
`timeout`:
```bash
FOLDERS=(INBOX "Отправленные" Archive Sent ...)
TIMEOUT=60
LIMIT=10
for folder in "${FOLDERS[@]}"; do
timeout $TIMEOUT python3 scripts/mail_archive.py --folder "$folder" --limit $LIMIT 2>&1 || true
done
```
This is the ACTUAL approach used in `mail-archive.sh`. The `--all` flag in
`mail_archive.py` iterates folders internally but without per-folder timeouts,
so the shell wrapper is the recommended pattern when you control the cron script.
## Contacts extractor cron (LLM-based)
```bash
# Script: ~/.hermes/scripts/contacts-cron.sh
#!/usr/bin/env bash
set -euo pipefail
cd /opt/hermes/email-assistant
exec python3 scripts/contacts_extractor.py --limit 15
```
Cron creation:
```bash
hermes cron create \
--name "contacts-extractor-every-30m" \
--schedule "every 30m" \
--script contacts-cron.sh \
--deliver local
```
Key differences from archive cron:
- **NO `--no-agent`** — the contacts extractor calls LLM (Qwen3:8b via Ollama)
internally. Without the agent loop, the script runs but output isn't
delivered/visible.
- **`--limit 15`** — Qwen3:8b takes ~20s per email. 15 emails × 20s = ~5 min,
well within the 30-min window. Bump to 25-30 if Qwen is on a GPU.
- **`deliver=local`** — results saved to disk, no notification. The agent
generates a summary message on each tick.
## Transition from bulk to incremental
When state files stop advancing (all emails archived):
1. Update archive cron: smaller limit or longer interval
```bash
hermes cron update <archive-id> --schedule "every 30m"
```
2. Keep contacts cron at `every 30m` — it always processes only new emails
(UID tracking in `last_scan.json`)
## Testing cron scripts
Before scheduling, verify the script works by running it once:
```bash
timeout 120 bash ~/.hermes/scripts/contacts-cron.sh
```
Check for:
- Exit code 0 = success; 124 = timeout (reduce `--limit`)
- Stale output = script isn't finding new files (check `last_scan.json` UIDs)
- Python import errors = missing dependencies (run `pip install -r requirements.txt`)
## Pitfalls
1. **Script path MUST be relative.** `--script /absolute/path` is silently
rejected. Copy the script to `~/.hermes/scripts/` and pass just the filename.
2. **Contacts cron needs the agent loop.** Unlike the pure-shell archive cron,
contacts cron must NOT have `--no-agent`. Without the agent, the LLM calls
in `contacts_extractor.py` still execute (it's Python), but the job output
is never delivered — you'd see "last_status=completed, last_output=<empty>"
even though contacts.vcf was updated.
3. **Qwen3:8b speed varies.** On CPU-only Ollama it's ~20s/email. On discrete
GPU (NVIDIA, AMD ROCm) it's ~2-3s/email. Set `--limit` accordingly:
- CPU: 10-15 emails per 5-min cron window
- GPU: 50-100 emails per 5-min window
4. **Cron jobs run from the session's last state, not a fresh login.**
Environment variables (like `PATH`) may differ. Always use absolute paths or
`cd` to the project directory in the script.
5. **`deliver=local` vs `deliver=origin`.** `local` saves output to the cron
DB only (viewable via `cronjob action=list`). `origin` sends it back to the
Hermes session that created the cron. For per-5min archive runs, `local`
avoids spam. For contacts (every 30min), consider `origin` if you want
a notification.