Per-user agent governance
Org-wide settings shape what every member can do; per-user governance lets an admin lock down a single user without changing their role or anyone else's experience. It covers two controls: blocking new agent creation, and masking which extensions that user's agents are allowed to call at runtime.
NoteThis is the most specific of three layers. The role-level Create + edit scheduled agents permission and the org-wide Agents kill-switch are set in Roles and permissions and Organization settings; the per-user controls below narrow a single member further. A user's effective capability is the intersection of all three.
The same user-management modal carries other per-user overrides on its Extensions & Data tab — which integrations the user can connect, their Analysis access, their Jira writes and Confluence writes settings, and which AI models they can use (unchecking models narrows that user below the organization's Model Catalog). Those controls apply to the user's chat experience as well as their agents; this page covers only the Agents tab.
Where it lives
Open Admin > Users, click the user to open their management modal, and switch to the Agents tab. It opens with two stacked sections:
- Agent permissions — a single Lock agent creation toggle.
- Allowed agent tools — eight per-extension checkboxes.
Below these, a Deployed agents table lists the user's agents (with a row-menu Delete action).
Lock agent creation
The toggle reads:
Lock agent creation — When on, this user can't create or edit agents. Their existing agents keep running on schedule — flip the agent's own pause toggle if you want to stop those too.
What the lock blocks:
POST /api/agents— creating a new agent. Returns 403 withAgent creation has been disabled for your account by your administrator.PATCH /api/agents/:idwith a content change — editing the prompt, name, schedule, capabilities, model, notifications, etc. Same 403.
What the lock does not block:
- Pause / Resume —
PATCH /api/agents/:idwith{ enabled: true | false }only. This is a documented exception so admins can lock a user and still pause their running agents through the same UI. - Star / Unstar —
PATCH /api/agents/:idwith{ is_starred: true | false }only. Same exception. - Run now —
POST /api/agents/:id/run. Manual runs of existing agents are not "edits". - Webhook triggers —
POST /api/agents/:id/webhook/trigger. Same reasoning. - Scheduled runs — the scheduler keeps firing existing agents on their schedule. The lock affects creation and content edits only, not execution.
- GET / DELETE / read-only paths — locked users can still browse their agents and delete them.
- Webhook generation —
POST /api/agents/:id/webhookandDELETE /api/agents/:id/webhookare not gated by the lock; they're per-agent admin actions for the agent's owner.
The lock is enforced in requireCreateAgents middleware on each request, so it takes effect on the next call after you flip it — no session refresh, no perm-cache lag.
Allowed agent tools
The eight per-extension checkboxes control which extension categories the user's agents can use at runtime. Each maps to a group of tools:
| Toggle | Tools it controls |
|---|---|
| SQL Server | run_sql |
| Outlook | search_outlook, read_outlook_email, read_outlook_attachment, send_email |
| OneDrive | search_onedrive, read_onedrive_file, save_to_onedrive |
| File generation | generate_excel, generate_pdf |
| Analysis | run_python |
| Invoke other agents | invoke_agent |
| Jira | search_jira, write_jira |
| Confluence | search_confluence, write_confluence |
Unchecking Jira strips both search_jira and write_jira from the user's agents even when the agent's own capability switches have them on. write_jira has to clear a second gate this mask can't open: whether Jira writes resolve on for the agent's owner — the organization's Let Strata create issues and comment toggle under Settings > Extensions > Atlassian > Manage, narrowed by the owner's role and by the Jira writes override on the Extensions & Data tab of this same modal. If any of those denies them, the issue-creation and comment tools are never handed to the agent at all, however the mask and the agent's own switches are set. Confluence behaves the same way for search_confluence and write_confluence, against the Let Strata create and edit pages toggle in the Confluence group of that same Atlassian page — the two products' write gates are independent, so unchecking one leaves the other untouched. See Who may write.
Those overrides are the narrowest lever for this: each one leaves the user's agents able to read that product and stops them writing to it, without touching their role or the organization.
Inherit mode vs. explicit mode
The control has two states:
- Inherit mode (default for new users) —
allowed_agent_extensionsisnull. The helper text reads Currently inheriting org defaults — flip any toggle to switch to an explicit list. Every checkbox shows as on, no Reset to org defaults button is visible, and every extension is allowed (subject to org-wide policy). - Explicit mode —
allowed_agent_extensionsis a strict array. The helper text reads Currently explicit — toggle a tool off to remove it from this user's agents. A Reset to org defaults button appears below the checkboxes.
Flipping any single toggle migrates the user from inherit to explicit. Once explicit, every flip writes the new array. To get a user fully back to inherit mode, use the Reset to org defaults button (below) rather than manually re-checking every box — re-checking all eight toggles leaves the user in explicit mode with all eight extensions listed, which is functionally "everything allowed" but is still an explicit array, not inherit.
The Reset to org defaults button explicitly sends { allowed_agent_extensions: null }, restoring inherit mode in one click. This is the reliable way to return to inherit.
The PATCH is debounced 300ms so toggling several extensions in quick succession only fires one request.
Runtime enforcement
The mask is enforced by selectAvailableTools in agentScheduler.js, which runs at the start of every agent execution. The flow:
- Build the agent's permission set from
agent.permissions(the wizard switches). - If this is a dry run, strip every destructive tool (
send_email,save_to_onedrive,run_python,write_jira,write_confluence). - Look up the user's
agent_extensions_overrideviagetUserAgentControls(userId). - Resolve the org-level effective extensions for the user (
_resolveEffectiveExtensions— the user's role allowlist, narrowed by the org allowlist when the org locks extensions, minus any per-user extension toggles the admin disabled) and fold that in too — an extension the org has since disabled is dropped even if the per-user mask still lists it. When the org imposes no list, this step is a no-op and the per-user mask stands alone. - If the user has an explicit array, intersect: drop every tool whose extension category isn't in the combined allowed list.
The intersection is template permissions ∩ org / role extensions ∩ user mask. A template might allow send_email; if the user mask doesn't include outlook — or the org has removed outlook from its allowlist — send_email is dropped at runtime. The agent simply runs with that tool absent from its toolset. There's no "you can't have this" error to the user; the tool just isn't there.
Strata also appends an ADMIN RESTRICTION: line to the agent's system prompt when a mask is active, so the AI sees the restriction up front and gracefully aborts with STRATA_STATUS: FAILED if the task requires a tool that's not in the allowed set, rather than thrashing on tool calls that have been stripped.
Existing runs are not interrupted
The lock takes effect on the next create / edit attempt — it never touches execution, so existing agents keep running on schedule. The mask takes effect on the next run (manual, scheduled, webhook, or invoke) — not on any run currently executing. executeAgentRun reads getUserAgentControls(userId) once at the start of the run and uses that snapshot for the duration. If you tighten the mask while a run is in flight, that run finishes with the old rules; the next run picks up the new rules.
Similarly, the org-wide kill switch (lock_agents) is checked in requireCreateAgents (the POST/PATCH gate). It blocks creating and editing agents org-wide, but — like the per-user lock — it does not stop already-scheduled agents from firing; the scheduler's due-agent query does not filter on lock_agents. In-flight executions complete normally.
Cross-org isolation
The admin endpoints (GET /api/admin/users/:userId/agent-controls, PATCH /api/admin/users/:userId/agent-controls) verify that the target user belongs to the admin's own org before honoring the request. A foreign user ID returns 404 ("Not found") on both verbs — the guard deliberately doesn't reveal whether the UUID exists in another org — and the foreign user's controls are not mutated. This is enforced via the _assertUserInOrg guard in adminService.js.
Audit considerations
Per-user agent control changes are written to the platform DB via setUserAgentControls and emit an audit row with the action user.agent_controls_changed. The audit details capture the full before and after state along with the request patch, so reviewers can see exactly which toggle moved without reconstructing prior state. The row is visible under Organization settings -> Audit filtered by that action and is included in audit exports.
What can go wrong
- A user reports "Agent creation has been disabled for your account by your administrator." — the per-user lock toggle is on for that user. If this is unexpected, open their Users modal > Agents tab and toggle Lock agent creation off.
- A locked user can't pause their own agent — historically this was a known bug (the lock blocked all PATCHes including the pause-only ones). The current build allows pause-only PATCHes (just
{enabled}and/or{is_starred}) through the lock. If you see a 403 on pause specifically, verify you're on a current build and that the PATCH body contains no other fields. - A locked user's existing agent is still running on schedule — by design. The lock blocks creation and edits, not execution. To stop the run, open the agent's row and pause it (allowed even while locked) or delete it.
- Toggling an extension off didn't take effect on the next run — confirm by hitting
GET /api/admin/users/:userId/agent-controls. Ifallowed_agent_extensionsis the expected array, the next run will use it. If a run is currently in flight, the change applies to runs after that one finishes. Unknown extension id: <id>— your PATCH body included an extension id that isn't in the canonical set (sql_server,outlook,onedrive,files,python,invoke_agent,jira,confluence,web). The UI renders a checkbox for the first eight;web(real-time web search) is part of the canonical set and is honored at runtime, but it doesn't yet have its own toggle in the modal. The UI never produces this error; if you see it, you're driving the API directly with a stale or mistyped value.- Cross-org admin tried to fetch another org's user controls — returns 404 ("Not found") on both GET and PATCH. This is the expected behavior of
_assertUserInOrg: it returns a not-found rather than a forbidden so it never reveals whether that UUID exists in another org. The write is rejected either way. - You unchecked all eight extensions but expected the user to inherit — explicit empty array
[]is a legitimate state meaning "block every tool". The UI does not currently surface this distinction with a dedicated "block all" label. If you wanted inherit, click Reset to org defaults instead of unchecking all eight.
Related
- /docs/agents/permissions — per-agent capabilities and the dry-run sandbox.
- /docs/agents/templates — org templates whose permissions get intersected with the user mask at runtime.
- /docs/admin/users — the user management modal where the Agents and Extensions & Data tabs live.
- /docs/chat/models — the Model Catalog and per-user AI model access set on the Extensions & Data tab.
- /docs/admin/rate-limits — org-wide agent caps that apply alongside per-user governance.