feat(cron): routing intent — deliver=all fans out to every connected channel (#21495)

Adds one reserved token to the cron `deliver` field:

- `all` — expand to every platform with a configured home channel

Resolves at fire time, not create time, so a job created before Telegram
was wired up picks it up once `TELEGRAM_HOME_CHANNEL` is set. Composes
with existing targets: `origin,all`, `all,telegram:-100:17`.

Inspired by Vellum Assistant's reminder routing-intent system.

## Changes
- cron/scheduler.py: _expand_routing_tokens + integrate into _resolve_delivery_targets
- tools/cronjob_tools.py: schema description updated
- tests/cron/test_scheduler.py: TestRoutingIntents (5 cases)
- website/docs/user-guide/features/cron.md: docs + table rows

## Validation
- tests/cron/test_scheduler.py -k 'Routing or Deliver' → 57 passed
This commit is contained in:
Teknium 2026-05-08 04:17:21 -07:00 committed by GitHub
parent 81928f03ab
commit 486b14b423
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 143 additions and 3 deletions

View file

@ -360,12 +360,52 @@ def _normalize_deliver_value(deliver) -> str:
return str(deliver)
# Routing intent tokens — resolved at fire time, not create time, so a
# job created before Telegram was wired up will pick up Telegram once it
# comes online. ``all`` expands into the set of connected platforms
# (those with a configured home chat_id) in _expand_routing_tokens.
_ROUTING_TOKENS = frozenset({"all"})
def _expand_routing_tokens(part: str) -> List[str]:
"""Expand a routing-intent token to concrete platform names.
``all`` expands to every platform in ``_iter_home_target_platforms()``
that has a configured home chat_id right now. Unknown / non-token
values pass through unchanged as a single-element list, so the caller
can treat every token uniformly.
"""
token = part.lower()
if token not in _ROUTING_TOKENS:
return [part]
expanded: List[str] = []
for platform_name in _iter_home_target_platforms():
if _get_home_target_chat_id(platform_name):
expanded.append(platform_name)
return expanded
def _resolve_delivery_targets(job: dict) -> List[dict]:
"""Resolve all concrete auto-delivery targets for a cron job (supports comma-separated deliver)."""
"""Resolve all concrete auto-delivery targets for a cron job.
Accepts the legacy comma-separated ``deliver`` string plus the
``all`` routing-intent token, which expands to every platform with
a configured home channel. Tokens may be combined with explicit
targets: ``origin,all`` and ``all,telegram:-100:17`` both work.
Duplicate (platform, chat_id, thread_id) tuples are collapsed by the
existing dedup pass.
"""
deliver = _normalize_deliver_value(job.get("deliver", "local"))
if deliver == "local":
return []
parts = [p.strip() for p in deliver.split(",") if p.strip()]
raw_parts = [p.strip() for p in deliver.split(",") if p.strip()]
# Expand routing intents.
parts: List[str] = []
for raw in raw_parts:
parts.extend(_expand_routing_tokens(raw))
seen = set()
targets = []
for part in parts:

View file

@ -351,6 +351,95 @@ class TestResolveDeliveryTarget:
assert _resolve_delivery_targets({"deliver": []}) == []
class TestRoutingIntents:
"""``all`` routing intent expands at fire time."""
def test_all_expands_to_every_connected_home_channel(self, monkeypatch):
"""deliver='all' fans out to every platform with a configured home channel."""
from cron.scheduler import _resolve_delivery_targets
monkeypatch.setenv("TELEGRAM_HOME_CHANNEL", "-111")
monkeypatch.setenv("DISCORD_HOME_CHANNEL", "-222")
monkeypatch.setenv("SLACK_HOME_CHANNEL", "C333")
# Sanity: platforms without the env var must NOT appear in the expansion.
monkeypatch.delenv("SIGNAL_HOME_CHANNEL", raising=False)
monkeypatch.delenv("MATRIX_HOME_ROOM", raising=False)
targets = _resolve_delivery_targets({"deliver": "all", "origin": None})
platforms = sorted(t["platform"] for t in targets)
assert "telegram" in platforms
assert "discord" in platforms
assert "slack" in platforms
assert "signal" not in platforms
assert "matrix" not in platforms
def test_all_combines_with_explicit_target_and_dedups(self, monkeypatch):
"""'telegram:-999,all' yields every home channel + the explicit target without dupes."""
from cron.scheduler import _resolve_delivery_targets
monkeypatch.setenv("TELEGRAM_HOME_CHANNEL", "-111")
monkeypatch.setenv("DISCORD_HOME_CHANNEL", "-222")
# Explicit telegram target precedes 'all'. Expansion adds discord;
# the dedup pass collapses any (platform, chat_id, thread_id) repeats.
job = {"deliver": "telegram:-999,all", "origin": None}
targets = _resolve_delivery_targets(job)
platforms = sorted(t["platform"].lower() for t in targets)
assert "telegram" in platforms
assert "discord" in platforms
# Every target is unique on (platform, chat_id, thread_id).
keys = [(t["platform"].lower(), str(t["chat_id"]), t.get("thread_id")) for t in targets]
assert len(keys) == len(set(keys))
def test_all_with_no_connected_channels_returns_empty(self, monkeypatch):
"""deliver='all' with nothing connected returns [] — delivery is recorded as failed upstream."""
from cron.scheduler import _resolve_delivery_targets
for var in ("TELEGRAM_HOME_CHANNEL", "DISCORD_HOME_CHANNEL", "SLACK_HOME_CHANNEL",
"SIGNAL_HOME_CHANNEL", "MATRIX_HOME_ROOM", "MATTERMOST_HOME_CHANNEL",
"SMS_HOME_CHANNEL", "EMAIL_HOME_ADDRESS", "DINGTALK_HOME_CHANNEL",
"FEISHU_HOME_CHANNEL", "WECOM_HOME_CHANNEL", "WEIXIN_HOME_CHANNEL",
"BLUEBUBBLES_HOME_CHANNEL", "QQBOT_HOME_CHANNEL", "QQ_HOME_CHANNEL"):
monkeypatch.delenv(var, raising=False)
assert _resolve_delivery_targets({"deliver": "all", "origin": None}) == []
def test_origin_comma_all_preserves_origin_first(self, monkeypatch):
"""'origin,all' delivers to the origin platform plus every other home channel."""
from cron.scheduler import _resolve_delivery_targets
monkeypatch.setenv("TELEGRAM_HOME_CHANNEL", "-111")
monkeypatch.setenv("DISCORD_HOME_CHANNEL", "-222")
job = {
"deliver": "origin,all",
"origin": {"platform": "discord", "chat_id": "888"},
}
targets = _resolve_delivery_targets(job)
platforms = sorted(t["platform"].lower() for t in targets)
assert "telegram" in platforms
assert "discord" in platforms
# The origin's explicit chat_id (888) wins the dedup race over the
# discord home channel (-222) because origin is resolved first.
discord = next(t for t in targets if t["platform"].lower() == "discord")
assert discord["chat_id"] == "888"
def test_all_token_case_insensitive(self, monkeypatch):
"""'ALL' / 'All' / 'all' are all recognized."""
from cron.scheduler import _resolve_delivery_targets
monkeypatch.setenv("TELEGRAM_HOME_CHANNEL", "-111")
monkeypatch.setenv("DISCORD_HOME_CHANNEL", "-222")
for token in ("ALL", "All", "all"):
targets = _resolve_delivery_targets({"deliver": token, "origin": None})
platforms = sorted(t["platform"].lower() for t in targets)
assert platforms == ["discord", "telegram"], f"token={token!r} -> {platforms}"
class TestDeliverResultWrapping:
"""Verify that cron deliveries are wrapped with header/footer and no longer mirrored."""

View file

@ -541,7 +541,7 @@ Important safety rule: cron-run sessions should not recursively schedule more cr
},
"deliver": {
"type": "string",
"description": "Omit this parameter to auto-deliver back to the current chat and topic (recommended). Auto-detection preserves thread/topic context. Only set explicitly when the user asks to deliver somewhere OTHER than the current conversation. Values: 'origin' (same as omitting), 'local' (no delivery, save only), or platform:chat_id:thread_id for a specific destination. Examples: 'telegram:-1001234567890:17585', 'discord:#engineering', 'sms:+15551234567'. WARNING: 'platform:chat_id' without :thread_id loses topic targeting."
"description": "Omit this parameter to auto-deliver back to the current chat and topic (recommended). Auto-detection preserves thread/topic context. Only set explicitly when the user asks to deliver somewhere OTHER than the current conversation. Values: 'origin' (same as omitting), 'local' (no delivery, save only), 'all' (fan out to every connected home channel), or platform:chat_id:thread_id for a specific destination. Combine with comma: 'origin,all' delivers to the origin plus every other connected channel. Examples: 'telegram:-1001234567890:17585', 'discord:#engineering', 'sms:+15551234567', 'all'. WARNING: 'platform:chat_id' without :thread_id loses topic targeting. 'all' resolves at fire time, so a job created before a channel was wired up will pick it up automatically once connected."
},
"skills": {
"type": "array",

View file

@ -240,9 +240,20 @@ When scheduling jobs, you specify where the output goes:
| `"weixin"` | Weixin (WeChat) | |
| `"bluebubbles"` | BlueBubbles (iMessage) | |
| `"qqbot"` | QQ Bot (Tencent QQ) | |
| `"all"` | Fan out to every connected home channel | Resolved at fire time |
| `"telegram,discord"` | Fan out to a specific set of channels | Comma-separated list |
| `"origin,all"` | Deliver to the origin **plus** every other connected channel | Combine any tokens |
The agent's final response is automatically delivered. You do not need to call `send_message` in the cron prompt.
### Routing intent (`all`)
`all` lets you ship one cron job to every messaging channel you have configured, without having to enumerate them by name. It is **resolved at fire time**, so a job created before you wired up Telegram will pick up Telegram on the next tick after you set `TELEGRAM_HOME_CHANNEL`.
Semantics: `all` expands to every platform with a configured home channel. Zero is fine; the job simply produces no delivery targets and is recorded as a delivery failure upstream.
`all` composes with explicit targets. `origin,all` delivers to the origin chat *plus* every other connected home channel, de-duplicating by `(platform, chat_id, thread_id)`.
### Response wrapping
By default, delivered cron output is wrapped with a header and footer so the recipient knows it came from a scheduled task: