Initial commit: Hermes skill email-local-archive

This commit is contained in:
estorozhenko
2026-09-06 13:51:14 +00:00
commit b123d2d3b4
7 changed files with 1386 additions and 0 deletions
+142
View File
@@ -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.