Skip to main content

Scheduler Module

The Scheduler module provides deployment scheduling capabilities with a provider-agnostic design.
Since v4.6.122, the execution primitives (ScheduledAgentExecutor, ShellConditionGate, JobResult) live in praisonai-bot. Import them from praisonai_bot.scheduler.* in new code. Safety primitives (RunPolicy, PromptScanResult) stay in the wrapper at praisonai.scheduler.*. The old executor/gate paths remain importable as backward-compatibility shims.

Import

from praisonai.scheduler.executor import ScheduledAgentExecutor and from praisonai.scheduler.condition_gate import ShellConditionGate still work as backward-compatibility shims when the praisonai wrapper is installed.

Quick Example

Classes

DeploymentScheduler

Minimal deployment scheduler with provider-agnostic design. Features:
  • Simple interval-based scheduling
  • Thread-safe operation
  • Extensible deployer factory pattern

ScheduleParser

Parses schedule expressions into scheduling parameters. ScheduleParser is also re-exported from praisonai.scheduler.shared and used internally by AgentScheduler / AsyncAgentScheduler.

DeployerInterface

Abstract interface for deployers to ensure provider compatibility.

Methods

DeploymentScheduler.schedule(interval_minutes)

Schedule deployments at a fixed interval. Parameters:
  • interval_minutes (int): Minutes between deployments

DeploymentScheduler.start()

Start the scheduler in a background thread.

DeploymentScheduler.stop()

Stop the scheduler.

DeploymentScheduler.is_running()

Check if the scheduler is currently running. Returns: bool

Example: Custom Deployer

Timezones

A naive at: timestamp (no Z, no +HH:MM offset) and every cron: expression resolve their timezone in this order: resolve_schedule_timezone in praisonaiagents.scheduler.due implements the env-var and UTC fallback; is_due(job, now, default_timezone=...) threads the instance default through.
An at: string that already carries an offset (at:2026-03-01T09:00:00+05:30, at:2026-03-01T09:00:00Z) is unaffected — the offset in the string always wins. See Schedule Tools → Timezones.

ScheduleJob — Pre-Run Gate Fields

Since PR #2238, ScheduleJob (in praisonaiagents.scheduler.models) has two optional fields for the pre-run condition gate:

Timezones

parse_schedule (in praisonaiagents.scheduler.parser) resolves the timezone for a naive at: timestamp and the clock forms (at 9am, at 17:00) in order:
  1. The tz= argument passed to parse_schedule / schedule_add (per-schedule).
  2. The instance default — the scheduler.timezone (or scheduler.tz) key in config.yaml, which ConfigYamlScheduleStore reads into its _default_timezone and passes to is_due().
  3. The PRAISONAI_SCHEDULE_TIMEZONE environment variable.
  4. The machine’s local zone, with the UTC offset in force on the target date (DST-correct).
An at: string that already carries an offset (+05:30, Z, +00:00) is used verbatim. cron: follows the same 1–3 precedence but falls back to UTC at step 4.

Parse-time stamping vs the is_due() fallback

As of PraisonAI #4732, the zone is attached at parse time by praisonaiagents.scheduler.due.localize_wall_clock, so parse_schedule returns a Schedule whose at is already an aware ISO string:
is_due() keeps a legacy branch for naive strings that did not come through the parser — jobs written before this PR, hand-edited config.yaml entries, or a Schedule(kind="at", at=<naive iso>) built directly. That branch calls the same localize_wall_clock helper, so both paths agree. An unknown IANA zone raises ValueError from either path instead of firing never.

The instance default

The store’s instance default flows through is_due(job, now, default_timezone=...) and applies to naive at: values that omit a per-schedule tz. Set it in config.yaml:
is_due() accepts the same value directly when you evaluate a job yourself:

ScheduleStoreProtocol — per-principal scoping

Each store method accepts an optional principal — the resolved end-user identity used to isolate one gateway user’s automations from another’s. None returns everything (global / single-tenant behaviour), so every call is backward-compatible.
ScheduleJob.principal (Optional[str] = None) round-trips through to_dict/from_dict and is omitted when None, so existing on-disk store files stay byte-identical. The same scoping was extended to SuggestionStorelist_pending, accept, and dismiss take a principal and refuse cross-owner mutations, with the pending cap and dedup window measured per owner. See Automation Suggestions → Multi-tenant isolation for the suggestion-store surface.

ScheduledAgentExecutor

ScheduledAgentExecutor lives in the bot tier (praisonai-bot). The canonical import is:
from praisonai.scheduler.executor import ScheduledAgentExecutor works as a backward-compatible shim when the praisonai wrapper is installed alongside praisonai-bot.

Constructor reference

Public methods

Atomic claims: When the backing store supports claim_due, each due job is reserved under a cross-process lock so it fires at most once across all tickers/processes/hosts. Stores without that support fall back to the non-atomic get_due_jobs path.

Example

condition_resolver values

JobConditionProtocol and GateResult

JobConditionProtocol and GateResult are exported from praisonaiagents.scheduler:
  • GateResult — dataclass with run: bool, context: Optional[str], reason: Optional[str]
  • JobConditionProtocol — protocol for custom gate implementations
  • SchedulerProviderProtocol — trigger backend protocol deciding when jobs fire; ScheduleLoop (aliased InProcessScheduleProvider) is the default. See Scheduler Providers.

RunPolicy

Run-scoped guardrail for unattended scheduled agent runs. Scopes the toolset, scans the assembled prompt for injection patterns, persists a durable output audit, and supports fail-closed delivery on failure.
See Scheduled Run Policy for the full reference.

PromptScanResult

Return type from RunPolicy.scan_prompt() and custom scanner callables.