Admin → Agent → Users: the people who hold the agent permission, each with
the model they use, their limits, what they spent today and this month and
whether a limit being hit is reported — and a form to set one user's
settings (the switch, the model, the admin's prompt, the limits, the notice
flag; PhoenixKitAgent.UserConfig.admin_update/4).
Needs agent.manage. The tab is registered under that key; the page checks
it again on mount and on every event that opens or saves a form, against the
socket's scope and the scope of the session as it is now, narrowed to the
role the session acts as (PhoenixKitAgent.Web.Access), and the context
function checks it a third time against that session scope. An Owner's
settings are opened and saved by an Owner only. The user being edited is the
one the server opened the form for — a user in a submit is never read —
and only the fields the form offers are taken from it, so a forged submit
cannot reach the lock columns, the user's own instructions or the notice
list. A model that the form did not offer (one that cannot chat, or does not
exist) is refused, and so is a newly chosen one that does not fit the
agent.chat AI task (PhoenixKitAI.Tasks.check_selection/2): known not
to take tools, or not known to. A save after someone else changed the same user's
settings since the form opened is refused (expected: of
UserConfig.admin_update/4), not written over their change.
A user whose model does not report what a call costs
(PhoenixKitAgent.Models.reports_cost?/2) is marked: money is not counted
for them and only the turn limit applies — in the list, and in the form for
the model chosen there.
Under the open form, what the user's assistant can use now
(PhoenixKitAgent.Web.ToolAvailability): the modules' read and write tools
by every role the user holds, which the role of a session can narrow; read
when the form opens.
Who changed what for whom goes to the core Activity log, field names only.
The model list marks the endpoints whose model is known not to take tools, and
those where that is unknown (PhoenixKitAgent.Models); that listing is
fetched off the page process after the form opens. The page keeps endpoints
as PhoenixKitAgent.Models.summary/1 — never the endpoint row with its key.
A user blocked from the AI guard's free_text group (the assistant and
the AI Playground, PhoenixKitAI.GuardBlocks) is marked, with the
block's reason only for a viewer who may see it (GuardBlocks.active/3
with the session's scope: ai.manage or an Owner). "Lift" calls
PhoenixKitAI.Guard.unblock/3 with that scope for the block the server
finds for the row's user, after agent.manage is checked again; the
guard refuses one's own block and an Owner's (unless the viewer is an
Owner), and the button is shown only where it would not refuse.