Skip to content

Job actions: react, read and view, on one paced, persistent op-layer execution model - #45

Merged
erfnzdeh merged 15 commits into
mainfrom
feat/job-actions
Oct 3, 2026
Merged

erfnzdeh merged 15 commits into
mainfrom
feat/job-actions

Conversation

@erfnzdeh

@erfnzdeh erfnzdeh commented Oct 3, 2026 •

Copy link
Copy Markdown
Collaborator

What

Three new gateway job actions, react, read and view, and one execution model for every action, forward and reply included:

  • Through the op layer. Each action runs as an operation (message.forward, message.send, media.upload, reaction.add, message.read, message.view.get, chat.typing, profile.presence.set) via dispatch.execute, in process, for the job's account. Jobs now get policy allow/deny, the rate limiter, flood budgets, self-origin events and the per-peer edge cases the ops already handle.
  • Never on the bus lane. A job plans (filters, percent roll, processors, emoji pick) and queues PendingItems on the account's ActionScheduler; the handler returns at once.
  • Knobs on every action or as job defaults: delay, percent, presence (leave, blip, session, quiet_hours), on_takeover (cancel, cancel_read, ignore), dry_run. Defaults keep existing jobs doing what they did, apart from pacing.
  • Pacer per (account, action kind) with cautious defaults (react 1/4 s and 300/h, read and view 1/2 s, forward and reply 1/1.5 s), configurable in a top-level pacing: block. FLOOD_WAIT reschedules and slows the queue, transient errors retry, permanent ones are counted.
  • Persistence in accounts/<alias>/pending.json (0600, atomic, bounded at 10 000): pending actions survive restarts and crashes and resume at their original due time. React, view and reply expire after 24 h (configurable). Recently finished keys stop a replayed update from being acted on twice.
  • react (single, random or weighted, custom:<id>, replaces the previous reaction, reads first, one per album), read (up to the message, right RPC per peer, coalesced per chat, reading time, mentions/reactions), view (channel view increment, voice and round notes listened in DMs and groups, view-once untouched unless enabled), reply typing on by default.
  • Manual takeover: reading or writing in the chat from another device drops pending actions by on_takeover; tlgr's own reads and sends are told apart by the ids it recorded.
  • Filters sender_is_contact and chat_is_new.
  • CLI: tlgr job queue (list) and tlgr job queue cancel ID | --chat | --job | --all (destructive, --yes off a terminal); per-action counters (done, skipped, superseded, expired, pending, errors) in job list, job get and daemon status; strict validation in job add, job reload --validate-only and config validate.

Why

The main use case is DMs: read, listen, react and answer the way a person does, without blocking the bus and without hammering the account. Moving forward and reply onto the same path removes the last places where a job called Telethon directly, outside policy and rate limits.

Decisions

Recorded in docs/design/JOB_ACTIONS.md. In short:

  • Jitter only lengthens the gap: spacing is drawn from [every, 1.5 x every], so every is a floor.
  • FLOOD_WAIT up to 10 s is slept in the request; longer ones reschedule, double the queue's spacing (cap 8x, recovers after 10 quiet minutes), give up after 10 floods. Transient errors retry after about 5 s, 30 s, 2 min; forward and read (never expire) keep retrying every 10 min for about 1.5 h. Everything else, including PEER_FLOOD, is an error and not retried.
  • Persistence is a JSON file per account (like flood.json), debounced writes plus a final write at shutdown, read before any job can submit.
  • Album reactions (and replies) go to the caption message, else the first, matching Telegram Desktop GroupedMedia::itemForText and Android findPrimaryMessageObject; an album is one percent roll.
  • Reading time: 250 words per minute, plus 3 s for media, capped at 60 s.
  • Presence: the most online request wins while its actions run; leave never sends anything; account-level [presence] mode other than off disables job presence. Quiet hours use [defaults] timezone, else local time, and release spread over 5 minutes.
  • Validation is per job: a broken job is skipped at load while others run, and a job whose edit broke it keeps running on job reload.
  • tlgr job queue is job.queue.list tagged group-default (the registry forbids an id that is also a group); job.get moved to the daemon surface to show live counters; job disable drops what the job had queued.

Behaviour changes for existing jobs

  • Forwards on one account are spaced at least 1.5 s apart.
  • With processors, a link-preview post is re-sent as text instead of failing.
  • An album gets one reply instead of one per photo.

Tests

New: test_gateway_knobs.py, test_gateway_pacer.py, test_gateway_scheduler.py, test_gateway_config.py, test_filters_dialog.py, test_job_queue.py; ported test_gateway.py, test_gateway_bus.py, test_actions.py to the new path; test_daemon_jobs.py covers save at shutdown and resume at boot. Real TL types throughout, a virtual clock for pacing (no real sleeps), and end-to-end runs through the daemon's dispatcher against the fake client (forward, processed re-send of text and media, reply with typing, react after read, per-peer read RPCs including forum topics, channel views).

make lint typecheck test docs parity passes locally (13 546 tests), and the relevant suites pass on Python 3.10 as well.

…er-account scheduler

Every action, forward and reply included, now runs as an operation through
dispatch.execute, so jobs get policy, rate limits, flood budgets and
self-origin events. Adds react, read and view; delays never block a bus
lane, each action kind has its own pacer, and reads and views coalesce.
…and validation in add and reload

job queue list shows every pending action with its due time and state;
job queue cancel drops by id, --chat, --job or --all and is destructive, so
it needs --yes off a terminal. A bare 'tlgr job queue' lists. job add and
job reload --validate-only now run the engine's own parser.
…over to forum topics, and tighten retries, batching and saving

From review: a disconnect cancelled the in-flight request and ended that
kind's worker for good; a read in one forum topic dropped items in others;
the read the server makes when tlgr sends looked like a takeover. Also:
floods have their own counter, a running item of a removed job is not
requeued, batches skip expired and quiet-held siblings, the debounced save
runs in a thread, waits re-check the wall clock every minute, and presence
recovers if an action starts while going offline.
@erfnzdeh
erfnzdeh merged commit 647f171 into main Oct 3, 2026
13 checks passed
@erfnzdeh
erfnzdeh deleted the feat/job-actions branch October 3, 2026 22:26
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