Skip to main content

Scheduling Policy

The scheduling policy controls when workflows can run. Use it to limit agents to business hours, block weekend runs, freeze execution on holidays, or carve out recurring maintenance windows where everything pauses. All checks are timezone-aware via IANA tz names.

For a more expressive window-based variant with multiple per-day windows and compliance-framework mapping, see time-of-day-gating.

Rules

RuleTypeDefaultDescription
allowed_hoursobject(none){"start": <0-23>, "end": <1-24>} -- workflow must run in [start, end)
allowed_daysinteger[](none)Days of week the workflow may run. 0=Monday, 6=Sunday
timezonestring"UTC"IANA timezone name (e.g. America/New_York)
blackout_datesstring[][]Specific YYYY-MM-DD dates the workflow cannot run
maintenance_windowsobject[][]Recurring weekly windows -- [{"day": 6, "start": 2, "end": 6}]

How It Works

The scheduling handler runs at before_workflow. It is cheap (a wall-clock check) and has selectivity_hint = 0.05.

PhaseWhat It ChecksActions
before_workflowCurrent time in the configured timezone against allowed_days, allowed_hours, blackout_dates, and maintenance_windowsBLOCK on any violation
after_workflowNo-op (ALLOW)ALLOW

Context Attributes Read

AttributePhasePurpose
(none)before_workflowThe handler reads only wall-clock time -- no context state

The handler is purely a function of the current time. It does not inspect inputs, workflow type, or any agent state.

Example Policy

{
"name": "Business Hours Only",
"category": "scheduling",
"rules": {
"allowed_hours": {"start": 9, "end": 17},
"allowed_days": [0, 1, 2, 3, 4],
"timezone": "America/New_York",
"blackout_dates": ["2026-12-25", "2026-12-26", "2027-01-01"],
"maintenance_windows": [
{"day": 6, "start": 2, "end": 6}
]
},
"scope": {"agents": ["finance-agent"]},
"enabled": true
}

The example above: New York business hours, Mon-Fri only, blocked on Christmas/Boxing/New Year, and a recurring Sunday 2-6am maintenance window.

SDK Integration

import waxell_observe as waxell
waxell.init()

@waxell.observe(agent_name="finance-agent", enforce_policy=True)
async def reconcile(date: str) -> str:
# before_workflow: blocks if outside business hours, on weekend,
# in blackout, or in maintenance window.
return await run_reconciliation(date)

Observability

FieldExample
Categoryscheduling
Actionblock
Reason"Workflow not allowed at 20:00 (allowed: 9:00-17:00)"
Metadata{"current_hour": 20, "allowed_start": 9, "allowed_end": 17}
FieldExample (day violation)
Reason"Workflow not allowed on Saturday. Allowed days: Monday, Tuesday, Wednesday, Thursday, Friday"
FieldExample (blackout)
Reason"Workflow blocked on blackout date: 2026-12-25"

Common Gotchas

  1. allowed_hours.end is exclusive. {"start": 9, "end": 17} allows runs at 9:00:00 through 16:59:59. A run at 17:00 is blocked. To allow up to and including 5pm, set end: 18.
  2. Day numbering: 0=Monday, 6=Sunday. Not the Python time.struct_time convention. The handler uses datetime.weekday().
  3. Invalid timezone silently falls back to UTC. A typo in timezone (e.g. "America/New_Yokr") logs a warning and uses UTC. Test with a real tz on first deploy.
  4. blackout_dates are evaluated in the configured timezone. A blackout for "2026-12-25" blocks 25 December in America/New_York, not UTC. Cross-tz tenants should set this carefully.
  5. Maintenance windows do not span midnight. A window {"day": 6, "start": 22, "end": 6} will not block 22:00 Sunday through 06:00 Monday -- start > end means the window never fires. Use time-of-day-gating for overnight windows.
  6. Only before_workflow is enforced. A long-running workflow that starts at 16:55 and finishes at 17:10 is not killed when business hours end. Combine with Safety max_steps/max_tool_calls for hard runtime caps.

Next Steps