Skip to main content
Async-native agent scheduler that replaces daemon threads with proper async execution and cooperative cancellation.
The user schedules recurring agent work; the async scheduler runs jobs with cooperative cancellation.
Pass deliver="telegram:123456" to route each successful run to a chat channel. Delivery dispatches through asyncio.to_thread, so it never blocks the loop. See Scheduler → Deliver Results.

Quick Start

1

Simple Usage

Create and start an async scheduler with basic configuration.
2

With Callbacks and Configuration

Add success/failure callbacks and custom configuration.
3

With Timeout & Budget Limit

Control execution time and budget with built-in enforcement.

How It Works

Multi-process deployments: When more than one BotOS process shares the same schedule store, each due job is claimed atomically before running and fires at most once across all processes — provided the store supports claim_due (the default FileScheduleStore does). See BotOS → Multi-Process / HA Deployments.

Multi-process safety (at-most-once)

Without an atomic claim, two workers polling the same store could each fire the same due job — causing double-posts, double-charges, or duplicated side effects. Since PraisonAI PR #2628, FileScheduleStore claims each due job under an OS advisory file lock (fcntl on POSIX, msvcrt on Windows) and hands it back to only one worker per tick. Losers receive an empty list and skip silently. Under the lock the store re-reads on-disk state, pre-advances last_run_at, and takes a short lease atomically. One-shot jobs are removed on claim. An unfinished claim releases automatically once its lease expires — this is the crash-safety mechanism.
The wrapper’s ScheduledAgentExecutor.tick() does this automatically: it claims under the lock when the store supports it (default owner_id = f"{hostname}:{pid}:{uuid}"), releases the lease after each run, and falls back to get_due_jobs otherwise.
fcntl / msvcrt locks are honored per host. Sharing one FileScheduleStore path across hosts only works on a filesystem that honors the lock — network filesystems like NFS do not reliably do so. Use a shared database-backed store for cross-host at-most-once instead.

Configuration Options

AsyncAgentScheduler API Reference

Complete parameter documentation and examples

Constructor Parameters

Start Parameters

Stats Response Format


Common Patterns

Pattern 1: Event Loop Safety

The scheduler is safe to construct outside an event loop:

Pattern 2: FastAPI Integration

Pattern 3: Cancellation Handling

Pattern 4: Budget-aware Scheduling

Real cost tracking (PR #2171): Before #2171, both schedulers added a fixed $0.0001 to total_cost per run, so the default max_cost=1.00 only tripped after ~10,000 runs regardless of model. The scheduler now pulls usage (input/output tokens) and model off the agent response and prices it through praisonai.cli.features.cost_tracker.ModelPricing. Responses with no usage metadata contribute $0 — the brake errs on the side of running rather than tripping on missing data. Negative token counts are clamped to 0 so they can never bypass the brake.

Best Practices

Always create async primitives lazily. The scheduler binds asyncio.Event and asyncio.Lock to the caller’s loop on first async entry, preventing “different loop” errors.
Use both sync and async callbacks safely. The safe_call utility handles both types automatically.
Use exponential backoff with jitter. Both sync and async schedulers share the same backoff_delay algorithm for consistency.
Always await stop() for proper cleanup and statistics reporting.
The default max_cost=1.00 caps unattended cost runaway. Since PR #2171, total_cost_usd is the real per-token spend computed from each response’s usage field — pick a value that matches your approved budget, not a multiple chosen to compensate for an undercount.
When the budget triggers, the scheduler logs a warning and calls stop_event.set() internally — stats["is_running"] flips to False.If your model is missing from DEFAULT_PRICING, the run is priced at $0 and total_cost_usd will under-report — register it via the custom pricing snippet so the brake stays meaningful.
Set timeout to bound the worst-case wall-clock time per run. Internally implemented with asyncio.wait_for(), which raises asyncio.TimeoutError and triggers the standard retry path.
timeout=None (the default) imposes no limit.
Pick a lease_seconds long enough to cover your slowest job, but short enough to recover quickly after a crash — an unfinished claim only re-runs after its lease expires.
Don’t share a FileScheduleStore path across hosts unless the filesystem honors the lock (NFS does not). Leave owner_id on its auto default (f"{hostname}:{pid}:{uuid}") unless you need a stable value for observability.


Import paths (PR #1552):
  • Pending deprecation (still works, emits PendingDeprecationWarning — will move to praisonai.scheduler.async_agent_scheduler in a future release): from praisonai.scheduler import AsyncAgentScheduler
  • Canonical: from praisonai.scheduler import AgentScheduler (sync scheduler)
  • Deprecated (still works, emits DeprecationWarning): from praisonai.agent_scheduler import AgentScheduler
The canonical sync AgentScheduler exposes from_yaml, start_from_yaml_config, and from_recipe, which the deprecated top-level shim does not advertise but now re-exports correctly.Shared dispatch helper (PR #2147): AsyncPraisonAgentExecutor.execute() now delegates to the shared adispatch_agent helper (praisonai.scheduler._dispatch). No behaviour change — the dispatch ladder (astartto_thread(start)AttributeError) is unchanged. See also the sync scheduler note for the corresponding fix to AgentScheduler.

Skipping Ticks with a Pre-Run Gate

A pre-run gate runs a cheap shell command before each tick and skips the model turn when the check says there’s nothing to do — zero tokens spent on quiet ticks. See Scheduler Pre-Run Gate for configuration options and examples.

Sync Agent Scheduler

Thread-based scheduler for sync environments

Pre-Run Gate

Skip ticks when a cheap check says nothing to do

Shared Scheduler Utilities

Common primitives used by both schedulers