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
| Rule | Type | Default | Description |
|---|---|---|---|
allowed_windows | object[] | [] | List of windows. Each: {"days": ["mon","tue",...], "start": "HH:MM", "end": "HH:MM"}. Empty list = no restriction (no-op) |
timezone | string | "" | IANA tz name (America/Chicago, Europe/Berlin). Empty = UTC |
applies_to_phase | string[] | ["before_workflow"] | Which phases to enforce in. Values: before_workflow, mid_execution |
exempt_workflow_types | string[] | [] | Workflow types exempt from the gate (e.g. ["administrative"]) |
action_on_violation | string | "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.
| Phase | What It Checks | Actions |
|---|---|---|
before_workflow | If applies_to_phase includes before_workflow, checks current tz-aware time against all allowed_windows | BLOCK or WARN per action_on_violation |
mid_execution | If applies_to_phase includes mid_execution, same check (useful for long-running workflows) | BLOCK or WARN |
after_workflow | No-op (ALLOW) -- time-of-day is a gate, not an audit signal | ALLOW |
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
| Attribute | Phase | Purpose |
|---|---|---|
context.workflow_type / context.workflow_kind | both | Match 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
| Field | Example |
|---|---|
| Category | time-of-day-gating |
| Action | block |
| 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
- Empty
allowed_windowsis 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. - 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. - Invalid timezone falls back to UTC silently. Like
scheduling, an unparseable IANA name logs a debug message and uses UTC. mid_executionenforcement requires opt-in. Defaultapplies_to_phaseis["before_workflow"]only -- a workflow started at 5:55pm will run to completion even if it crosses 6pm. Addmid_executionto the list to kill long-running runs at the boundary.exempt_workflow_typesis case-insensitive."Administrative"and"administrative"both match. But it checkscontext.workflow_typeORcontext.workflow_kind-- if your runtime only setsworkflow_name, the exemption never fires.- Overnight windows wrap UTC midnight, not local midnight, when tz is empty. A
22:00-06:00window with notimezoneis evaluated against UTC. For New-York-overnight semantics, settimezone: "America/New_York". 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
- Scheduling Policy -- Simpler single-window variant with blackout dates and maintenance windows
- Approval Policy -- Require human override outside hours instead of hard-blocking
- Compliance Policy -- Bundle time-of-day with PII, audit, and other controls per framework
- Policy Categories