> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Intraday adherence and agent requests

> Run the day-of WFM loop: 15-second real-time dashboards, breach detection, the exceptions queue, and two-sided agent requests (time-off, shift swaps, preferences) with counterparty accept.

# Intraday adherence and agent requests

Planning-side WFM — forecasts, schedules, assignments — is only half of a contact center's day. The day-of loop answers a different question: **are the agents who are scheduled actually working the schedule right now**, and when they are not, how do you decide whether a deviation is excused or counts against them? This guide walks that loop end-to-end: the live adherence surface, how breaches are scored, the exceptions queue that excuses them, and the two-sided agent requests that feed it (time-off, shift swaps, preferences).

This guide assumes the schedule already exists — the schedule-side workflow (shift templates, skills, generation, assignments) is covered in [Workforce-management workflows](/guides/wfm-workflows), and the full contract for every call below is on the [WFM API reference](/api-reference/wfm). This page is the narrative for everything that happens **after** the schedule goes live.

## 1. The day-of loop

Once a schedule is published, four surfaces keep supervisors ahead of coverage, and they all live under **Voice → Scheduling** on the dashboard (the **Intraday**, **Approval queue**, and exceptions-queue tabs) or under `/api/v1/wfm/*` on the API:

1. **Intraday staffing** — scheduled vs. required agents per channel for the operating date. A deficit is what a rebalance, an open shift, or an overtime offer fixes.
2. **Real-time adherence** — which scheduled agent is off-schedule *right now*, scored every 15 seconds against the live agent-state feed.
3. **The breach feed** — the subset of off-schedule agents whose deviation has run long enough to warrant attention (default: 5 continuous minutes).
4. **The exceptions queue** — the approve/deny workflow that decides whether a deviation an agent files about gets excused from their adherence score, or continues to count against them.

Everything here is two-sided. Supervisors evaluate and decide; agents file exceptions and requests; the system credits or penalizes only once the two sides agree. Approving a time-off request, approving a shift swap, or approving an adherence exception all change how the same scoring engine treats the same agent on the same day — which is why they belong to one loop, not three separate features.

## 2. Prerequisites

Three things have to be true before any of the day-of surfaces report truthfully:

* **The schedule exists for the operating date.** Adherence computes against `wfm_schedule_assignments` rows with status `scheduled` or `swapped` for the day being evaluated; if generation has not covered today, the dashboards return empty agent lists, not zeros.
* **A live agent-state feed is set.** Adherence compares the schedule against what agents actually do in the ACD — online, on a call, on break, in training, logged out. Those states come from the agent activity (aux code) feed in the dialer or inbox. If agents never set their aux state, intraday adherence reads as blank (not "good"), the breach feed sees nothing, and exceptions are being asked to excuse deviations that were never measured. Fix the feed before approving any exceptions.
* **API access with the right scope.** Reads (the dashboards, the breach feed, the request lists) need an API key with `wfm:read`; agent self-serve writes (filing an exception, a request, an accept-swap) execute under any authenticated role acting as themselves. Management decisions — approve, deny, award — require the `owner` or `admin` role. A supervisor-adjacent role (`supervisor`) can read the RTA stream and breach feed, but is filtered to its assigned queues.

## 3. Intraday telemetry — RTA stream + breach feed

Two live surfaces answer the same question at two different cadences, and the **Intraday** tab on the dashboard wires both, refreshing on 15-second intervals.

### 3a. The real-time stream (server-sent events)

For a supervisor who runs a wallboard or a custom manager dashboard, `GET /api/v1/wfm/rta/stream` is a server-sent event (SSE) stream that pushes adherence changes rather than requiring a poll. It opens with a `connected` event, then a full `snapshot` of every scheduled agent's current adherence state, then forwards two event types live: `rta.state_changed` (an agent flipped from in-adherence to off-schedule, or back) and `wfm.adherence_alert` (an agent's cumulative intraday ratio dropped below threshold). The stream stays open with a 30-second heartbeat; a queue-scoped supervisor sees only their queues, and a supervisor with no queue mappings sees an empty stream rather than the tenant-wide picture.

```bash theme={null}
# One supervisor's live adherence feed (curl consumes SSE as plain lines)
curl -N "https://api.orbit.devotel.io/api/v1/wfm/rta/stream?date=2026-09-25" \
  -H "X-API-Key: dv_live_sk_your_key_here"
```

The stream queries accept a `queue_id` filter and a `date` override; the date defaults to the tenant's operating day in its shift timezone. When SSE is not an option — a cron-style worker, a fetch-driven dashboard, an API client that cannot hold a long-lived socket — the same snapshot is available as a poll on `GET /api/v1/wfm/adherence/intraday?date=…` and the current breach subset on the endpoint below.

### 3b. The breach feed (who have I actually got a problem with)

An agent who has been off-schedule for 45 seconds because they are between calls is noise; an agent who has been off-schedule for 14 minutes is an incident. `GET /api/v1/wfm/rta/breaches` applies that duration threshold so the supervisor's "who needs a nudge" list is short and actionable:

```bash theme={null}
# The current off-schedule agents, worst-first, where the deviation has
# been running for at least five minutes
curl "https://api.orbit.devotel.io/api/v1/wfm/rta/breaches?threshold_minutes=5" \
  -H "X-API-Key: dv_live_sk_your_key_here"
```

The response carries `agents_evaluated`, `breach_count`, and a `breaches` array of `(agent_id, current_state, continuous_ooa_seconds, current_state_started_at)` sorted longest-breach-first. `threshold_minutes` is the supervisor-tunable knob — default 5, clamped to 1–480 — so a stricter or a noisier team can tune the band without a redeploy. The `op` query switches the boundary semantics (`gte` by default; `gt` for "strictly over"). A `supervisor` role is filtered to the queues an owner or admin assigned them: a supervisor with no queue mappings gets an empty breach list, not the tenant-wide firehose.

## 4. How a breach is scored

Every surface above reads the same scoring engine, and the engine answers two questions per scheduled agent per tick:

1. **Where is the agent now?** — the latest entry on their adherence-events timeline for the operating date (their current aux state and when it started).
2. **Should they be working?** — whether the schedule has a `scheduled` or `swapped` assignment spanning right now. Pre-shift agents and post-shift agents are graded `off_shift` and never generate a breach; breaches only exist inside an active assignment window.

An agent is **in adherence** when their current aux state is one the shift treats as work (online, on a call, on a scheduled break or activity whose timing matches the shift template's activities). An agent is **off-schedule** when their state says otherwise — logged out mid-shift, in an unscheduled aux state, on a break that ran 20 minutes past its timed segment. The `continuous_ooa_seconds` (out-of-adherence) counter is the elapsed time since the current off-schedule state began; it is what the breach threshold compares against. An approved exception does not delete the deviation — it marks it excused so the ratio recovers, and the raw and the adjusted score are both reported so the carve-out is auditable, never silent.

## 5. The exceptions queue — excuse or count

Adherence punishes agents for deviations they had no control over: a dialer outage, a mandatory all-hands, unplanned coaching a manager pulled them into. An **adherence exception** is the filed carve-out — while approved, those minutes stop counting as non-adherent.

An agent (or a manager on their behalf) files one in `pending`:

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/wfm/adherence-exceptions \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "agent_id": "user_agent_12",
    "exception_type": "system_outage",
    "starts_at": "2026-09-25T13:00:00Z",
    "ends_at": "2026-09-25T13:45:00Z",
    "reason": "Dialer SIP trunk failover — could not place calls"
  }'
```

`exception_type` is one of `system_outage`, `emergency_meeting`, `unplanned_coaching`, `approved_absence`, `training`, or `other`. Non-managers may only file for themselves (an attempt to file for another agent returns `403`); managers may file on behalf of any agent. The `ends_at` must be strictly after `starts_at` — anything else returns `422` with a field location.

A manager then works the queue. `GET /wfm/adherence-exceptions` filters by `agent_id`, `status`, `exception_type`, and a `from`/`to` window; non-manager callers only ever see their own rows, so an agent's queue view never leaks a peer's reason text:

```bash theme={null}
# The pending queue, most recent first
curl "https://api.orbit.devotel.io/api/v1/wfm/adherence-exceptions?status=pending&from=2026-09-19&to=2026-09-25" \
  -H "X-API-Key: dv_live_sk_your_key_here"

# Approve — the deviation becomes excused from adherence scoring
curl -X POST https://api.orbit.devotel.io/api/v1/wfm/adherence-exceptions/exc_abc123/approve \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "note": "Confirmed outage window on the status page" }'

# ...or deny — the deviation continues to count against the agent
curl -X POST https://api.orbit.devotel.io/api/v1/wfm/adherence-exceptions/exc_abc123/deny \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "note": "Schedule already covered this as a training activity" }'
```

The state machine is strict, and it is the part of the queue that makes it auditable: only `pending` rows can be approved or denied (a second decision returns `409 INVALID_TRANSITION`, which is how a queue race between two managers resolves cleanly), and only the requester — or a manager, as a moderator action — can cancel a still-pending row with `POST /wfm/adherence-exceptions/{id}/cancel`. An `approved` exception credits the excused seconds back to the adherent total; a `denied` exception leaves the deviation counting against the agent. On the dashboard this queue sits under **Voice → Scheduling** next to the approval queue.

For the management view over a period, `GET /wfm/adherence-exceptions/trends?from=…&to=…` returns counts and excused seconds grouped by exception type and status — a spike in `system_outage` points at infrastructure, a spike in `emergency_meeting` points at ops, and a spike in `approved_absence` for one team points at a manager habit. Non-managers calling trends get aggregates over their own rows only, so the per-team roll-up never leaks across teams.

## 6. Agent requests — time-off, swaps, preferences

Exceptions explain deviations after the fact; requests are how agents ask for changes to the schedule **before** the deviation happens, and how supervisors keep the loop two-sided. They queue on the **Approval queue** tab and on the API under `/wfm/requests`, as a discriminated union on `type`:

```bash theme={null}
# Time-off — carries a (starts_at, ends_at) window
curl -X POST https://api.orbit.devotel.io/api/v1/wfm/requests \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "time_off",
    "agent_id": "user_agent_12",
    "starts_at": "2026-10-02T09:00:00Z",
    "ends_at": "2026-10-02T17:00:00Z",
    "reason": "Dentist — back Tuesday"
  }'

# Shift swap — names a counterparty agent; goes `awaiting_counterparty`
curl -X POST https://api.orbit.devotel.io/api/v1/wfm/requests \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "shift_swap",
    "agent_id": "user_agent_12",
    "swap_with_agent_id": "user_agent_09",
    "reason": "Covering their Saturday"
  }'

# Preference — free-form, no window; a manager acts on it manually
curl -X POST https://api.orbit.devotel.io/api/v1/wfm/requests \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "preference",
    "agent_id": "user_agent_12",
    "reason": "Prefer mornings — childcare pickup covers afternoons"
  }'
```

The three types behave differently, and the differences are where the two-sidedness lives:

* **Time-off** enters `pending` immediately. If the tenant has an active `pto` leave policy, the submit response also surfaces the agent's remaining balance and how many hours this request would consume — the balance is informative at submit and authoritative at approve: an approval that would overdraw the balance is blocked with `409 INSUFFICIENT_LEAVE_BALANCE`, and a successful approval posts the debit atomically. Approving also marks the overlapping assignment rows `time_off`, so the adherence engine does not penalize the approved absence.
* **Shift swap** enters `awaiting_counterparty` — the named counterparty agent never asked to swap, so ratifying without their consent is the unilateral-reassign defect this state machine prevents. Only the counterparty can move the request to `pending`, with `POST /wfm/requests/{id}/accept-swap`; approve and deny gate on `pending`, so a swap that has not been accepted cannot be ratified. On approval the two agents' `scheduled` assignments are exchanged atomically so a half-swap is never observable.
* **Preference** enters `pending` and stays free-form — a supervisor uses it as a signal when building the next schedule; nothing is mutated automatically.

Reviewers decide with `POST /wfm/requests/{id}/approve` or `/deny` (both take an optional `note`), and the requester can cancel from `pending` or `awaiting_counterparty` with `POST /wfm/requests/{id}/cancel`. Every list and read endpoint applies the same scope: managers see all rows; agents see only their own — and agents reading a peer's request get a `404`, not a `403`, so the endpoint does not leak that the request exists.

## 7. Worked example — one 30-minute incident

It is a Saturday morning and the retail queue is scheduled with four agents; one of them, agent 12, is late after a bus breakdown. The supervisor is watching the intraday staffing tab and the breach feed.

**09:12** — The breach feed surfaces agent 12 with `continuous_ooa_seconds` accumulating past the five-minute default threshold: `GET /wfm/rta/breaches` shows one agent, `breach_count: 1`, `current_state: "offline"`. The supervisor gets the nudge list down to one name.

**09:14** — The supervisor files an adherence exception on the agent's behalf (managers may file for any agent; agents themselves can file the same request under their own identity):

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/wfm/adherence-exceptions \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "agent_id": "user_agent_12",
    "exception_type": "approved_absence",
    "starts_at": "2026-09-27T09:00:00Z",
    "ends_at": "2026-09-27T09:30:00Z",
    "reason": "Late arrival — supervisor aware"
  }'
```

**09:18** — The queue holds one pending exception. The supervisor approves it with a note; the exception credits the off-schedule window back into adherence, so the late arrival does not drag the day:

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/wfm/adherence-exceptions/exc_late1/approve \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "note": "Agent called ahead; late bus. Approved." }'
```

**09:31** — The agent arrives and sets their state to online. The next RTA tick clears the breach; the breach feed returns to zero; the exceptions queue has one resolved row with an auditable note; the day's adherence ratio, computed with the approved exception credited, reflects the half hour as excused rather than as 30 minutes of unexplained absence. Without the exception, the same 30 minutes would read as non-adherent through the agent's week-end review.

## 8. Operational playbook

Three habits keep the day-of loop running well, and each maps to one of the surfaces above:

* **Shrink the exceptions queue at handover.** A pending exception is a deviation still marked non-adherent. Deciding every pending exception before a supervisor hands over — and again before a team's week is locked for review — keeps per-agent numbers honest and keeps the trends view from drifting. The queue tolerates one pending decision per row, so two supervisors deciding in the same minute get a clean `409` on the loser rather than a double-credit.
* **Nudge on the breach feed, not the ratio.** The intraday ratio is a lagging indicator; it moves after the deviation has been running. The breach feed is the leading one: it surfaces the first tick of an off-schedule state that crosses the duration threshold. Coach from the threshold-tick onward — a five-minute nudge prevents the twenty-minute absence — and save the ratio view for end-of-day review.
* **Measure shrinkage from the requests, not guesswork.** Approved time-off and denied time-off both tell you which hours never land as productive work; approved exceptions tell you which deviations were management-caused. The shrinkage report (and its input to capacity planning) reads the same events the adherence engine scores, so excusing an exception or approving a time-off request changes the next forecast's shrinkage figure the same day, not the next planning cycle.

## 9. Production checklist

Run this once when you stand the day-of loop up, and again any time the numbers look wrong:

* **Seeded schedule:** at least one assignment in `scheduled`/`swapped` spans today. The dashboards evaluate the assignment list — nothing to score returns empty, not zero.
* **Live agent state feed:** agents consistently set their ACD/aux state. Spot-check one agent's intraday timeline against their schedule; if their aux feed is blank, fix the feed before approving any exceptions — you would be excusing unmeasured time.
* **Queue-scoped supervisors:** each supervisor role has queue mappings. A supervisor with no mappings fails closed and sees an empty breach list and an empty stream.
* **Threshold is deliberate:** the breach feed's default 5-minute threshold suits most teams. If Monday's queue is staffed on a tighter band, set `threshold_minutes` explicitly (1–480 accepted).
* **Notes on every decision:** approve, deny, and cancel carry an optional `note`; leave it every time. The note is the six-month-later audit trail between "approved" and "why".
* **Role split:** the dashboards run on `wfm:read`, self-serve filings run under the caller's own identity, and approve/deny/award actions run on an `owner` or `admin` key. Do not put a management key on an agent-facing client.

## See also

* [Workforce-management workflows](/guides/wfm-workflows) — the schedule-side loop this guide leaves alone (templates, skills, generation, assignments, open shifts, overtime/VTO)
* [WFM API reference](/api-reference/wfm) — full endpoint contract for every call above
* [Quality Management API](/api-reference/quality) — scorecards and the adherence panels under **Quality**
* [Build a contact-center QA program](/guides/quality-management-program) — the quality loop that reads WFM adherence as an input
* [Webhook consumer](/guides/webhook-consumer) — push adherence events to a manager dashboard outside Orbit
* [Error codes](/api-reference/error-codes) — `409` state conflicts and `422 VALIDATION_ERROR` shapes
