Skip to main content

Time-of-Day Gating Policy

The time-of-day-gating policy enforces window-of-allowed-use rules per agent / workflow. "Don't let the loan-decision agent fire after 6pm" is a common control in regulated workflows (financial services, healthcare staffing, retail fulfillment after market hours). Compared to scheduling, this handler supports multiple windows per policy, overnight windows that span midnight, mid-execution enforcement, and workflow-type exemptions.

Maps to SOX §404 IT general controls (segregation of access by time), ISO 27001 A.9.4 (information access restriction), and HIPAA §164.312(a)(1) (access control).

Rules

RuleTypeDefaultDescription
allowed_windowsobject[][]List of windows. Each: {"days": ["mon","tue",...], "start": "HH:MM", "end": "HH:MM"}. Empty list = no restriction (no-op)
timezonestring""IANA tz name (America/Chicago, Europe/Berlin). Empty = UTC
applies_to_phasestring[]["before_workflow"]Which phases to enforce in. Values: before_workflow, mid_execution
exempt_workflow_typesstring[][]Workflow types exempt from the gate (e.g. ["administrative"])
action_on_violationstring"block"Either block or warn

Day names are lowercase 3-letter abbreviations: mon, tue, wed, thu, fri, sat, sun. Omitting days on a window means all 7 days.

How It Works

The time-of-day-gating handler runs at the phases listed in applies_to_phase. It supports both before_workflow and mid_execution gates.

PhaseWhat It ChecksActions
before_workflowIf applies_to_phase includes before_workflow, checks current tz-aware time against all allowed_windowsBLOCK or WARN per action_on_violation
mid_executionIf applies_to_phase includes mid_execution, same check (useful for long-running workflows)BLOCK or WARN
after_workflowNo-op (ALLOW) -- time-of-day is a gate, not an audit signalALLOW

A run is allowed if any window matches. Windows where start <= end are same-day ranges (09:00-18:00). Windows where start > end are overnight (22:00-06:00 means 22:00 today through 06:00 tomorrow).

Context Attributes Read

AttributePhasePurpose
context.workflow_type / context.workflow_kindbothMatch against exempt_workflow_types

The handler is otherwise time-only -- no input scanning or state inspection.

Example Policy

{
"name": "Loan Agent Business Hours",
"category": "time-of-day-gating",
"rules": {
"allowed_windows": [
{"days": ["mon", "tue", "wed", "thu", "fri"], "start": "09:00", "end": "18:00"}
],
"timezone": "America/Chicago",
"applies_to_phase": ["before_workflow", "mid_execution"],
"exempt_workflow_types": ["administrative", "monthly-close"],
"action_on_violation": "block"
},
"scope": {"agents": ["loan-decision-agent"]},
"enabled": true
}

Overnight Window (Night-Shift Healthcare Staffing)

{
"name": "Night Shift Window",
"category": "time-of-day-gating",
"rules": {
"allowed_windows": [
{"days": ["mon", "tue", "wed", "thu", "fri", "sat", "sun"],
"start": "22:00", "end": "06:00"}
],
"timezone": "America/New_York",
"action_on_violation": "block"
}
}

SDK Integration

import waxell_observe as waxell
waxell.init()

@waxell.observe(
agent_name="loan-decision-agent",
workflow_type="decision",
enforce_policy=True,
)
async def decide_loan(application_id: str) -> str:
# before_workflow: blocks if outside 9am-6pm America/Chicago
# mid_execution: re-checks every LLM/tool call (long-running runs
# get cut off when business hours end)
return await score_application(application_id)

Observability

FieldExample
Categorytime-of-day-gating
Actionblock
Reason"Run attempted at 2026-05-28T19:42-05:00 (thu); outside all configured allowed windows."
Metadata{"phase": "before_workflow", "signal": "outside_allowed_window", "now": "2026-05-28T19:42-05:00", "day": "thu", "iso_27001": "A.9.4", "sox": "404", "hipaa": "164.312(a)(1)"}

Common Gotchas

  1. Empty allowed_windows is a no-op ALLOW. The handler exits early when no windows are configured -- this is intentional so disabling is one rule away, but means a typo-empty array silently disables enforcement.
  2. Day abbreviations are lowercase 3-letter. "Mon" or "monday" will not match. The handler does compare case-insensitively against the canonical set, but "weekdays" is not understood.
  3. Invalid timezone falls back to UTC silently. Like scheduling, an unparseable IANA name logs a debug message and uses UTC.
  4. mid_execution enforcement requires opt-in. Default applies_to_phase is ["before_workflow"] only -- a workflow started at 5:55pm will run to completion even if it crosses 6pm. Add mid_execution to the list to kill long-running runs at the boundary.
  5. exempt_workflow_types is case-insensitive. "Administrative" and "administrative" both match. But it checks context.workflow_type OR context.workflow_kind -- if your runtime only sets workflow_name, the exemption never fires.
  6. Overnight windows wrap UTC midnight, not local midnight, when tz is empty. A 22:00-06:00 window with no timezone is evaluated against UTC. For New-York-overnight semantics, set timezone: "America/New_York".
  7. short_circuit_on_block = True. When this handler blocks, other handlers in the chain are skipped. Useful for time-as-master-gate; surprising if you expected a downstream policy to also evaluate.

Next Steps