Skip to content

feat(cron/slack): flat in-channel continuable cron delivery surface - #176

Open
hashbender wants to merge 1 commit into
mainfrom
mirror/pr-56096
Open

feat(cron/slack): flat in-channel continuable cron delivery surface#176
hashbender wants to merge 1 commit into
mainfrom
mirror/pr-56096

Conversation

@hashbender

Copy link
Copy Markdown
Owner

Summary

Adds a per-platform cron_continuable_surface config key so a continuable cron job can deliver flat into a Slack channel — no dedicated thread — and still be replied-to and continued in context.

Today continuable cron (cron.mirror_delivery / per-job attach_to_session) is thread-preferred: on any thread-capable platform it unconditionally mints a hidden handoff thread (cron/scheduler.py). There was no "continuable but flat" mode. This adds one.

How it works

cron_continuable_surface takes "thread" (default, byte-identical to today) or "in_channel". In in_channel mode the scheduler skips the thread-open branch (leaves thread_id=None), so the delivery posts flat — and the shipped origin-mirror then seeds the (slack, chat_id, None) shared-channel session, the exact bucket reply_in_thread: false already routes inbound channel replies to. A plain channel reply resolves to that session with the brief in context. No new seed code — it reuses the existing mirror.

This is model B (shared-channel session), deliberately not anchoring the seed to the delivery message's ts: on Slack, replying to a specific message is threading, so a ts anchor would only relocate the thread to first-reply-time, never deliver true threadless-continuable.

Config (the pairing)

# ~/.hermes/config.yaml
slack:
  cron_continuable_surface: in_channel   # default: thread
  reply_in_thread: false                 # required: answer + key the reply flat
  require_mention: false                 # so a plain reply continues the job
  • reply_in_thread: false is required so the reply is answered flat and keyed to the seeded channel session. Missing it fails SAFE — you get a threaded continuation (≈ today), never a dropped reply — and the gateway logs a warning at startup (warn, not hard-reject: the two knobs stay orthogonal).
  • require_mention: false (or a free-response channel) lets a plain reply continue the job; otherwise you'd have to @-mention on each reply. (Long-standing orthogonal channel knob — surfaced in docs.)
  • Because the continuation is the whole-channel session, it's shared (other chatter joins the rolling conversation) — the same tradeoff reply_in_thread: false users already accept. Use the default thread surface when you want each delivery isolated.

Generic key, Slack first consumer

The scheduler reads the key generically from platform config; the in_channel branch is gated on a base-adapter capability flag supports_inchannel_continuable (Slack = True). Any platform without an implementation fails safe to thread with a debug log — no Slack-only special-case in core.

Files

  • gateway/platforms/base.pysupports_inchannel_continuable capability flag (default False).
  • plugins/platforms/slack/adapter.py — flag True; _cron_continuable_surface() resolver; _warn_if_inchannel_without_flat_reply() connect-time warning.
  • gateway/config.py — shared-key bridge line (top-level or nested config).
  • cron/scheduler.py — resolve the surface generically, gate the in_channel branch on the capability flag, skip thread-open.
  • Docs: cron.md + slack.md (+ zh-Hans mirrors of both).

Testing

Run from inside the worktree (PYTHONPATH=$PWD):

  • +6 cron scheduler testsin_channel skips thread-open; seeds the flat channel session with thread_id=None; thread-mode regression; fail-safe on an unsupported platform; value coercion.
  • Prove-fail — removing the and not in_channel_surface guard turns the two load-bearing tests RED; restore → GREEN.
  • +10 slack resolver/capability/warning tests; +2 config-bridge tests.
  • Offline E2E (tests/manual/cron_inchannel_e2e.py) — drives both real legs (delivery seed + inbound reply keying) and asserts they converge on (slack, C, None).
  • No regressions: test_slack.py 216 passed alone; broader sweep green. (4 pre-existing cross-file-ordering failures in test_slack.py reproduce identically on pristine origin/main.)

Deploy note

Gateway-side config flag — a /restart picks it up. No Slack app reinstall needed (contrast a manifest/scope change).

Infographic

in-channel-cron


Mirror-of: NousResearch#56096
NousResearch#56096

@tenki-reviewer

tenki-reviewer Bot commented Jul 1, 2026

Copy link
Copy Markdown

Review Complete
No issues found!

Risk: 🟢 Low (18/100) — no findings · 738 LOC across 12 files


No issues found meeting confidence and severity thresholds. PR adds cron in-channel delivery for Slack with proper config gating and fail-safe fallbacks.

Files Reviewed (12 files)
cron/scheduler.py
gateway/config.py
gateway/platforms/base.py
plugins/platforms/slack/adapter.py
tests/cron/test_scheduler.py
tests/gateway/test_slack_cron_continuable_surface.py
tests/gateway/test_slack_mention.py
tests/manual/cron_inchannel_e2e.py
website/docs/user-guide/features/cron.md
website/docs/user-guide/messaging/slack.md
website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/features/cron.md
website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/messaging/slack.md

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant