mirror of
https://gitverse.ru/kpa39l/email-local-archive.git
synced 2026-09-29 09:15:11 +00:00
142 lines
4.8 KiB
Markdown
142 lines
4.8 KiB
Markdown
# 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. |