--- name: recurring-briefings description: Set up cron jobs for periodic data-gathering and formatted briefings — weather checks, market updates, monitoring digests, or any recurring "fetch → analyze → deliver" workflow. version: 1.0.0 author: Hermes Agent tags: [cron, scheduling, briefings, automation, weather, stocks, monitoring] --- # Recurring Briefings Set up automated daily/weekly briefings that fetch external data, analyze it against user-defined constraints, and deliver formatted reports on a schedule. ## Core Pattern Every recurring briefing follows the same shape: ``` cron job (scheduled) → fetch data (terminal/curl) → analyze → format → deliver (Telegram) ``` ## Step 1 — Define the Briefing Clarify with the user: | Question | Example | |----------|---------| | **What data?** | Weather, stock chatter, news headlines | | **What constraints/filters?** | "After 6pm on weekdays," "most talked-about stocks" | | **When?** | "Every Monday 8 AM," "Weekdays 8:30 AM" | | **Format?** | Bullet list, table-free (Telegram), emoji headers | ## Step 2 — Create the Cron Job Use `cronjob(action='create')` with these settings: ```yaml schedule: cron expression or human string like "0 8 * * 1" (Mon 8 AM) enabled_toolsets: ["web", "terminal"] # for fetching data ``` ### Cron Schedule Cheatsheet | Pattern | Meaning | |---------|---------| | `30 8 * * 1-5` | Mon–Fri at 8:30 AM | | `0 8 * * 1` | Monday at 8 AM | | `0 9 * * *` | Every day at 9 AM | | `0 9 1 * *` | 1st of every month at 9 AM | ### Prompt Structure in the Cron Job The prompt should be self-contained (cron jobs have no conversation context). Include: 1. **The agent's role** — "You are a [weather/stock/market] briefing agent" 2. **Explicit data sources to call** — e.g. "curl -s wttr.in/Knoxville+TN+37919 to get the JSON forecast, then parse hourly data" 3. **User constraints** — "Weekdays only after 6pm, weekends anytime" 4. **Format instructions** — "Output as a Telegram-friendly message with emoji headers" 5. **Anti-hallucination rule** — "Actually call the sources. Do not make up data. If a source fails, note it and move on." ## Step 3 — Data Sources > **Firecrawl setup:** see `references/firecrawl-setup.md` for installation, API key config, and SDK usage patterns for scraping JS-heavy financial sites. ### Weather (free, no API key) ```bash # JSON forecast (3 days) curl -s "wttr.in/LOCATION?format=j1&days=3" # Formatted terminal output curl -s "wttr.in/LOCATION?0&days=3" ``` Parse hourly data to find dry windows matching the user's time constraints (after 6pm weekdays, anytime weekends). Key fields: `chanceofrain`, `precipMM`, `tempF`, `windspeedMiles`, `weatherDesc`. ### Stock/Financial Buzz - **Firecrawl (preferred)** — see `references/firecrawl-setup.md` for setup. Bypasses JS-rendered paywalls and blocks that kill raw curl on Yahoo Finance, MarketWatch, etc. Free tier: 500 credits/month (~4 sources × 21 weekdays = 84 credits). **Firecrawl explicitly blocks Reddit** — do not attempt Reddit URLs with it. - **Yahoo Finance quote API** — `query1.finance.yahoo.com/v8/finance/chart/SYMBOL` still works with a proper User-Agent header. Use for real-time prices on discovered tickers. - **Google News RSS** — `news.google.com/rss/search?q=stock+market+today` works reliably via curl. - **Reddit (r/wallstreetbets only)** — the `.rss` endpoint at `https://www.reddit.com/r/wallstreetbets/.rss` returns valid Atom XML (HTTP 200). This is the ONLY working Reddit access path. Full Reddit scraping landscape: `references/reddit-scraping.md`. All other subs (r/stocks, r/StockMarket, r/pennystocks) return 403 on both `.rss` and `.json`. Parse XML titles for ticker mentions ($TICKER or all-caps symbols). ## Formatting for Telegram Delivery Cron jobs auto-deliver to the home channel. Format for Telegram: ``` 📈 **Morning Stock Buzz — [Date]** **🔥 Most Talked-About Stocks** - **TICKER** — why it's trending **📰 Top Headlines** - Headline 1 - Headline 2 **📊 Market Snapshot** - Futures direction, sector trends, notable movers ``` Use: **bold**, bullet lists, emoji headers. No tables (Telegram rewrites them to bullets anyway). ## Pitfalls - **Self-contained prompts** — cron jobs have no memory of past conversations. Every detail the agent needs must be in the prompt. - **Rate-limited APIs** — GitHub and Reddit may 429 without proper auth. Use `GITHUB_TOKEN` env var for GitHub API calls. - **CGNAT blocks external access** — if a briefing references a self-hosted service (like Immich), the link must use the local LAN IP, not the CGNAT address. - **Reddit is perma-blocked** — the JSON API (`old.reddit.com/r/*.json`, `reddit.com/r/*/hot.json`) returns HTML blocks, not JSON. Firecrawl explicitly rejects Reddit (`403: "We do not support this site"`). The only working fallback: `r/wallstreetbets/.rss` returns valid Atom XML. All other subreddit RSS feeds (`r/stocks`, `r/StockMarket`, `r/pennystocks`) return HTTP 403. Use WSB `.rss` as the sole Reddit source in briefing prompts; skip Reddit entirely if WSB isn't sufficient. - **Firecrawl API key setup** — must be added directly to `~/.hermes/.env` (terminal echo/append), NOT via `hermes config set env.FIRECRAWL_API_KEY` which rejects the dotted key format. See `references/firecrawl-setup.md`. - **Weekend vs weekday** — stock/market briefings should run Mon–Fri only ($30 8 * * 1-5$). Weather/mowing briefings can include weekends. - **No `web_search` tool** — use `terminal` with `curl` instead. Design the prompt to use curl-based data collection. ## Alternative: no_agent=True (Watchdog Pattern) For **deterministic monitoring** where the output is pre-defined (drive health checks, service uptime, disk space alerts), use `no_agent=True` with a script instead of the LLM-driven approach: ```python cronjob( action="create", name="daily-disk-check", schedule="0 5 * * *", script="disk-alert.py", # ~/.hermes/scripts/disk-alert.py no_agent=True # script stdout = delivery message ) ``` **When to use `no_agent=True` vs default (LLM-driven):** | Aspect | `no_agent=True` (watchdog) | `no_agent=False` (briefing) | |--------|---------------------------|---------------------------| | Cost | Zero tokens per run | LLM inference per run | | Output | Script stdout verbatim | Agent formats the message | | Use case | Threshold checks, alerts, pings | Summaries, digests, formatted briefings | | Silent on no-op | Script prints nothing = silent | Agent always produces output | **Key behaviors:** - Non-empty stdin → delivered as the message - Empty stdout → SILENT (nothing sent to user — perfect for "all clear" checks) - Non-zero exit / timeout → error alert sent to user (can't fail silently)