The scheduled half of a resident personal assistant: the cron jobs, market-data scripts and Discord delivery layer that run on top of Hermes Agent.
Hermes Agent gives you a messaging gateway and a scheduler. This repo is what I hang off them — a Taiwan/US market desk that files briefings into a Markdown vault and posts them to Discord, on a schedule, with no LLM anywhere in the cron path.
Two write-ups explain the thinking:
shared/ data + delivery layer used by everything else
crawlers/ research-report crawlers (dedupe -> summarize -> embed -> markdown)
briefings/ TW/US market scans and the Discord briefings built from them
wrappers/ the cron-wrapper pattern: three lines of glue per scheduled job
cron/ an example schedule
docs/ setup, the vault frontmatter contract, channel-as-persona
tools/ secret scanner, run it before you commit
No LLM in the cron path. The briefings used to be agent jobs that read a prompt and improvised. They broke constantly: path errors, tool-budget exhaustion, rate limits, a model id that 404'd for a week. They are now deterministic scripts. An LLM still writes prose — but only inside a crawler summarizing one PDF, where a failure degrades to "post the embed without a summary" instead of "no briefing today".
Writers write files; one sweeper owns git. The vault is a git repo with two writers (a Mac editing notes by hand, a scheduler writing dated files). Every writer used to commit and push for itself, with inconsistent discipline. Now nothing does — a single sweeper job commits, rebases and pushes on an interval, and shouts only on a real same-line conflict. Per-machine UI state is gitignored, because that is where the conflicts actually came from.
Secrets have exactly one door. Everything calls shared/llm.py::secret(),
which tries a secret manager and falls back to the environment. Cron wrappers
export what the script needs; the script itself never learns where its password
lives. Swapping backends is a one-file change.
Schedule to when the data exists, not when the market closes. Taiwan futures close at 13:45, but the statement emails arrive between 18:00 and 21:50. Jobs scheduled off the close reported success and posted yesterday's number for weeks. Every ingestion job now runs after its data lands, fetches a multi-day window, backfills quietly, and labels a stale value as stale instead of dressing it up as today's.
cp .env.example .env # then fill it in
pip install -r requirements.txt
python briefings/pre_scan_tw.py --lite--lite skips the slow full-market sweep, which is the fastest way to check
your data credentials work. See docs/setup.md for the rest.
Included: market scans, briefing rendering, Discord embed delivery, the unauthenticated research crawlers, the vault contract, the cron pattern.
Not included, deliberately:
- Portfolio and statement parsing. Those scripts are shaped around specific brokerage accounts and encrypted statement formats. Nothing generalizes and everything leaks.
- Authenticated crawlers. Two of my four report crawlers log into a broker's
members area; one of them needs a CAPTCHA solver to do it. That code works
against my own subscription and I am not publishing a turnkey bypass of
someone's login. The pipeline after login — dedupe, summarize, embed, file
as Markdown — is here in
crawlers/yuanta.py, which needs no auth at all. - My vault. Obviously.
MIT.