mirror of
https://gitverse.ru/kpa39l/email-local-archive.git
synced 2026-09-29 09:15:11 +00:00
Initial commit: Hermes skill email-local-archive
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user