Skip to content

Scheduling

Why Schedules?

AI agents should be proactive, not just reactive. Daily reports, hourly health checks, background research — these need time-based triggers. You could run external cron jobs, but then timing logic lives outside your agent, doesn't survive failures gracefully, and knows nothing about tenants.

Xians schedules are cron jobs built into your workflows, powered by Temporal Schedules:

  • Durable — survive restarts and failures
  • Multi-tenant aware — automatic isolation per tenant, zero configuration
  • Workflow-native — safe to create from inside workflows (the SDK keeps it deterministic)
  • Production-ready — built-in overlap policies, retries, and timeouts

The key insight: a workflow that schedules itself is autonomous. It controls its own timing and operates continuously without external coordination.

Quick Start: A Self-Scheduling Workflow

C#
[Workflow("MyAgent:Daily Report Workflow")]
public class DailyReportWorkflow
{
    [WorkflowRun]
    public async Task RunAsync(string reportType)
    {
        // Do the work
        await GenerateReport(reportType);

        // Ensure the recurring schedule exists (idempotent — safe every run)
        await XiansContext.CurrentAgent.Schedules
            .Create<DailyReportWorkflow>("daily-report")
            .Daily(hour: 9, timezone: "America/New_York")
            .WithInput(reportType)
            .SkipIfRunning()
            .CreateIfNotExistsAsync();
    }
}

The workflow runs, does its work, and guarantees its own next run. That's the whole pattern.

Defining When to Run

Style Methods Example
Time-based .Daily(), .Hourly(), .Weekdays(), .Weekly(), .Monthly() .Daily(hour: 9, timezone: "America/New_York")
Interval .EverySeconds(), .EveryMinutes(), .EveryHours(), .EveryDays() / .WithIntervalSchedule(...) .EveryMinutes(15)
Cron .WithCronSchedule(expr, timezone?) .WithCronSchedule("0 9 * * 1-5", timezone: "America/New_York")
One-time .WithCalendarSchedule(dateTime, timezone?) .WithCalendarSchedule(new DateTime(2026, 12, 25, 9, 0, 0))
C#
.Daily(hour: 9, timezone: "America/New_York")       // every day, timezone-aware
.Weekdays(hour: 8, minute: 30)                       // Mon–Fri
.Weekly(DayOfWeek.Monday, hour: 10)
.Monthly(dayOfMonth: 1, hour: 8)
.EveryHours(2)                                       // duration-based, no timezone
.WithCronSchedule("0 */2 * * *")                     // full cron power

Multi-day intervals

.EveryDays(n) with n > 1 ignores hour/minute parameters. .EveryDays(1, hour: 9) is equivalent to .Daily(hour: 9).

Overlap Policies: What If the Previous Run Is Still Going?

Schedules can fire faster than workflows finish. Decide up front what happens:

Policy Behavior When to use
.SkipIfRunning() Skip the new run Default choice — prevents pile-up
.BufferOne() Queue one run for after the current Work must not be skipped, but shouldn't overlap
.AllowOverlap() Run concurrently Runs are independent
.CancelOther() Cancel the running one, start fresh New data supersedes old run
.TerminateOther() Force-kill the running one Last resort

Creation Methods: Idempotent by Default

Self-scheduling workflows call the creation code on every run, so it must be safe to repeat:

C#
.CreateIfNotExistsAsync()  // Returns existing or creates — idempotent (recommended)
.CreateAsync()             // Throws ScheduleAlreadyExistsException if it exists — strict

Use CreateIfNotExistsAsync() unless you specifically need strict failure (CreateAsync).

Managing Schedules

C#
var schedules = XiansContext.CurrentAgent.Schedules;

var schedule = await schedules.GetAsync("my-schedule");

await schedule.PauseAsync("System maintenance");
await schedule.UnpauseAsync("Maintenance complete");
await schedule.TriggerAsync();          // run now, without affecting the schedule
var info = await schedule.DescribeAsync(); // next run times, recent actions
await schedule.DeleteAsync();

// Backfill: run actions for a past period
await schedule.BackfillAsync(new[]
{
    new ScheduleBackfill(startAt: DateTime.UtcNow.AddDays(-7), endAt: DateTime.UtcNow.AddDays(-1))
});

All of these also exist as by-name overloads on the collection (e.g. schedules.PauseAsync("my-schedule")). For advanced Temporal features, schedule.GetHandle() returns the native Temporal handle.

Multi-Tenant Isolation

Schedule IDs are automatically namespaced as {tenantId}:{agentName}:{idPostfix}:{scheduleName}, so:

  • Tenants can't see or trigger each other's schedules.
  • The same agent code deployed to many tenants creates independent schedules per tenant.
  • No manual filtering or prefixing required.

Common Patterns

Per-entity schedules

C#
[Workflow("MyAgent:Research Setup")]
public class ResearchSetupWorkflow
{
    [WorkflowRun]
    public async Task RunAsync(string[] companies)
    {
        foreach (var company in companies)
        {
            await XiansContext.CurrentAgent.Schedules
                .Create<ResearchWorkflow>($"research-{company.ToLower()}")
                .Weekdays(hour: 8, timezone: "America/New_York")
                .WithInput(company)
                .SkipIfRunning()
                .CreateIfNotExistsAsync();
        }
    }
}

Create paused, start later

C#
var schedule = await XiansContext.CurrentAgent.Schedules
    .Create<MaintenanceWorkflow>("maintenance-task")
    .Daily(hour: 2)
    .StartPaused(true, "Created for future use")
    .CreateIfNotExistsAsync();

// When ready:
await schedule.UnpauseAsync("Ready to start maintenance");

Error Handling

Exception Thrown when
ScheduleAlreadyExistsException CreateAsync() on an existing schedule
ScheduleNotFoundException GetAsync() for a missing schedule
InvalidScheduleSpecException Invalid schedule configuration

Best Practices

  • CreateIfNotExistsAsync() + .SkipIfRunning() is the right default for nearly everything.
  • Always specify timezones for time-based schedules — interval schedules don't need them.
  • Use descriptive IDsdaily-sync-{company}, not schedule1.
  • Catch the specific exceptions — they tell you exactly what went wrong.
  • [Workflow("AgentName:WorkflowName")] — custom workflows need the agent-prefixed type name.

What's Next?

  • Workflows — the workflows your schedules trigger
  • Agents — the self-scheduling pattern in context