feat(curator): hook into the gateway's cron-ticker thread

Long-running gateways need the curator to fire on cadence without
restarts. Piggy-back on the existing cron ticker thread (which already
runs image/document cache cleanup every hour on the same pattern)
instead of spawning a dedicated timer thread.

- New CURATOR_EVERY = 60 ticks (poll hourly at default 60s interval).
  The inner config.interval_hours gate controls the real cadence, so
  60 of these 60 hourly pokes are cheap no-ops and one runs the review.
- Removed the boot-time call added in the prior commit — the ticker
  covers boot + every hour thereafter. Avoids double-running.

Handles the weekly-default-on-24/7-gateway gap flagged in review.
This commit is contained in:
Teknium
2026-04-26 07:16:27 -07:00
committed by Teknium
parent a12f7aa8bb
commit 019d4c1c3f

View File

@@ -2383,29 +2383,6 @@ class GatewayRunner:
# Discover and load event hooks
self.hooks.discover_and_load()
# Curator — kick off a background skill-maintenance pass on gateway
# startup if the schedule says we're due. Runs in a daemon thread
# so it never blocks gateway startup. Best-effort; any failure is
# swallowed. The interval_hours gate prevents re-running on quick
# restarts.
try:
from agent.curator import maybe_run_curator
def _curator_summary(msg: str) -> None:
# Surface the one-line summary into gateway logs so operators
# can see what the curator did. No per-platform push since
# there's no user-facing session at gateway boot.
logger.info("curator: %s", msg)
maybe_run_curator(
idle_for_seconds=float("inf"), # gateway boot = no active agent
on_summary=_curator_summary,
)
except Exception:
logger.debug(
"curator boot hook failed", exc_info=True,
)
# Recover background processes from checkpoint (crash recovery)
try:
@@ -11767,6 +11744,7 @@ def _start_cron_ticker(stop_event: threading.Event, adapters=None, loop=None, in
IMAGE_CACHE_EVERY = 60 # ticks — once per hour at default 60s interval
CHANNEL_DIR_EVERY = 5 # ticks — every 5 minutes
PASTE_SWEEP_EVERY = 60 # ticks — once per hour
CURATOR_EVERY = 60 # ticks — poll hourly (inner gate handles the real cadence)
logger.info("Cron ticker started (interval=%ds)", interval)
tick_count = 0
@@ -11818,6 +11796,21 @@ def _start_cron_ticker(stop_event: threading.Event, adapters=None, loop=None, in
except Exception as e:
logger.debug("Paste sweep error: %s", e)
# Curator — piggy-back on the existing cron ticker so long-running
# gateways get weekly skill maintenance without needing restarts.
# maybe_run_curator() is internally gated by config.interval_hours
# (7 days by default), so CURATOR_EVERY is just the poll rate — the
# real work only fires once per config interval.
if tick_count % CURATOR_EVERY == 0:
try:
from agent.curator import maybe_run_curator
maybe_run_curator(
idle_for_seconds=float("inf"),
on_summary=lambda msg: logger.info("curator: %s", msg),
)
except Exception as e:
logger.debug("Curator tick error: %s", e)
stop_event.wait(timeout=interval)
logger.info("Cron ticker stopped")