Technical docs
Staff
Overview
Support reference for the Staff area:
- `/staff`
- `/staff/ai/[employee_id]`
- `/staff/user/[user_id]`
What users see
- Main tabs: `Staff` and `Invites`.
- Trash view is reached from `View Deleted` and is labeled `Pending Deletion`.
- The invite dialog also opens from `?invite=true`.
- Primary actions on the active Staff view:
- `Invite Member`
- `Add AI Teammate`
- Empty state buttons:
- `Invite Member`
- `Add AI`
Staff tab
- Header title: `Staff Directory`.
- Description: `Manage your team members and AI Teammates in one place`.
- Current table filters:
- `Type`: `Team Member`, `AI Teammate`
- `Status`: `Active`, `Paused`, `Inactive`
- `Added`
- `Updated` or `Deleted` in trash view
- `Role`
- `Workflow`
- `Access Groups`, including `No Access Group`
- The active Staff view initially includes both `Active` and `Paused` statuses.
- Current table columns can include:
- `Access Groups` for human members and AI teammates
- `LLM` for AI teammates
- The `LLM` cell shows the resolved model display name when Alloy has one, otherwise the raw model id.
- Current behavior:
- The staff API currently returns the full authorized list without server pagination, server filtering, or server search.
- Owners and admins see all regular AI teammates. Members see the union of regular AI teammates granted through their active Access Groups, plus their own personal Ally.
- Selecting several Access Group filters uses OR semantics; `No Access Group` also matches rows whose Access Group list is empty.
- Search, pagination, date filters, role filters, and workflow filters are handled in the frontend.
- Human rows come from accepted organization memberships, not from pending invites.
- Trash view is AI-teammates only.
- Staff delete actions (single or bulk, AI teammate or human member) show an undo toast while the removed row is restorable. The toast countdown pauses while the toast is expanded, while the pointer is interacting with it, or while the document tab is hidden, so the remaining undo time tracks the toast's own pause behavior.
- Per-user Ally rows show a `Personal` badge in the name cell.
- An otherwise-active AI teammate whose daily spending limit is reached has effective API and Staff status `paused`; this is computed and is not a persisted lifecycle state.
- Paused teammates show an amber bot badge and amber `Paused` status. The interactive hover card shows the USD amount used, the configured limit, `Its work is paused until the daily limit resets.`, and a `Resets in {duration}` countdown.
- Organization owners and admins also see `Manage daily cost limit`, linking to the organization spending-limit card. At the reset time, the UI removes the stale paused indicator locally and refreshes Staff data.
- On the AI teammate detail page, the same warning appears by the teammate identity and as a read-only `Status: Paused` field after Tools. Inactive or below-limit teammates do not get that field.
- A workflow run aborted because an AI teammate reached its daily cost limit is not treated as an operator error: the failed assistant message (the temporary-unavailability reply) is still published, but no Sentry error capture or system error alert is raised for it.
- Row actions:
- AI teammates: `Chat`
- All rows: click-through to the detail page
- Human teammates: no inline action button in the list
- Bulk actions:
- Active view: `Delete`
- Trash view: `Restore`
- Selection rules:
- active-view human owners cannot be bulk-selected
- active-view system AI teammates cannot be bulk-selected
- trash-view selection is AI-only
Invites tab
- Header title: `Pending Invites`.
- Description: `Track and manage pending organization invitations`.
- The invite dialog supports two delivery methods:
- send by email
- generate a shareable invite link
- Both delivery methods create the same backend invite record through `POST /api/organizations/{orgId}/invites/`.
- The dialog's `Administrator` option maps to backend invite role `admin`.
- The free-text `Position` field is currently UI-only and is not persisted by the backend invite model.
- Current filters:
- `Status`: `Pending`, `Accepted`, `Cancelled`, `Expired`
- `Role`: `Admin`, `Member`
- `Added`
- `Updated`
- Row actions:
- pending email invites: `Resend`, `Cancel`, `Copy invite link`
- pending link invites: `Cancel`, `Copy invite link`
- accepted, cancelled, and expired rows are read-only
- Invites use server pagination, while filtering remains in the frontend.
- Invite status is returned by the backend. The `Expires In` display starts from backend `remaining_seconds`; the frontend does not reclassify pending invites by comparing `expires_at` with the browser clock.
AI teammate detail
- Create route: `/staff/ai/new`.
- Create mode initially exposes only the `Rules` tab; the full tab set appears after the teammate is created.
- Tabs:
- `Rules`
- `Workflows`
- `Storage`
- `Connectors`
- `PATs`
- `Telegram`
- `MS Teams`
- `Slack`
- `Logs`
- `Reports`
- `Jobs`
- `Schedulers`
- `API` only when channel is `API`
- When the tab strip is too narrow, hidden tabs move into the `Tabs` overflow menu. The overflow menu can select hidden tabs, change which tabs stay visible, and reorder tabs through `Reorder {name}` drag handles. Tab tooltips use the current tab descriptions, including `Review activity and execution history` for Logs, `Review performance and usage reports` for Reports, and `Schedule recurring and one-off runs` for Schedulers.
- Previously opened tab panels can remain mounted to preserve their UI state, but hidden `Workflows`, `Connectors`, and `PATs` panels pause their list requests while another tab is active.
- The hidden Workflows panel also releases its workflow realtime subscriptions and refreshes cached workflow data when it becomes active again. Create mode does not start the teammate-scoped Workflows request before the AI teammate has been created.
- Hidden Connectors panels pause organization/employee connector, OAuth-definition, integration, and connected-user lookups until the tab is active again.
- Ally is a special case:
- their detail page hides `Storage`
- their detail page hides `API`
- a personal Ally is auto-created for each user the first time the employee list or staff list is accessed, so every user always has an Ally without manual setup
- the database permits at most one live personal Ally for each user in each organization
- migration cleanup keeps the oldest live personal Ally when duplicates already exist and soft-deletes the other duplicate rows
- the current user's personal Ally is available in shared internal-chat target strips and history
- personal Ally employees remain excluded from the Docs workspace teammate selector, `@` mention catalogs, workflow builder selectors, Staff table teammate choices, and system settings dropdowns for global Ally and Onboarding teammate selection
- the Personal Ally `Slack` tab does not accept dedicated bot credentials; it shows `Connected`, `Reconnect required`, or `Not connected` for the organization-wide Slack app
- the Personal Ally `Slack` tab links to `Open Organization Settings`, where authorized users manage the shared workspace connection
- each linked Slack user is routed to that user's own Personal Ally; an external Slack identity must be linked to an Alloy organization user before an AI run starts
- The profile header includes an `Access Groups` selector for regular AI teammates. Owners and admins can edit grants; member views are read-only. Personal and other system AI teammates are not grantable resources.
- Channel choices in the header:
- `Not selected`
- `Web Chat`
- `API`
- `Web Chat` shows a copy action for the chat widget snippet in the header.
- `API` uses a dedicated tab with:
- `API Endpoint`
- request examples and built-in request tester
- API request log filtered from workflow runs
- Jobs and schedulers are documented in jobs-and-schedulers.md. The Schedulers tab uses a mixed recurring/one-off list, dashboard metrics, and create cards for `Schedule recurring` and `One-off run`.
- Connectors tab lets users enable or disable org MCP servers for that teammate. The tab description is `Connected systems and services`.
- The same Connectors tab also manages OAuth service connections for that specific teammate.
- For connected OAuth services, the tab can show:
- status badge `Valid`, `Invalid`, or `Unknown`
- `Connected by {user}`
- connected account identifier when available
- OAuth connector checkboxes stay disabled until the matching service is connected for that teammate.
- PATs tab lets users create tokens that let external AI agents act as that teammate.
- Telegram tab lets users:
- connect a Telegram bot by pasting a BotFather token
- disconnect the linked bot
- reconnect the webhook when the bot is linked but webhook delivery is not active
- Telegram tab states:
- unlinked: `No Telegram bot is linked to this AI Teammate yet. Connect a bot to receive messages from Telegram.`
- linked + active webhook: `Connected` badge plus webhook success notice
- linked + missing webhook: warning notice plus `Reconnect webhook`
- Generated Telegram replies use rich Markdown when accepted by Telegram. Oversized or invalid-Markdown replies fall back to plain `4096`-character chunks.
- MS Teams tab lets users:
- connect a Microsoft Teams bot by entering Azure Bot app credentials
- disconnect the linked Teams bot
- copy the current `Messaging endpoint (set this in your Azure Bot)` value
- download the sideloadable Teams app package as `.zip`
- Full setup procedure: `tech docs/references/microsoft-teams-ai-teammate.md`.
- MS Teams tab always shows the current messaging endpoint when the backend provides one, even before the bot is connected.
- The MS Teams tab shows one `Microsoft Teams chats` list covering personal chats, channels, and groups that the bot has seen.
- Chat rows currently show:
- the resolved chat name, with a raw external chat ID fallback
- status badge `Enabled` or `Disabled`
- a toggle for enabling or disabling AI replies in that chat
- Newly discovered Teams chats are created disabled by default and only appear after the bot has seen a message there.
- For non-personal AI teammates, the Slack tab lets users:
- connect a dedicated Slack bot by entering Bot User OAuth Token and Signing Secret
- disconnect the linked bot
- copy a generated Slack app manifest JSON for quick app setup
- copy the `Events Request URL` from the `Troubleshooting` accordion when Slack URL verification needs checking
- Full setup procedure for a dedicated AI teammate Slack app: `tech docs/references/slack-ai-teammate.md`.
- Dedicated Slack tab states:
- unlinked: `No Slack bot is linked to this AI Teammate yet. Connect a dedicated Slack app to receive messages from Slack.`
- linked: `Connected` badge with bot app name and bot user ID
- Dedicated Slack setup starts from a centered empty state with `Connect`; opening it reveals the numbered manifest setup guide.
- The manifest is generated by the backend and pre-fills the Slack app name, required scopes, message events, App Home direct-message settings, and Events Request URL.
- If the Events Request URL is unavailable, `Copy manifest` is disabled and the tab explains that backend Slack webhook configuration must be completed first.
- Connected dedicated apps expose `Check permissions`. Missing scopes appear in a `Slack app permissions need attention` alert with instructions to copy the updated manifest, reinstall the app, and check again.
- The dedicated Slack tab shows a `ChannelChatsList` for managing per-channel enable/disable state, same pattern as Telegram and MS Teams tabs.
- Slack setup info responses now include `settings_path` (`/staff/ai/{employeeId}?tab=slack`), `messaging_endpoint_method` (`POST`), and `messaging_endpoint_usage` (`slack_events_request_url`) alongside the previous fields.
- Channel chat status updates (MS Teams, Slack, Telegram) scope lookups to the specific employee ID and channel type, preventing cross-employee or cross-channel chat ID collisions. Employee ID and chat ID in status routes are now validated as UUIDs.
- Slack DMs show a typing indicator while the AI teammate is processing. The indicator uses the triggering message timestamp as the thread identifier for correct Slack rendering.
- Slack app setup details live in the Slack reference instead of this Staff-area overview.
- Channel linking allows organization users to connect their external channel accounts (starting with Slack) to their Alloy user identity. The link page is at `/organizations/{orgId}/channels/{channel}/link/` and generates a temporary `LINK-CODE-XXXX-XXXX` code that the user enters in the external channel to complete the link. Codes last 15 minutes, and the page countdown starts from backend `remaining_seconds`. The linked contact channel profile (name, avatar) is synced on connection. The AI prompt context includes `external_id` from linked contact channels so AI teammates can reference the external identifier.
- Storage tab shows only the folders explicitly shared with that teammate; it no longer expands every file inside those folders into the table.
- The Storage table is centered on access-management metadata: shared folder name, location, current teammate access, grantor, and grant date.
- Storage table filters include created, modified, granted, team, and grantor.
- Folder row actions are `Open`, `Share`, and `Download`.
- `Download` retrieves the shared folder as the server-generated `{folder name}.zip`; a download failure reports `Download failed`.
- The table loads permissions through the batched employee storage access endpoint. Each shared folder row shows the effective read or write access and whether it is inherited from a parent share.
- The empty state for AI teammate Storage still links back to `/storage`.
- Existing AI teammates can use `#` file and folder references in the Rules instruction editor. The catalog searches only storage entries readable by that teammate; create mode has no teammate storage scope yet.
- Employee-backed search/list endpoints filter per-user Ally records so a signed-in user only sees their own Ally.
- Updating a per-user Ally keeps the name fixed as `Ally`; editable fields are the assistant settings such as description, role, instructions, model, tools, and voice configuration.
- The AI teammate model picker includes a `Default` option at the top. Selecting it clears the per-teammate model, so runtime resolution falls back to the organization default model and then the system default model. The header displays `Default` whenever no per-teammate model is set.
- The AI teammate profile header includes a `Sandbox:` switch. Enabling it sets `is_sandbox_enabled` (default `false`) and lets the teammate run code in a disposable Linux VM billed for the time it stays up; the tooltip advises leaving it off unless the teammate needs it. The flag is part of the AI teammate create/update payloads and detail responses.
- Non-system AI teammate profile headers also include a `Show thinking:` switch backed by `is_webchat_thinking_enabled` (default `true`). Its tooltip says: `Controls whether the webchat shows the AI agent's reasoning and tool activity. When disabled, this information is not sent to visitors.` The switch saves immediately through the normal teammate update path and is hidden for system AI teammates.
- Disabling `Show thinking:` affects webchat only: answer text still streams, but reasoning and tool-activity stream events are not sent and persisted webchat message reads omit `stream_reasoning` and `stream_tools`.
- The AI teammate profile header filters tool badges to tools the organization currently offers: stale tool ids (for example from a disconnected MCP server or deleted workflow) are swept out of update payloads, and each tool badge shows a spinner while its removal or toggle request is in flight.
- In the AI teammate model picker, Codex models are disabled when `GET /api/init/models` returns `is_connected: false` for them. Disabled Codex models show a lock icon with accessibility label `Codex is not connected`.
- When a provider group contains unavailable Codex models, org admins see a `Connect` link to `/org-settings/api-keys`; non-admin users see `Admin required`.
- System AI teammates cannot be deleted from the active Staff list, bulk delete, or the AI teammate detail page.
- AI teammate create and update paths now snapshot teammate state into the shared backend `entity_versions` store.
- The stored entity type for AI teammate versions is `ai_employee`.
- Restore support is confirmed in backend code through the shared entity-version restore route.
- This doc does not claim a dedicated AI teammate version-history UI because that was not confirmed in the current frontend source.
Human teammate detail
- Back action: `Back`.
- The route reads accepted organization users via `GET /api/organizations/{orgId}/users/{user_id}`.
- Pending invitations stay in the `Invites` tab and do not become human Staff rows.
- Overflow menu includes:
- `Open in new tab`
- `Remove` for non-owner rows only
- The page shows email, phone, location, joined date, and invited date when available.
- The profile shows the user's `Access Groups`. Owners and admins can edit memberships for active users whose organization role is `member`; other views are read-only.
API and realtime
- Staff list:
- `GET /api/organizations/{orgId}/staff`
- `GET /api/organizations/{orgId}/staff/deleted`
- Humans:
- `GET /api/organizations/{orgId}/users`
- `GET /api/organizations/{orgId}/users/{user_id}`
- `PUT /api/organizations/{orgId}/users/{user_id}/role`
- `DELETE /api/organizations/{orgId}/users/{user_id}`
- `POST /api/organizations/{orgId}/users/bulk-delete`
- `POST /api/organizations/{orgId}/users/bulk-restore`
- Invites:
- `POST /api/organizations/{orgId}/invites/`
- `GET /api/organizations/{orgId}/invites/`
- `DELETE /api/organizations/{orgId}/invites/{invite_id}/`
- `POST /api/organizations/{orgId}/invites/{invite_id}/resend`
- public acceptance flow: `/api/invites/{code}` and `/api/invites/{code}/accept`
- AI teammates:
- `GET /api/organizations/{orgId}/employees/`
- `POST /api/organizations/{orgId}/employees/`
- `GET /api/organizations/{orgId}/employees/{employee_id}`
- `PUT /api/organizations/{orgId}/employees/{employee_id}`
- `DELETE /api/organizations/{orgId}/employees/{employee_id}`
- `POST /api/organizations/{orgId}/employees/bulk-delete`
- `POST /api/organizations/{orgId}/employees/bulk-restore`
- `GET /api/organizations/{orgId}/employees/{employee_id}/external-urls`
- `GET /api/organizations/{orgId}/employees/{employee_id}/telegram`
- `POST /api/organizations/{orgId}/employees/{employee_id}/telegram`
- `DELETE /api/organizations/{orgId}/employees/{employee_id}/telegram`
- `PUT /api/organizations/{orgId}/employees/{employee_id}/telegram/webhook`
- `GET /api/organizations/{orgId}/employees/{employee_id}/msteams`
- `POST /api/organizations/{orgId}/employees/{employee_id}/msteams`
- `DELETE /api/organizations/{orgId}/employees/{employee_id}/msteams`
- `GET /api/organizations/{orgId}/employees/{employee_id}/msteams/chats`
- `PUT /api/organizations/{orgId}/employees/{employee_id}/msteams/chats/{chat_id}/status`
- `GET /api/organizations/{orgId}/employees/{employee_id}/msteams/package`
- `GET /api/organizations/{orgId}/employees/{employee_id}/slack/manifest`
- `GET /api/organizations/{orgId}/entity-versions/ai_employee/{employee_id}?limit=<n>&before_version=<n>`
- `POST /api/organizations/{orgId}/entity-versions/{entity_version_id}/restore`
- Access Groups and audit events:
- See `access-groups.md` for group, membership, grant, audit-event, and authorization routes.
- Organization daily spending limit:
- `PUT /api/organizations/{orgId}/ai-employee-daily-cost-limit`
- AI teammate list/detail payloads include `is_daily_cost_limit_reached` plus `daily_cost_usage.used_usd`, `limit_usd`, and `resets_at`
- Logs and MCPs:
- `GET /api/organizations/{orgId}/workflows/runs/`
- `GET /api/organizations/{orgId}/mcps`
- `GET /api/organizations/{orgId}/mcps?employee_id={employee_id}`
- `POST /api/organizations/{orgId}/mcps/{mcp_id}/employee/{employee_id}`
- `DELETE /api/organizations/{orgId}/mcps/{mcp_id}/employee/{employee_id}`
- Jobs and schedulers APIs are documented in jobs-and-schedulers.md.
- WebSocket events used by Staff/Invites:
- `organization.staff_user_added`
- `organization.staff_user_removed`
- `ai_employee.created`
- `ai_employee.updated`
- `ai_employee.deleted`
- `staff_employee.created`
- `staff_employee.updated`
- `staff_employee.deleted`
- `organization_invite.created`
- `organization_invite.updated`
- `ai_employee_ms_teams_channel.created`
- `ai_employee_ms_teams_channel.updated`
- `access_group.created`
- `access_group.renamed`
- `access_group.deleted`
- `access_group.restored`
- `access_group.member_added`
- `access_group.member_removed`
- `access_group.resource_added`
- `access_group.resource_removed`
Permission notes
- Staff and invite actions are organization-scoped and authorized by backend checks.
- AI teammate reads and updates are resource-scoped: owners and admins can access all regular AI teammates, while members can access only regular AI teammates granted through their active Access Groups. A user can also access their own personal Ally.
- Only owners and admins can create or delete AI teammates.
- Access Group management is owner-or-admin only. Members can read only their currently assigned groups through the available-groups scope.
- Human removal and human bulk delete/restore are admin-or-owner only on the backend.
- Human role changes are owner-only on the backend.
- The current trash UI only surfaces deleted AI teammates even though backend user restore endpoints exist.