StrataDocs

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.

Note

This 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 with Agent creation has been disabled for your account by your administrator.
  • PATCH /api/agents/:id with a content change — editing the prompt, name, schedule, capabilities, model, notifications, etc. Same 403.

What the lock does not block:

  • Pause / ResumePATCH /api/agents/:id with { 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 / UnstarPATCH /api/agents/:id with { is_starred: true | false } only. Same exception.
  • Run nowPOST /api/agents/:id/run. Manual runs of existing agents are not "edits".
  • Webhook triggersPOST /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 generationPOST /api/agents/:id/webhook and DELETE /api/agents/:id/webhook are 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:

ToggleTools it controls
SQL Serverrun_sql
Outlooksearch_outlook, read_outlook_email, read_outlook_attachment, send_email
OneDrivesearch_onedrive, read_onedrive_file, save_to_onedrive
File generationgenerate_excel, generate_pdf
Analysisrun_python
Invoke other agentsinvoke_agent
Jirasearch_jira, write_jira
Confluencesearch_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_extensions is null. 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 modeallowed_agent_extensions is 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:

  1. Build the agent's permission set from agent.permissions (the wizard switches).
  2. If this is a dry run, strip every destructive tool (send_email, save_to_onedrive, run_python, write_jira, write_confluence).
  3. Look up the user's agent_extensions_override via getUserAgentControls(userId).
  4. 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.
  5. 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. If allowed_agent_extensions is 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