Proactive push: internal scheduler + reusable Telegram delivery pipeline #5
Labels
No labels
epic
in-review
ready-for-agent
ready-for-human
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
weiwen/evie#5
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Let evie push messages to Telegram unprompted (e.g. a morning note summary), on an internal schedule, breaking the current pull-only request/response model.
Design decisions (from grilling)
editMessageText, pagination, and source-note buttons. There is no separate push path; the scheduler is just another producer of(chat_id, prompt)into the shared pipeline.pi.system_prompt_file: a[[schedule]]entry points at aprompt_filepath, not an inline string. (Inline-string support is a separate follow-up — #6 — that depends on this issue.)telegram.allowed_chat_ids; use the first if more than one).TZis the user's to control.--no-session). It begins fresh (any prior idle session for that chat is replaced), so it is not contaminated by earlier conversation. Because it is a normal persisted session under the usual idle timeout, if the user replies within the timeout window the reply continues the conversation with full digest context; otherwise it expires like any session.Part 1 — Refactor delivery into a reusable pipeline (prerequisite)
Today
handle_message(src/telegram/mod.rs) inlines everything: placeholder send, typing task, snapshot channel,MessageView,markdown::paginate, throttled edits, final render. Extract a delivery unit that takes(chat_id, prompt)and produces the streamed/rendered messages viaMessageView, so it can be driven by:Both callers get identical behaviour — streaming, pagination, and the source-note buttons (#4) — because it is literally the same code. Since the digest session is keyed to the same
tg-<chat>namespace, continuation on reply is free via the existing session routing.Part 2 — Internal scheduler
[[schedule]]array inconfig.toml, each entry carrying a cron expression and aprompt_filepath. Add aScheduleConfigstruct (#[serde(default)]on the vec so it is optional),expand_tildethe path, extenddefault_toml()with a commented-out example, validate at startup.croner) evaluates each entry in local time and fires it; on fire, starts a fresh session for the global chat and sends the resolved prompt into the shared delivery pipeline over an mpsc channel.teloxide::Botand runs the shared delivery pipeline — this is how the scheduler reaches Telegram without owning theBot.Config
Constraints
prompt_filethat does not exist, is a config error at startup (extendConfig::validate).telegram.enabledand a non-emptytelegram.allowed_chat_ids; reject at startup otherwise.Acceptance
[[schedule]]fires on cron (local time) and streams a rendered answer into the global Telegram chat with no user message, identical to an interactive reply.Implemented in branch sandcastle/issue-5 (commit a9c199e).
Part 1 — Refactor delivery: Extracted
deliver_response()fromhandle_message()into a public reusable function that streams pi answers with live pagination, typing indicator, and final render. Both interactive handler and scheduler call the same function.Part 2 — Internal scheduler: Added
[[schedule]]config section with cron + prompt_file.scheduler_task()evaluates cron in local time, sleeps to the next fire, and sends (chat_id, prompt) over an mpsc channel.delivery_consumer()holds a Bot clone, clears the session (fresh start), and runs the shared pipeline. Missed schedules are naturally skipped on restart.Tests: 5 new tests for config parsing and validation. All 31 tests pass.
Blockers for next iteration: none known.