Skip to content

⏰ feat: Scheduled Chats - Agent-Centric Recurring Runs - #14373

Closed
danny-avila wants to merge 125 commits into
devfrom
claude/librechat-scheduled-chats-e821b0
Closed

danny-avila wants to merge 125 commits into
devfrom
claude/librechat-scheduled-chats-e821b0

Conversation

@danny-avila

Copy link
Copy Markdown
Collaborator

Summary

I added Scheduled Chats: users bind a prompt to one of their agents and a recurring cadence, and the server fires it on time as the owning user, producing a real conversation that appears in the sidebar like any other chat. The scheduler is Mongo-arbitrated end to end, so it is correct on a single instance without Redis and on N replicas with it, with one new runtime dependency (croner, zero transitive deps) used solely for timezone-aware next-occurrence math.

  • Added Schedule and ScheduleRun schemas (packages/data-schemas): structured cadence object (hourly/daily/weekdays/weekly + hour/minute/daysOfWeek) with required IANA timezone, materialized nextRunAt, lease fields, typed disabledReason, and a run collection with a unique {scheduleId, scheduledFor} index plus a 90-day TTL.
  • Implemented dispatch as a two-phase lease claim: an atomic findOneAndUpdate CAS takes a 5-minute lease (exactly one replica wins per occurrence), and the run-doc insert against the unique index is the durable idempotency record, so crash-retry never double-fires. The deterministic clientRequestId (sched:{id}:{scheduledFor}) additionally collapses retries in the existing claimGeneration layer.
  • Built the tick engine (packages/api/src/schedules/): 30s self-rescheduling tick with jittered sleep, per-tick claim cap, deterministic per-schedule fire jitter (herd shaping), misfire skip-forward, and job-store-aware reconciliation that surfaces HITL pauses as requires_action (which deliberately does not block the next fire), marks crashed runs interrupted, and resolves resumed runs.
  • Fired runs through a loopback POST to /api/agents/chat/agents with a scope-claimed short-lived JWT, so the full middleware chain (moderation, PII filter, agent ACL, convo ownership, idempotency) applies unchanged; persistence, title generation, Meili indexing, and client resume all ride the existing ResumableAgentController path with no client connected.
  • Enforced spend guardrails before every fire: balance pre-check (skips record skipped_balance and auto-disable with insufficient_balance after 5 consecutive), overlap skip while a prior run is still started, agent-deletion auto-disable, and consecutive-failure auto-disable, every disable carrying a typed reason the client localizes.
  • Exempted scheduled fires from LIMIT_MESSAGE_IP/LIMIT_MESSAGE_USER via a signature-verified scope claim, so the scheduler's own caps (hourly floor, maxPerUser 10, admin-overridable under interface.schedules) are the single throttle and legitimate fires cannot be rate-limited into auto-disable.
  • Added the SCHEDULES permission type (USE/CREATE), interface flag seeding, and /api/schedules CRUD plus POST /:id/run (run-now), with server-side validation of timezone, interval floor, agent access, and file ownership.
  • Supported attachments end to end on the backend: schedules store file_ids, fires re-resolve the file docs at run time and proceed without deleted files (recording droppedFileIds on the run) instead of failing the run. The dialog uploader affordance is a fast-follow; the API and fire path are complete.
  • Built the client: a Scheduled chats sidebar panel (cards with agent, localized cadence sentence, relative next run, last-run status chip linking to the run's conversation, enable/disable switch, run-now/edit/delete menu, quota footer) and a create/edit dialog (agent combobox, frequency presets with hour/minute/AM-PM selects, cadence summary line), gated by permission and interface flag, with 41 new localization keys.
  • Wrote 39 tests: DST fixtures through the real preset→cron→croner path (spring-forward gap fires shifted one hour, no run lost; fall-back fires once), 8-way concurrent claim contention with exactly one winner, lease expiry re-claim, run idempotency via the unique index, and the full outcome/auto-disable bookkeeping matrix.

Recorded limitations for follow-ups: the dialog attachment uploader, run-history UI (the collection already captures it), a per-schedule tool allowlist (schema field reserved), and resumed-run success attribution beyond the job store's completed-job TTL window.

Change Type

  • New feature (non-breaking change which adds functionality)

Testing

  • cd packages/api && npx jest cadence — 23 tests: preset→cron mapping, DST spring-forward/fall-back behavior in America/New_York asserted via Intl wall-clock formatting, weekday skips, jitter determinism/bounds, timezone validation.
  • cd packages/data-schemas && npx jest schedule.methods — 16 tests on mongodb-memory-server with real indexes: concurrent claim contention, lease expiry, claim ordering, E11000 idempotency, success/error/threshold auto-disable, balance-skip threshold, transitionRunStatus CAS, hasActiveRun lifecycle.
  • npm run build (Turborepo) green across all workspaces; tsc --noEmit clean in packages/api, packages/data-schemas, and client; eslint clean on all touched files.
  • Manual verification path: create a schedule for 2 minutes out (temporarily lower the interval floor via interface.schedules.minIntervalMinutes), watch the engine claim and fire it, confirm the conversation appears with a generated title, confirm run-now works, kill the server mid-generation and confirm the run reconciles to interrupted with no duplicate on restart.

Test Configuration:

  • Node v24, local MongoDB (no Redis required; Redis-backed job store exercised implicitly when USE_REDIS is enabled).

Checklist

  • My code adheres to this project's style guidelines
  • I have performed a self-review of my own code
  • I have commented in any complex areas of my code
  • My changes do not introduce new warnings
  • I have written tests demonstrating that my changes are effective or that my feature works
  • Local unit tests pass with my changes

Loading
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.

2 participants