Entitlement Assistant · Use Cases
- Lab
- Cloudflare
- Authorization
- AI Agents
This part follows the system at work: six use cases, each a sequence of who asks whom for what, and then, in words, the ones that vary them. Each section’s sequence comes first; its steps, with the code that runs each, follow beneath it.


- Chat turn — where the model works, and the guard is hardest
- Explaining access — why can’t this person do that?
- Grant at once — within every policy: applied on confirmation
- Grant by approval — beyond 14 days: a supervisor decides
- Sweep — what time alone changes, every 15 minutes
- Case lifecycle — the frame around everything else
- Other use cases — a revocation, an extension, and the rest
1. Chat turn
A chat turn is where the model works, and where the system guards hardest. The browser opens a WebSocket to the case’s agent; every frame it sends is checked before the Agents SDK sees it; the request to the model is rebuilt from the conversation the server stored, never from what the page sent; and the model’s tool calls come back to the agent, which runs each as the representative.
Rebuilding the request is what makes the chat trustworthy. As the Agents SDK ships, a page sends the whole history with each message, and the SDK stores it; a page could write an earlier reply the assistant never gave, or a tool result no tool returned, and the model would take it as said. Here the page sends only its newest text: the history is the one the server stored, turn by turn, and a tool’s result reaches the model only from the tool (Figure 23).


Figure 23 — A chat turn
The model reaches the system through nine tools and nothing else. Eight reach data through a use case and only read or prepare; the ninth drafts text and reaches nothing. None confirms, cancels or decides.
| tool | what it does | reaches |
find_customer_account | finds an account by its id, part of its name, or a person’s email | reads |
check_entitlement | whether an account has one feature, with its sources or why not | reads |
list_entitlements | every effective entitlement of an account | reads |
explain_access | why a person can or cannot do a feature’s operation | reads |
propose_case_subject | proposes the account a case is about; it sets nothing | reads |
prepare_grant | prepares a grant as a change request; it changes nothing by itself | prepares |
prepare_grant_revocation | prepares a grant’s revocation | prepares |
prepare_grant_extension | prepares a grant’s extension | prepares |
draft_case_summary | drafts a case’s summary as text; it stores nothing, refuses emails | no use case |
1.1 A chat turn, step by step
1.1 A chat turn, step by step
The guard’s checks run in a fixed order, and each refusal closes the connection with its own code, so the page knows why.
| step | what happens |
| the upgrade | the Origin, the session, the principal reloaded, a well-formed case, its current assignee |
| the handshake | the agent receives the handshake’s headers alone and the session’s id |
| the frame’s size | over 16 KiB closes with 1009 |
| the frame’s kind | a chat request is rebuilt; resume and cancel pass; anything else is dropped |
| the minute and the day | over 20 messages a minute closes with 4429; over the day’s allowance, 4430 |
| the token, the person | an expired token or a principal no longer valid closes with 4401 |
| the case | a case no longer assigned to the caller closes with 4404 |
| the request | the stored conversation and the newest message’s text alone |
| the model | the last 20 messages, the case’s latest 30 events as facts, five steps, 1,024 tokens a call |
| the tools | each call run as a use case for the representative: authorize, execute, audit |
| the last step | no tool may be called, so the turn ends in words |
| the names | the model writes [acc_…]; the screen writes each name from this reply’s results alone |
2. Explaining access
Explaining access answers “why can’t this person do that?” with the decision point’s own findings. The use case asks twice: whether the representative may read the account at all, and what the decision point would answer for the person and the operation. Only then does it decide what it may disclose (Figure 24).


Figure 24 — Explaining access, and what it discloses
Disclosure is the subtle part. A refusal is explained only to someone who holds account.read on the
account and was stopped by policies alone; anyone else receives a not-found that is byte for byte the
answer for an account that does not exist. A level-1 representative asking about an Enterprise account is
told which policy stopped them; someone with no right to read accounts learns nothing, not even that it
exists.
One finding comes with a remedy: where the account’s plan lacks a feature, the answer names the lowest plan on its ladder that includes it, and any add-on that does. The use case computes it through the product subsystem, not the decision point, and no code suggests a role. Every other refusal comes with its reason alone, so the representative can say why and the model cannot promise a way through that the system would refuse.
Before it can explain anything, the model has to find the account, and it never guesses one. It finds an account by its id, by part of its name, or by a person’s email; when a name matches two accounts — Acme Corp and Acme Labs — the assistant asks which one is meant rather than picking, and the answer it gives is about the account the representative chose.
2.1 Explaining access, step by step
2.1 Explaining access, step by step
Each step is the use case’s, and the model’s only part is the wording at the end.
| step | what happens |
| the question | the model calls explain_access with the person, the feature and the operation |
| the first ask | may the representative read the account |
| the second ask | may the person do the operation: principal & subject account, principal access, contact access, entitlement, policy |
| the disclosure | reasons and remedies, or a not-found identical to an unknown account’s |
| the answer | the model words the findings; the details list them as the result holds them |
3. Grant at once
A grant within every policy applies the moment the representative confirms it. The model prepares it as a change request, stored with its diff and its preview whatever the preview says; the card is drawn from what was stored; the click asks the decision point again, and Allow applies it (Figure 25).


Figure 25 — A grant applied at once
The preview is a courtesy, not a decision. What decides is the confirmation, made for the person who clicks and in the request’s own transaction: the decision, the grant, the account’s new version, the request’s status and every audit record commit together or not at all.
The diff is what the representative confirms. It lists what the change would add to or take from the account’s effective entitlements, computed as the grant’s preview from the same building block every read uses, and it is stored with the request; the card shows the stored diff, and applying it checks the diff’s tests still hold, so the change applied is the change the card showed.
3.1 A grant applied at once, step by step
3.1 A grant applied at once, step by step
The confirmation is a plain HTTP request from the card’s button, not a message to the model.
| step | what happens |
| the proposal | prepare_grant computes the preview and the diff and stores the request awaiting confirmation |
| the card | drawn from the stored request, read again after each click |
| the click | POST …/change-requests/{id}/confirmation decides grant.create for the representative |
| the decision | Allow |
| the application | the account’s version locked and checked, the grant created, the version advanced, Applied |
4. Grant by approval
A grant beyond 14 days needs a supervisor, and the system waits for one without holding anything open. The confirmation’s decision is Conditional: the approval, its deadline and the outbox’s message to start a workflow are written in one transaction. After the commit, the relay starts an approval workflow whose id is the approval’s; it waits for the decision until the deadline (Figure 26).


Figure 26 — A grant through approval
The supervisor’s approval is decided like any action: the supervisor must hold approval.decide, may
read the account, and must be neither the request’s requester nor its confirmer — the policy
no-self-approval. The approval and the outbox’s decision message commit together; after the commit,
the relay sends the workflow its event.
The workflow wakes and applies the change as the approval-process identity, which first decides its own right to apply a change request. Then it decides again for the person who confirmed, with the approval granted: if that person may no longer make the change, it is refused. Only then is it applied, the account’s version checked like any other. At the deadline with no decision, the approval and the request expire.
The supervisor decides with what the representative saw. The approvals panel shows each pending request: the change, who asked and their reason as written, and why it needs approval — the policies’ descriptions, not the model’s words. A denial needs a reason from a fixed list, and the case hears it.
The deadline is the workflow’s own timer, set to the time left until the approval’s expires_at, so a
workflow started late still ends on time. When it fires, the approval and its request expire — unless a
decision arrived in the meantime, in which case the decision is applied instead, so a supervisor who
approved in the last second is not overruled by the clock.
5. Sweep
Every 15 minutes a Cron runs the sweep, which settles what time alone changes. It runs as system identities, and its three stages are apart: one failing stops none of the others (Figure 27).


Figure 27 — The sweep, every 15 minutes
A grant past its end grants nothing from that moment, whether or not the sweep has come by; the sweep records the expiry so the row says what is already true. The other stages are the safety net under the outbox and the workflows: anything a relay missed, the sweep delivers again.
The outbox waits a minute before the sweep delivers a message again, so a relay still at work after a commit is not doubled by the sweep; and because a workflow’s id is its approval’s, a message delivered twice starts or wakes one workflow, never two.
5.1 The sweep, step by step
5.1 The sweep, step by step
Each stage takes a bounded batch, so one run is short whatever has piled up.
| stage | what it does |
| grant expiries | records each grant past its end as expired, as the grant-expiry identity |
| approvals | wakes up to 100 approvals pending past their deadline, and up to 100 approved and not applied |
| the outbox | delivers again each message undelivered for 60 seconds — 100 a run; one failed 50 times no more |
6. Case lifecycle
A case is the frame around everything else. It opens assigned to whoever opened it; its subject and contact are set by the representative’s clicks; its events form the timeline the agent reads as facts; it closes with a reason and a summary (Figure 28).


Figure 28 — A case from open to close
The model helps at two points and decides at none. It proposes the subject, which the representative sets; it drafts the summary, which the representative reviews. Closing cancels the requests still awaiting confirmation or approval, but an approval already given is applied after the close.
Authenticating the contact is a person’s judgement, recorded. The representative asks the customer’s security questions or checks the account’s details, and records the result; those two methods are the ones accepted. A change the customer asks for needs the contact’s authentication to be current — within 30 minutes — and the contact to hold a role in the account that lets them ask for it.
The case’s events reach the model as facts, not as instructions: they are written into the system’s own instructions with the case’s state line — the subject, the contact, whether the contact is authenticated — and the model is told that events are what happened, never what to do. That lowers the risk; what bounds it is that no tool can confirm, cancel or decide, and every effect needs a person’s click, so a customer’s words that did sway the model could still change nothing.
6.1 A case from open to close, step by step
6.1 A case from open to close, step by step
Every step but the two the model helps with is a click in the case panel.
| step | what happens |
| open | a case, assigned to its opener |
| the subject | the model’s propose_case_subject, the representative’s click; set once |
| the contact | the representative’s choice; setting it clears any authentication |
| the authentication | passed or failed; a pass counts for 30 minutes |
| the summary | the model’s draft_case_summary: text alone, an email address refused; closing refuses one too |
| close | Resolved or Abandoned, a summary of 1 to 4,000 characters |
7. Other use cases
A revocation runs as a grant applied at once does: prepared, confirmed, decided, applied, and the account’s entitlements back to its plan. An extension is prepared the same way, but counts with the grant it extends, so one that passes 14 days in all needs an approval like a long grant.
A denied approval ends the request refused, with the supervisor’s reason told to the case. A
supervisor approving a change they requested or confirmed is refused by no-self-approval, and another
supervisor must decide. A canceled request, from the card’s other button, changes nothing: the request stays, canceled, beside its record in the audit chain.
Limits that no approval lifts refuse a change outright. A grant adds at most 100 to a quota, counting the grants that overlap it, and lasts at most 90 days, counting the feature’s grants in the last 90 days, so splitting a large request into small ones does not avoid either. Such a card is refused as it stands, with every reason, and confirming it answers the same reasons.
A suspended account refuses every action but reading it, cancelling a request and handling its case: its entitlements read as nothing, the hold shown to those who may read it, and any grant on it is refused at the decision point’s first stage. An expired grant cannot be extended; the assistant says to prepare a new one. A contact whose own role does not allow what they ask for is refused, so a customer cannot obtain through support what their role denies them.
When the day’s allowance is spent, a chat turn is refused before the model is called, and the connection says why; the case, its panel, its cards and the approvals keep working, since none of them needs the model. When a session’s token expires mid-turn, the WebSocket closes, and the next request needs a valid session.
References
- The project’s API:
tech-docs/api/api.md. - Its feature specifications:
tech-docs/features/— explain access, case lifecycle, grants and approvals, usage guards, employee sign-in.