background_agents.channel_heartbeat module
Per-channel staggered heartbeat: Redis next_at + short polling tick.
- async background_agents.channel_heartbeat.get_next_run(redis, platform, channel_id)[source]
Read a channel’s next scheduled heartbeat timestamp from Redis.
Fetches and parses the per-channel
next_atepoch-seconds value used to stagger heartbeats so all channels do not fire at once. A missing key (never scheduled) or an unparsable value yieldsNone, which the caller treats as “schedule a fresh randomized first run”.Performs a single
GETon the key produced by_next_key(), decoding bytes responses tostrbefore converting tofloat. Called by_tick()while deciding which active channels are due; no callers exist outside this module.- Parameters:
- Returns:
The next-run epoch timestamp in seconds, or
Noneif the key is absent or its stored value cannot be parsed as a float.- Return type:
- async background_agents.channel_heartbeat.set_next_run(redis, platform, channel_id, ts)[source]
Persist a channel’s next scheduled heartbeat timestamp to Redis.
Stores
ts(epoch seconds, six decimal places) under the per-channel key, overwriting any prior value, so the next_tick()pass knows when this channel becomes due again. The key has no TTL; it is rewritten on every scheduling decision.Issues a single
SETagainst the key from_next_key(). Called by_tick()(and its nested_oneworker) to spread an initial run, push a disabled or adapter-down channel forward, apply the normal randomized interval after a successful beat, or apply_FAILURE_BACKOFF_Safter a failed one. No callers exist outside this module.- Parameters:
- Return type:
- Returns:
None
- async background_agents.channel_heartbeat.channel_heartbeat_loop(bot_runner)[source]
Run the forever loop that drives staggered per-channel heartbeats.
The long-lived entry point for the channel-heartbeat background agent: it builds one shared flash-model LLM client, then loops on a short tick, delegating each pass to
_tick()so the most-recently-used channels are beaten on their own randomized schedules rather than all at once. This keeps the bot’s per-channel awareness warm cheaply; the loop exits immediately if no Redis is available and otherwise runs until cancelled.Resolves the async Redis client from
bot_runner.message_cache.redis_client, constructs anopenrouter_client.OpenRouterClientconfigured withcfg.channel_heartbeat_modeland no tools (so beats never call tools), and passes both into_tick()each iteration. Re-readsbot_runner.cfgevery pass so a flippedchannel_heartbeat_enabledflag or changedchannel_heartbeat_tick_stakes effect live; per-tick exceptions are logged and swallowed whileasyncio.CancelledErrorpropagates, and the shared client is closed in afinallyon shutdown. Called by the inference service’schannel_heartbeat_taskinbackground_tasks.py, which is the only caller.