Skip to content

Cron-Zeitsteuerung ​

源码版本v2026.7.20

Verantwortung ​

Den Agenten «zur richtigen Zeit automatisch laufen» lassen: Berichte, Backups, Briefings ohne menschliche Anwesenheit. Das cron/-Subsystem stellt Scheduler, Job-Speicher, Ausführungsprotokoll, Lifecycle-Guard, Blaupausen-/Vorschlagskatalog sowie eine eigene Toolset-Isolation bereit (geplante Jobs können Werkzeuge (tools) teilweise deaktivieren, um unbeaufsichtigte gefährliche Aktionen zu verhindern). Das Gateway (gateway) treibt den Scheduler mit einem 60s-Herzschlag.

Designmotiv ​

Unbeaufsichtigt ist das Kernproblem dieses Subsystems. Der Agent steht um drei Uhr morgens selbst auf — wer passt auf? Hermes antwortet mit «auf der Scheduler-Kette Stufe für Stufe Rechte abbauen»: Toolsets schneiden interaktive und cron-eröffnende Werkzeuge (cronjob) standardmäßig ab; lifecycle_guard fängt Befehle wie systemctl restart hermes-gateway im Skript ab, bevor es zur Ausführung kommt; die executions-Tabelle hinterlässt für jeden Lauf eine prüfbare Spur und markiert nach Crash unknown statt erfolgreich vorzutäuschen. «Der Agent läuft selbst» soll auditierbar und rollbackfähig sein.

Schlüsseldateien ​

Datenfluss ​

  1. Das Gateway startet _start_cron_ticker:22335, der den Scheduler alle 60s triggert (tatsächlich nach cron/scheduler_provider.py::InProcessCronScheduler verschoben; das alte Symbol ist ein Shim).
  2. Der Scheduler liest den Job-Speicher (cron/jobs.py:106) und ermittelt nach Cron-Ausdrücken die fälligen Jobs.
  3. Jeder fällige Job:
  4. Crash während der Ausführung → beim nächsten Start stellt recover_interrupted_executions (cron/executions.py:156) die Marker wieder her.
  5. Das Ergebnis wird über das Gateway zugestellt (oder zu einer Plattform gespiegelt).

Der Scheduler beschneidet Toolsets hartkodiert um drei Einträge, darüber gelegt kommt agent.disabled_toolsets aus der config.yaml des Users:

python
def _resolve_cron_disabled_toolsets(cfg: dict) -> list[str]:
    """Toolsets a cron-spawned agent must never receive."""
    disabled = ["cronjob", "messaging", "clarify"]
    agent_cfg = (cfg or {}).get("agent") or {}
    user_disabled = agent_cfg.get("disabled_toolsets") or []
    for name in user_disabled:
        name = str(name).strip()
        if name and name not in disabled:
            disabled.append(name)
    return disabled

cronjob ist verboten, damit Cron sich nicht selbst neuen Cron erzeugt (Versehen kaskadiert sonst eine Lawine von Jobs); messaging/clarify sind interaktiv und würden unbeaufsichtigt nur blocken. Skript-Jobs laufen über _run_job_script, deren Pfade in HERMES_HOME/scripts/ eingesperrt sind, um Traversal zu verhindern.

Der Ausführungs-Zustandsautomat liegt in executions.py; Zustände sind nur claimed → running → completed/failed/unknown, ein Endzustand wird nur einmal geschrieben (finish_execution schützt mit UPDATE ... WHERE status IN ('claimed','running'); doppelte Schreibversuche liefern rowcount=0 und geben None zurück). Crash-Recovery markiert nur Ausführungen, deren Prozess nicht mehr existiert, als unknown — sie tut nicht so, als wäre sie erfolgreich, und startet auch nicht automatisch neu; der Subprozess hat vielleicht schon Nebenwirkungen geschrieben, vielleicht auch nicht — das wird dem Operator überlassen zu prüfen, statt failed zu raten und ihn glauben zu lassen, ein Re-Run reiche. lifecycle_guard kompiliert systemctl restart hermes-gateway, pkill hermes gateway als Regex und scannt sowohl bei Erstellung als auch vor der Ausführung; Prompt (prompt) und Skript werden zusammengespannt gescannt, um die Split-Umgehung «im Prompt sagen, im Skript machen» zu verstopfen.

Grenzen und Fehler ​

  • Sekunden-Drift und Kollateralschaden: Der 60s-Herzschlag bedeutet, dass der Trigger bis zu einer Minute abweichen kann. mark_running_jobs_interrupted unterscheidet nicht, welcher PID getötet wurde — jeder zum Kill-Zeitpunkt laufende Job wird abgebrochen, was einen kurz vor Abschluss stehenden Job erwischen kann.
  • Nebenwirkungen im unknown-Zustand: Hat die Crash-Recovery unknown markiert, läuft nichts neu; das Skript hat vielleicht schon eine Nachricht gesendet oder Datei geschrieben. Downstream darf nicht «nicht abgeschlossen» annehmen.
  • Lease-Heartbeat verklemmt: _run_job_script_with_claim_heartbeat verlängert das Lease über einen eigenen Thread; hängt dieser Thread selbst fest (z. B. langer GIL-Halt), kann ein anderer Scheduler-Prozess denselben One-Shot-Job ein zweites Mal auslösen — doppelte Nebenwirkung.
  • Prompt-Injection blinder Fleck: Der assembled Prompt (inklusive Skill-Inhalt) wird erst zur Laufzeit gescannt; ein bösartiger Skill wird erst zum Trigger-Zeitpunkt abgefangen, bei der Job-Erstellung ist er nicht sichtbar.

Zusammenfassung ​

Im Cron-Subsystem steht «Sicherheit bei Unbeaufsichtigtheit» im Vordergrund: Toolset-Isolation, lifecycle_guard gegen Fehlbedienung, Ausführungs-Zustandsautomat für Crash-Recovery. Das Scheduling selbst wird vom Gateway-Heartbeat getrieben und braucht keinen externen Cron-Daemon.