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_at epoch-seconds value used to stagger heartbeats so all channels do not fire at once. A missing key (never scheduled) or an unparsable value yields None, which the caller treats as “schedule a fresh randomized first run”.

Performs a single GET on the key produced by _next_key(), decoding bytes responses to str before converting to float. Called by _tick() while deciding which active channels are due; no callers exist outside this module.

Parameters:
  • redis (Any) – Async Redis client (from bot_runner.message_cache.redis_client).

  • platform (str) – Platform identifier the channel belongs to.

  • channel_id (str) – Platform-specific channel identifier.

Returns:

The next-run epoch timestamp in seconds, or None if the key is absent or its stored value cannot be parsed as a float.

Return type:

float | None

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 SET against the key from _next_key(). Called by _tick() (and its nested _one worker) 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_S after a failed one. No callers exist outside this module.

Parameters:
  • redis (Any) – Async Redis client used to persist the timestamp.

  • platform (str) – Platform identifier the channel belongs to.

  • channel_id (str) – Platform-specific channel identifier.

  • ts (float) – Absolute epoch-seconds time at which the channel becomes due.

Return type:

None

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 an openrouter_client.OpenRouterClient configured with cfg.channel_heartbeat_model and no tools (so beats never call tools), and passes both into _tick() each iteration. Re-reads bot_runner.cfg every pass so a flipped channel_heartbeat_enabled flag or changed channel_heartbeat_tick_s takes effect live; per-tick exceptions are logged and swallowed while asyncio.CancelledError propagates, and the shared client is closed in a finally on shutdown. Called by the inference service’s channel_heartbeat_task in background_tasks.py, which is the only caller.

Parameters:

bot_runner (Any) – Runtime context exposing message_cache (for Redis), cfg, get_adapter, and processor used throughout the loop.

Return type:

None

Returns:

None