agentsclimarketplace

Hermes cron agent scheduler

Skill kjuhwa/skills-hub/skills/llm-agents/hermes-cron-agent-scheduler

Run an LLM agent on a cron schedule with inactivity timeout, silent marker, and cross-platform delivery.From its SKILL.md

Install
npx -y skills add kjuhwa/skills-hub --skill hermes-cron-agent-scheduler

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

5.9 KB, ~1.3k tokens by cl100k_base, as published. Nobody here has run it

Cron-Driven LLM Agent with Inactivity Timeout

Context

Scheduled LLM agents (daily reports, nightly audits) have two failure modes that naive timeout() wrappers miss: (1) a long tool call is legitimate and should not count as "stuck", and (2) an empty model response should not look like a successful run.

Hermes' cron path solves both, plus adds platform-agnostic delivery and a [SILENT] marker the agent can emit to suppress delivery when there's nothing to report.

When to use

  • Building a cron/scheduler layer that invokes an LLM agent.
  • Agent runs can take minutes (browser work, long test suites).
  • Delivery happens across multiple channels (Telegram, Slack, Discord, email, SMS, webhook).
  • You want the agent itself to decide "nothing to say today".

Procedure

1. Advance next_run BEFORE executing

To avoid crash-loop re-fire, update the job's next_run_at before you start the agent, not after. See cron/scheduler.py:1028-1034:

for job in due_jobs:
    advance_next_run(job["id"])   # future run already scheduled
    success, output, final_response, error = run_job(job)

Keep one-shot jobs one-shot by only advancing recurring jobs.

2. File-lock the tick so overlapping instances don't double-fire

tick() takes an exclusive file lock (fcntl.flock on Unix, msvcrt.locking on Windows) so gateway + daemon + systemd-timer overlap is safe. Skipped ticks return 0 silently (cron/scheduler.py:1001-1082).

3. Drive the agent with an inactivity timeout, not a wall-clock one

A 2-hour compilation is not "stuck". Hermes polls the agent's get_activity_summary() and only aborts when seconds_since_activity exceeds the limit:

_cron_inactivity_limit = float(os.getenv("HERMES_CRON_TIMEOUT", 600))
_cron_pool = concurrent.futures.ThreadPoolExecutor(max_workers=1)
_cron_context = contextvars.copy_context()  # preserve ContextVars
_cron_future = _cron_pool.submit(_cron_context.run, agent.run_conversation, prompt)

while True:
    done, _ = concurrent.futures.wait({_cron_future}, timeout=5.0)
    if done:
        result = _cron_future.result()
        break
    idle_secs = agent.get_activity_summary().get("seconds_since_activity", 0.0)
    if idle_secs >= _cron_inactivity_limit:
        agent.interrupt("Cron job timed out (inactivity)")
        raise TimeoutError(...)

contextvars.copy_context() is essential — without it, any skill-declared env passthrough or session-context propagation set in the scheduler thread gets lost in the worker. See cron/scheduler.py:850-914.

4. Signal "nothing to report" via a sentinel

The scheduler prepends a system hint to every cron prompt:

"SILENT: If there is genuinely nothing new to report, respond with exactly [SILENT] (nothing else) to suppress delivery. Never combine [SILENT] with content."

After the run, the scheduler checks for the marker and suppresses delivery while still saving the output locally for audit (cron/scheduler.py:600-612, 1045-1049).

5. Validate script paths for pre-run data collection

Jobs can declare script: to run a data-collection script before the LLM call. Hermes forces the script inside HERMES_HOME/scripts/ via Path.resolve() + relative_to() to block path traversal, and also redacts secrets from stdout/stderr with redact_sensitive_text() before the output enters the prompt (cron/scheduler.py:486-564).

6. Delivery: live adapter first, standalone fallback

If the gateway is running, prefer its live adapter (supports E2EE rooms like Matrix). Otherwise spin up a fresh asyncio.run() in a worker thread:

runtime_adapter = (adapters or {}).get(platform)
if runtime_adapter is not None and loop is not None and loop.is_running():
    future = asyncio.run_coroutine_threadsafe(runtime_adapter.send(...), loop)
    future.result(timeout=60)
else:
    # asyncio.run() + close-coro-on-RuntimeError fallback
    try:
        asyncio.run(coro)
    except RuntimeError:
        coro.close()
        with concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool:
            future = pool.submit(asyncio.run, _send_to_platform(...))
            future.result(timeout=30)

See cron/scheduler.py:370-445.

7. Treat empty response as soft failure

If the model completes but returns nothing, don't mark the job "ok" — that hides silent misconfiguration:

if success and not final_response:
    success = False
    error = "Agent completed but produced empty response..."

(cron/scheduler.py:1062-1065)

8. Disable toolsets the agent shouldn't have in cron context

agent = AIAgent(
    ...,
    disabled_toolsets=["cronjob", "messaging", "clarify"],
    skip_context_files=True,  # no SOUL.md/AGENTS.md injection
    skip_memory=True,          # don't let cron corrupt user memory
    platform="cron",
)

skip_memory=True prevents the scheduled prompt from being written back into persistent user-memory files.

Pitfalls

  • Don't use a wall-clock timeout. A 20-minute pip install or 2-hour dataset download will be killed even though the agent is making progress.
  • Don't forget contextvars.copy_context() around the worker submit — ContextVars don't cross thread boundaries automatically.
  • Clean up injected env vars in a finally block so HERMES_CRON_AUTO_DELIVER_* etc. don't leak into the next job.
  • Validate platform names against an allowlist (_KNOWN_DELIVERY_PLATFORMS) to prevent env-var enumeration via crafted deliver: values.

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Gives 0 of the 12 instructions most context ai engineering skills give in ~1.3k tokens

Counted across 1,193 of the 1,976 authors here whose files we hold, read 2026-08-07

  • Dispatch a fresh implementer subagent per taskin 48 of 1193, across 19 files
  • Dispatch a final code reviewer after all tasksin 33 of 1193, across 8 files
  • Provide full task text to the subagentin 30 of 1193, across 9 files
  • Review spec compliance before code qualityin 27 of 1193, across 10 files
  • Make the hook script executablein 26 of 1193, across 8 files
  • Re-snapshot after navigation or DOM changesin 25 of 1193, across 19 files
  • Read files before editing themin 22 of 1193, across 11 files
  • Answer subagent questions before proceedingin 22 of 1193, across 7 files
  • Mark task complete in TodoWrite after approvalin 22 of 1193, across 6 files
  • Merge hook into existing settingsin 21 of 1193, across 3 files
  • Ask if installation is global or projectin 20 of 1193, across 2 files
  • Copy the hook script to target locationin 20 of 1193, across 2 files

Said here and by no other author read

  • advance next run before executing job
  • file-lock the tick to prevent overlap
  • abort on inactivity timeout not wall-clock
  • copy context before submitting worker thread
  • suppress delivery on silent marker
  • redact secrets from pre-run script output

Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.

Keep looking

Skills are one crate of 326,149. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.