@xema/omni-protocol 0.1.106 → 0.1.108

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/guide/lead.md ADDED
@@ -0,0 +1,334 @@
1
+ # `@xema/omni-protocol`: Team leads
2
+
3
+ The lead's own surface: the team member list, the lead's commands, lead assist, listening to a call. Part of the contract in `guide.md`, which holds the terms, the shapes and the rules every file here relies on; a reference in bold names the file it points into where that is not this one.
4
+
5
+ ## Team leads
6
+
7
+ The team feature is the lead's own surface, beside the agent's and never mixed with it. The
8
+ login's `lead` flag turns it on in the agent application; the lead retrieves their members and
9
+ sees, for each, their assignments as they stand and the full history of each, and acts on them
10
+ with the authority the flag carries. Nothing the lead does here is a task on their desk. A lead
11
+ who also takes calls is an agent like any other on the agent side, and a lead who does not want
12
+ the agent role for now places themself on a break of a working kind and keeps the team feature on.
13
+
14
+ The team reaches the lead whole once, on `team-updated` after the lead switches the feature on
15
+ and on every snapshot while it is on, and from then on one member at a time: when a member is
16
+ offered a call, answers, holds, mutes or is taken over, the provider publishes that member whole
17
+ on `team-member-updated`, so the lead's screen moves when the member's does and a team of two
18
+ hundred is never resent because one of them held. The agent application asks for nothing and
19
+ computes nothing. See **`team-updated`** in `guide.md` and its companions.
20
+
21
+ | Field | Contract |
22
+ | --- | --- |
23
+ | `members` | Every member of this lead's team, whatever their state, each with their open tasks. `[]` says the lead has a team with nobody in it; omitting the team member list says something else entirely — see **The login is the permission** below. |
24
+ | `policies` | The team's policy per capability as it stands — the setting, who set it, and `lockedBy` where a level above the team made it theirs to keep. Present where the provider offers policy control; absent where it does not. See **Who decides what an agent may do** in `guide.md`. |
25
+
26
+ | `TeamMember` field | Contract |
27
+ | --- | --- |
28
+ | `id` | Required `UserId`. A task carries no names and neither does a team member list: Omni resolves what to display with `getUserDetails()`. |
29
+ | `availability` | Required. What the member is doing now. |
30
+ | `since` | Optional. When the current `availability` began — not when they signed in, and not when the team member list was read. |
31
+ | `break` | Present only while the member has an outstanding break request. See **A member waiting for a break**. |
32
+ | `tasks` | The member's open tasks as `MemberTask`s: the same task the member's desk holds, less the workspace. The assignment, title, task type, phase, party, room, audio and completion terms -- `completionMode`, `wrapAllowance`, `wrapEndsInSeconds` -- are what the lead reads, and the task's record -- `history`, steps and totals -- travels whole, exactly as the member's desk holds it, so the lead's screen and the member's show the same record. The workspace -- `capabilities`, `capabilitySource`, `browsers` and their URLs -- is the member's desk's alone and never travels (`team.member.task.workspace`). The party's number and email are shown to leads or locked as the provider decides for leads, apart from what it decides for the member -- see **What the queue locks is locked on the whole task**. `[]` when the member holds none; omitted only where the provider cannot see them. |
33
+ | `listening` | Every lead on this member's call — listening, coaching or joined — published to every lead alike: each entry names the lead by `leadId`, which of the member's calls by `assignmentId`, the `mode` they are heard in, and since when. The entry naming the signed-in lead is what their own audio opens on. Absent while nobody is on the call, never `[]` (`team.member.listening.*`); a lead is on one member's call at a time (`team.listening.single`), and a lead who is heard is also in the task's `onCall` (`team.member.listening.heard`). |
34
+ | `request` | Present while the member is asking a lead to join one of their calls: which assignment, the note they wrote, since when. The call named is one their `tasks` carry (`team.member.request.assignment`). Absent when they are not asking. See **Lead assist**. |
35
+ | `phone` | The member's phone as the platform sees it: the same `PhoneState` the member's own snapshot carries, so a lead sees a desk phone that is down, or a call on it that is no task, as the member does. Present where the provider declares `phoneStatus`. See **The phone's own view** in `guide/phone.md`. |
36
+ | `shift` | The member's own history for the day, about the person rather than any one call: `signedInAt`, `signedOutAt` once it has happened, today's totals as the provider counts them — `talkSeconds`, `holdSeconds`, `breakSeconds`, `tasksHandled`, each present only when the provider knows it — and the day's sign-in, sign-out and break `events`, oldest first. The same `Shift` the member's own snapshot carries, the same numbers from the same count. Omitted where the provider cannot say. |
37
+
38
+ Each availability value means one thing:
39
+
40
+ | Value | Meaning |
41
+ | --- | --- |
42
+ | `ready` | Signed in, able to take work, none assigned. |
43
+ | `on-task` | Working on at least one task. It says nothing about how many, and nothing about whether more will fit. |
44
+ | `on-break` | Stopped and not taking work, whether they asked or somebody stopped them. The reason lives on their own `BreakState`, not here. |
45
+ | `elsewhere` | Signed in here, and the agent application holds this agent's capacity for another provider (`count: 0`, agent application-stopped): not receiving this provider's work, and not on a break. See **Capacity**. |
46
+ | `signed-out` | Known to this team but not signed in to this provider. |
47
+
48
+ **Publish the whole team once, then each member whole.** Team presence typically reaches an
49
+ adapter over a best-effort channel with no ordering and no delivery guarantee, so the adapter
50
+ reconciles against its own authoritative read before it publishes anything. What it publishes is
51
+ the whole team on `team-updated` -- after the switch, and on every snapshot -- and after that one
52
+ member at a time on `team-member-updated`, carrying the member whole: their availability, their
53
+ tasks, their listening, their shift, their request, as they now stand. Applying the same member
54
+ twice changes nothing, so a redelivery is harmless, and a member the team never carried is added
55
+ by it, as a colleague signing in is. `team-member-removed` takes one off. Never a field of a
56
+ member, never a task of theirs on its own: a hold on a member's desk is that member republished
57
+ whole on the lead's, and nothing else on the lead's screen moves.
58
+
59
+ **Whatever the member's desk hears of a task, every lead hears of the member.** The trigger is
60
+ not a list of changes but the publication itself: each `task-offered`, `task-updated`,
61
+ `task-audio-started`, `task-audio-ended` and `task-ended` the provider sends to the member is
62
+ matched by a `team-member-updated` to every lead with the feature on, carrying the member whole
63
+ with the task as it now stands, or without it once it has ended. A held leg closing with its
64
+ `seconds`, a room restated, a phase moving, the wrap left ticking on a restatement: each reaches
65
+ the lead's copy in the same round it reaches the member's, and the two copies of the record never
66
+ part for longer than the delay between them.
67
+
68
+ **The agent's day is on the wire.** The lead sees `shift` totals as the provider counts them, and
69
+ the agent sees the same: `Snapshot.shift` and `shift-updated` carry the agent's own day in the same
70
+ shape, from the same count, so "calls today: 12" is one number on both screens. The desk computes
71
+ nothing of the day; `tasksHandled` moves when a task ends and `talkSeconds` when a leg closes, and
72
+ the active call's running time is the screen's own until then, as **Every screen counts from the
73
+ provider's clock** sets out.
74
+
75
+ **Omit `since` rather than inventing one.** Omni renders it as a duration, so a timestamp
76
+ synthesised from the adapter's own clock at seed time reads as "on task for 0 seconds" for
77
+ everybody — worse than showing nothing, because it looks like data. Send it only when the provider
78
+ knows when the state actually began. It times the current `availability`, so it moves every time
79
+ that value does. And it is counted from the provider's clock, never the screen's, so a provider
80
+ that publishes it declares `timeCheck` -- see **Every screen counts from the provider's clock** in `guide.md`.
81
+
82
+ **The login is the permission.** A team member list goes to a login that declares
83
+ `capabilities.lead`, once the team feature is on, and to nobody else. Omni never decides who
84
+ leads a team: the provider said so at sign-in, and the team member list agrees with it — owed,
85
+ `members: []` included, once a lead has switched the feature on; absent for everybody else, which
86
+ is the correct rendering for an agent who leads nobody (`team.required`, `team.unentitled`). What
87
+ the lead may do with it comes with the flag: there is no per-action permission on the login or on
88
+ the list.
89
+
90
+ **The lead switches the feature on and off, and the provider is told.** A lead may work as an
91
+ agent alone, as agent and lead, or as lead alone, and moves between them on the fly. The
92
+ agent application says which with `{ type: "lead-features", enabled }`: while it is on, the
93
+ provider sends team events scoped to the lead's team and the lead may act; while it is off, the
94
+ lead is an ordinary agent, nothing of the team is owed or expected (`team.unexpected`,
95
+ `event.team.features`), and no lead act is possible, since the lead learns their members'
96
+ assignments only from the list.
97
+
98
+ **The provider assumes nothing.** The switch is not remembered across connections: the agent
99
+ application sends `lead-features` on every connect, after it has stated capacity, and until it has
100
+ the provider treats the feature as off. The connect snapshot therefore carries no team, whatever
101
+ the login declares; the whole team arrives on `team-updated` once the switch is on, and every
102
+ snapshot after that carries it. A conformance run switches the feature on for a lead and holds the
103
+ provider to that first `team-updated` (`team.required`); a team on the connect snapshot is the
104
+ provider assuming (`team.unexpected`).
105
+
106
+ **The team member list never carries the agent it is published to.** A lead does not report to
107
+ themself: their own break request and their own ask for a lead go up to whoever leads them and
108
+ appear on *that* person's team member list, as a member with a `request`, while the requester sees
109
+ only their own `BreakState` and their task's `leadAssist` move. An adapter whose platform lists the lead
110
+ among their own members filters the signed-in identity out before publishing. **Being a lead is a
111
+ role the provider knows, never inferred from who is listed:** it is declared at sign-in, a lead
112
+ with nobody in their team publishes `members: []`, an agent with no such role publishes nothing, and no
113
+ member count can tell those two apart.
114
+
115
+ ### Lead commands
116
+
117
+ One method, `executeTeam`, taking a discriminated command exactly as `execute` takes a
118
+ `TaskCommand`. Every act on a member's call names the member, `memberId`, and may name the
119
+ assignment, `assignmentId`, where the lead has it; the provider resolves the member's current
120
+ assignment from the member alone.
121
+
122
+ | Command | Effect |
123
+ | --- | --- |
124
+ | `{ type: "lead-features", enabled }` | The team feature on or off. See **The lead switches the feature on and off** above. |
125
+ | `{ type: "decide-break-request", memberId, decision, reason? }` | Settles one pending request. `decision` is `granted` or `denied`. A grant moves the member to `granted`; a denial ends the request and moves it directly to `not-requested`. |
126
+ | `{ type: "set-break-policy", policy }` | `approval-required`, `automatically-approved`, or `requests-suspended`. |
127
+ | `{ type: "force-break", memberId, reasonId?, reason?, expectedDurationMs? }` | Puts a member on a break they did not ask for. `reasonId` names a published `BreakReason.id` and is required whenever the provider publishes `reasons`; the member's forced break carries it as `activeReasonId`, so its kind is known. Optional `expectedDurationMs` is advisory; the resulting forced break publishes what is left of it as `expectedEndsInSeconds`, restated on every publication. |
128
+ | `{ type: "end-forced-break", memberId }` | Asks the provider to lift a forced-break restriction a lead asked for, by clearing `BreakState.forced`; one the platform imposed is its own to lift (`team.command.endForcedBreak.provider`, `omni.break-forced-by-provider`). The committed break continues; only the agent resumes work. |
129
+ | `{ type: "join", memberId, assignmentId? }` | Answers the member's request: the provider bridges the lead's channel into the call. See **Lead assist**. |
130
+ | `{ type: "decline", memberId, assignmentId?, reason? }` | Refuses the member's request. |
131
+ | `{ type: "listen", memberId, assignmentId? }` | The lead's channel joins the member's call unasked, in silence. See **Listening to a call**. |
132
+ | `{ type: "coach", memberId, assignmentId? }` | The lead is heard by the member alone. Needs the lead on that member's call. |
133
+ | `{ type: "join-call", memberId, assignmentId? }` | The lead is heard by everyone on the call. Needs the lead on that member's call. |
134
+ | `{ type: "leave", memberId, assignmentId? }` | The lead's channel leaves the member's call, however it got there. The member's call goes on. |
135
+ | `{ type: "take-over-call", memberId, assignmentId? }` | The provider moves the call to the lead as its new agent, from a joined call or a listened one alike. See **Lead assist**. |
136
+ | `{ type: "set-policy", capability, setting }` | Sets the team's policy for one capability: `on`, `off`, or `person`. See **Who decides what an agent may do** in `guide.md`. |
137
+
138
+ `memberId` is this provider's own identifier for the member, as published on its team member list. It is
139
+ never an identifier from another provider, and Omni does not translate between them; names come
140
+ from `getUserDetails()`. `validateTeamCommand` holds each command to the team member list as it
141
+ stands -- see **Runtime break prerequisite checks** in `guide/breaks.md`.
142
+
143
+ `requests-suspended` means requests are **rejected outright** rather than left pending — nobody is coming to
144
+ approve them. A provider that suspends break requests must also publish `canRequestBreak: false` to the team's
145
+ agents so they see it before asking. A `force-break` must likewise reach that member as a `forced` break
146
+ on their own `BreakState`, or they are stopped from working with no way to see why.
147
+
148
+ What happens when no lead is online — auto-approving, for instance — is the provider's decision and is
149
+ never expressed here.
150
+
151
+ ### Lead assist
152
+
153
+ An agent on a call may ask a lead to join it -- a dispute that needs approval, a customer who
154
+ asks for a manager, a moment the agent wants a second pair of ears. A call centre calls this
155
+ assistance or escalation, and the name says who assists: the capability is `leadAssist` on the
156
+ task; the lead's side is the team member list, which is already the lead's view of the team. The
157
+ flow, in order:
158
+
159
+ ```ts
160
+ // 1. The agent asks, with a small note. Their task carries `leadAssist` from here on.
161
+ execute({ assignmentId: "alloc-42", command: { type: "lead-assist", action: "request", note: "Refund dispute, needs approval" } })
162
+ // task.leadAssist = { stage: "requested", note: "Refund dispute, needs approval", since }
163
+
164
+ // 2. Every lead with the team feature on sees the request on the member.
165
+ // team-member-updated: member: { id: "A-1", ..., request: { assignmentId: "alloc-42", note, since } }
166
+
167
+ // 3. A lead joins, or declines.
168
+ executeTeam({ command: { type: "join", memberId: "A-1", assignmentId: "alloc-42" } })
169
+ executeTeam({ command: { type: "decline", memberId: "A-1", reason: "In a call" } })
170
+ ```
171
+
172
+ **On `join` the provider bridges the lead's channel into the call.** The member's task moves to
173
+ `leadAssist: { stage: "joined", leadId }` and its `onCall` gains the lead as an `agent`, since a
174
+ lead who is heard is in the room; the member on the team member list carries
175
+ `listening: [{ leadId, assignmentId, mode: "join-call", since }]`, and the lead's audio opens on
176
+ it as **Listening to a call** describes. Nothing is a task on the lead's desk: a join is the lead's act on the team surface, not
177
+ work assigned to them.
178
+
179
+ **A lead is on one member's call at a time.** A `join` from a lead already on a call -- their own
180
+ or a member's -- is answered `failed`; the request stands for another lead, or until it is
181
+ withdrawn or declined. A join is work, so Omni offers Join only to a lead whose voice channel is
182
+ free, and a provider answers a `join` from one whose channel is not `failed`.
183
+
184
+ **On `decline`, or a request the agent withdraws with `{ type: "lead-assist", action: "cancel" }`, the
185
+ provider clears `leadAssist` from the agent's task** and drops the request from every team member list. Nothing
186
+ else changes; the agent is still on the call.
187
+
188
+ **On `leave`, the lead's channel drops and the call goes on**: the member's task loses its
189
+ `leadAssist`, the member loses this lead's entry in `listening`, and the lead is free. Nothing ended for the member.
190
+
191
+ **On `take-over-call`, the provider moves the call from the member to the lead**, and the lead is
192
+ its new agent. There are two paths to it -- the agent asked and the lead joined, or the lead was
193
+ listening unasked -- and one act at the end of either. What follows is the same on both:
194
+
195
+ | | The member's task | The lead's desk |
196
+ | --- | --- | --- |
197
+ | Take-over | `task-audio-ended`, then `completing`: the member's interaction is over and their wrap runs, exactly as after `end-call`. The audio ends first, as before every voice ending (`stream.taskEnded.audioOpen`). The member loses this lead's `listening` entry on the team member list no later than the offer, and the lead's listening audio closes. | An ordinary assignment arrives: `task-offered` with `acceptance: "automatic"`, carrying the call's history and `takenOver: { memberId, since }`, within the lead's capacity. |
198
+ | The member completes | `task-ended` with `{ type: "taken-over", leadId }`, at the member's own completion. The lead is named by user id, since a lead is not a directory item. | The lead works the call as any agent would, and ends it as any call ends. |
199
+
200
+ **The taken-over call is offered whatever break the lead is on, and counted like any other.** A
201
+ lead working as lead alone is on a break of a working kind, and the provider assigns the call
202
+ regardless: it is the one task a break in effect may hold (`break.on-break.tasks` allows it, and
203
+ nothing else). It is a call landing on the lead's desk, so it counts against their capacity as
204
+ every call does, with no exemption: a lead at capacity cannot take over, and the provider answers
205
+ `failed`. The other conditions are that the lead is not already on another call and their voice
206
+ channel is free. A lead with the team feature off cannot take over, since they have no member's
207
+ assignment to name.
208
+
209
+ ```ts
210
+ const leadAssistCapable = {
211
+ channel: "voice",
212
+ capabilities: { hold: true, leadAssist: true, outcomes: true },
213
+ phase: "in-progress",
214
+ leadAssist: { stage: "joined", leadId: "L-9", note: "Refund dispute, needs approval", since: "2026-08-21T09:04:00Z" },
215
+ } satisfies Pick<Task<"voice">, "channel" | "capabilities" | "phase" | "leadAssist">;
216
+
217
+ const takenOver = {
218
+ channel: "voice",
219
+ capabilities: { hold: true, endCall: true, outcomes: true },
220
+ phase: "in-progress",
221
+ takenOver: { memberId: "A-1", since: "2026-08-21T09:06:00Z" },
222
+ } satisfies Pick<Task<"voice">, "channel" | "capabilities" | "phase" | "takenOver">;
223
+ ```
224
+
225
+ Lead and member alike are `UserId`s of this provider, so an adapter publishing them implements
226
+ `getUserDetails()`; names never travel on a task or a team member list.
227
+
228
+ ### Listening to a call
229
+
230
+ A lead may listen to a member's call without being asked -- to coach, to check quality, to step in
231
+ when it goes wrong. It is a team act naming the member, and no task on the lead's desk carries it:
232
+ the lead's view of the call is the member's task on the team member list, with its room and its
233
+ history. The three modes determine who hears the lead:
234
+
235
+ | Mode | Who hears the lead |
236
+ | --- | --- |
237
+ | `listen` | Nobody. The lead hears both sides in silence. |
238
+ | `coach` | The agent alone. The customer hears nothing. |
239
+ | `join-call` | Everyone on the call. |
240
+
241
+ All three come with the `lead` flag; a centre reserves none of them per lead.
242
+
243
+ **There is no team audio.** Audio is the agent's or the lead's, and the lead's audio while
244
+ listening follows the provider's published state exactly as a task's does: the entry naming this
245
+ lead in `listening` on the member is the provider's word that the lead's channel is in the call.
246
+ When it appears, the application opens `openAudio({ assignmentId })` on the lead's connection for
247
+ the call it names, and plays the `CallAudio` returned; when it goes, the application closes it.
248
+ Another lead's entry opens nothing: it is shown, and that is all. Nothing else says
249
+ when the lead's audio is up, and a reload reopens it from the snapshot's team member list as a task
250
+ carried with `audio: "started"` is reopened.
251
+
252
+ The flow, end to end:
253
+
254
+ ```ts
255
+ // 1. The lead, with the feature on, sees a member on-task with their tasks, and picks a call.
256
+ // 2. The lead's channel joins it in silence. Conditions: the lead's voice channel is free --
257
+ // not on a call, not listening elsewhere. Capacity is not involved: nothing is assigned.
258
+ executeTeam({ command: { type: "listen", memberId: "A-1", assignmentId: "alloc-42" } })
259
+ // 3. The provider publishes the member whole on team-member-updated, to every lead; the member carries
260
+ // listening: [{ leadId: "L-9", assignmentId: "alloc-42", mode: "listen", since }]
261
+ // -- L-9's application opens openAudio({ assignmentId: "alloc-42" }) on its own connection; every
262
+ // other lead's shows L-9 on the call and opens nothing.
263
+ // 4. The lead changes how they are heard; the member is published again with the entry's new mode. The audio stays open.
264
+ // join-call is heard by everyone, so the member's task is also republished with L-9 on onCall as an agent.
265
+ executeTeam({ command: { type: "coach", memberId: "A-1" } })
266
+ executeTeam({ command: { type: "join-call", memberId: "A-1" } })
267
+ // 5. The lead leaves: the provider publishes the member without L-9's entry, and the task without L-9 in the room; the application closes the audio.
268
+ executeTeam({ command: { type: "leave", memberId: "A-1" } })
269
+ // 6. The member's call ends: the same as leave, from the provider's side.
270
+ // 7. The lead takes the call: the member loses this lead's listening entry no later than the offer, the application
271
+ // closes the listening audio, and the lead's own call arrives as an ordinary assignment marked
272
+ // takenOver, counted against capacity, with its audio following task-audio-started as any call's does.
273
+ // At capacity the take-over is refused and the lead stays listening.
274
+ executeTeam({ command: { type: "take-over-call", memberId: "A-1" } })
275
+ // 8. A join on the member's request is the same flow entered at step 3 with mode: "join-call",
276
+ // and the member's task shows leadAssist: joined.
277
+ // 9. A reload of the lead's application: the switch is sent again, the team-updated that follows
278
+ // still carries this lead's entry in listening on the member, so the audio is reopened.
279
+ ```
280
+
281
+ **`listening` on the member is the state**, one entry per lead on the call, restated on every
282
+ change of mode, naming the lead (`team.member.listening.leadId`, once per member,
283
+ `team.member.listening.unique`) and the call, so the application knows which audio is its own to
284
+ open (`team.member.listening.assignmentId`, and one the member's tasks carry,
285
+ `team.member.listening.assignment`). Every lead sees every entry: a silent lead is hidden from
286
+ the agent, never from the other leads and managers watching the same member on other screens.
287
+ A lead is on one member's call at a time (`team.listening.single`).
288
+
289
+ **A lead listens while holding no call of their own.** Omni offers Listen to a lead whose voice
290
+ channel is free, and a provider answers a `listen` from one whose channel is not `failed`. That
291
+ includes a lead on a break: a lead working as lead alone is on a break of a working kind, and
292
+ listens through it.
293
+
294
+ **A silent lead reaches nobody but the leads.** In `listen` and `coach` the member's task carries
295
+ no trace of the lead: not on `onCall`, not in the record. Whether coaching is announced to the
296
+ agent is the platform's business and travels on the audio, not on this wire. In `join-call`, and
297
+ on a `join` answering a request, the lead is heard by everyone and is in the room: the task's
298
+ `onCall` carries them as an `agent` by user id, on the member's desk and on every lead's copy of
299
+ the task alike, and `leave` takes the entry off as it takes the channel off
300
+ (`team.member.listening.heard`).
301
+
302
+ ### A member waiting for a break
303
+
304
+ A member who has asked for a break **keeps working** until Omni commits it, so asking is not an
305
+ availability of its own — it rides alongside one on `break`. That a request exists says nothing
306
+ about whether anybody has to act on it, and the difference is a lead's entire action list:
307
+
308
+ | `break` | Means |
309
+ | --- | --- |
310
+ | `awaiting-approval` | Somebody has to decide. This is the lead's queue. |
311
+ | `granted` | Decided yes, but Omni has not told the provider to stop yet. Work continues and nobody needs to decide. |
312
+ | `starting-after-task` | Already granted; it begins when their current task ends. Nobody needs to act. |
313
+
314
+ Those three are the only values that appear here. `not-requested` is absence — omit `break`
315
+ instead. `on-break` is `availability: "on-break"`, and a denial transitions to `not-requested`,
316
+ so neither survives to be reported. It is otherwise the same `BreakStatus` the member's own
317
+ break state uses, rather than a parallel vocabulary for the lead's view, so the two cannot drift
318
+ apart.
319
+
320
+ Omni offers Approve and Deny only while a member is `awaiting-approval`, shows `granted` as agreed
321
+ but not started, and shows `starting-after-task` as settled.
322
+
323
+ **Live status, not a record.** The provider derives it from what is true now — not stored, not
324
+ historical, carrying no decision made earlier. Like the team member list it belongs to, it is published
325
+ whole and replaced whole, and a provider that cannot say omits it.
326
+
327
+ **An agent is not waiting on one person.** Authority is held by several, everyone who holds it
328
+ sees the request on their own console, and **any one of them settles it**. Omni offers the
329
+ decision to every login that declares `lead` and has the team feature on — which is how the provider already
330
+ says who may decide — and does not try to work out whose turn it is.
331
+
332
+ A request needing *more than one* approval is not something this contract describes. There is
333
+ no partial state to report and no progress to display: a request is either still owed a
334
+ decision or it is not.