FeaturesAgents

Permissions

The tools an agent can use, human approval, and the people who administer an agent and its credential bindings

An agent's permissions have three layers: the tools and permission mode set by the profile, the permission requests that a person answers, and the people who administer the agent and connect credential names.

Tools: set by the profile

A session uses only the tools in the profile's tools. The built-in tools the profile blocks (deny) are not in tools. Tools outside it are denied without a permission request, and the model receives an error tool_result. What a profile blocks is in Choose a profile. The script below calls WebFetch, which the local profile blocks (the local profile's deny is ["NotebookEdit","WebFetch","WebSearch"]).

created=$(curl -sS "$API/v1/sessions" \
  -H "Authorization: Bearer $KEY" \
  -H 'Content-Type: application/json' \
  -H "Idempotency-Key: $(uuidgen)" \
  -d "$(jq -nc --arg m 'GATE-SPEC {"id":"outside","steps":[{"tool":"WebFetch","input":{"url":"https://example.com","prompt":"summarize"}}],"final":"done"}' \
    '{profile_id: "claude-coding-local", repository_id: "sample-app", message: $m}')")
SID=$(echo "$created" | jq -r .session_id)
until [ "$(curl -sS "$API/v1/sessions/$SID/turns/1" -H "Authorization: Bearer $KEY" | jq -r .status)" = completed ]; do
  sleep 2
done
curl -sSN --max-time 5 "$API/v1/sessions/$SID/events" -H "Authorization: Bearer $KEY" \
  | awk '/^event: tool_result/ { getline; sub(/^data: /, ""); print }' \
  | jq -c '.data | {is_error, content}' || true
curl: (28) Operation timed out after 5006 milliseconds with 4482 bytes received
{"is_error":true,"content":"<tool_use_error>Error: No such tool available: WebFetch. WebFetch is disabled for this session, in subagents as well as here.</tool_use_error>"}

curl: (28) is --max-time cutting the stream.

A session created from a release is narrower still: it uses only the tools pinned to that release. That list is the card's capabilities.tools (every tool the profile does not block at release time when null) minus forbidden, and the release response's effective_tools.tools shows it. If the card lists only Read, the session uses only Read, and other built-in tools are denied as above. A session created without a release uses all of the profile's tools.

Two places block tools, with different reach.

Profile denyCard capabilities.forbidden
Who sets itThe owner for an operator profile, owners and admins for a workspace profileThe agent's admins
Where it appliesEvery session and release on that profile versionSessions created from that agent's releases
Names it takesBuilt-in tool names onlyTool names
A change takes effectIn a new profile version, from the releases and sessions made on itFrom a new release

A card cannot open a tool its profile blocks. Naming one in the card's capabilities.tools is refused when the release is made.

Previously the card's tools and forbidden were pinned to the release but not applied to execution. Now even a session already running on a release created before that is narrowed to the release's tools. This applies when the session's execution environment starts again (the next message after the environment has stopped following 30 minutes without input since the last turn ended, or a resume after a pause). The built-in tool values in the card's tool_policy (allow, ask, deny) are pinned to the release but are not applied to execution yet. Whether a tool asks before it runs is set by the permission mode below.

In a card's tool_policy, the default for built-in tools is allow and the default for MCP tools is ask. What makes a built-in tool ask a person before it runs is the permission mode, not this value. To keep a built-in tool from running at all, block it with the profile's deny or the card's forbidden.

Permission mode

The profile's permission_mode decides whether a person is asked before a tool runs. It appears in runtime.permission_mode of the session detail.

ModeBehavior
defaultFile writes and edits inside the session workspace, every git and gh command, sed, mkdir inside the workspace, and bun, npm, pnpm and yarn test and run run without asking. A command joined with &&, ; or | passes only when every part is in that range; one part outside makes the whole command a permission request. Writes outside the workspace (judged on the path after symlinks and .. are resolved), secret, .git and .claude paths, sed -i, git --output and --ext-diff (which write a file or run a program), git -C at another repository, git add -f, gh auth, and other network tools such as curl create a permission request before running. git and gh do not ask, whatever the options: what stops harm is not a permission request but the workspace container isolation, the egress authorizer (push target, token scope) and GitHub branch protection. Reads that change nothing (ls, cat, grep, find and the like) run without asking inside the workspace, by the engine's own rules. The engine asks for a path outside the workspace and for find -exec or -delete; this list asks when a secret file name (.env, a key, .git/config) is on a cat, head, tail, grep or rg line, and for rg -u or --hidden, which search hidden files. It matches the name only, so cat .e* and grep -r get past it. This is the local profile's mode
acceptEdits, dontAskRuns in the Claude Code mode of the same name, with the same workspace pre-approvals and ask list as default. Secret, .git and .claude paths on that list ask in acceptEdits too
planRuns in the Claude Code mode of the same name. Nothing is pre-approved
autoA model classifier allows or denies each call in place of a person, and denies it when the classifier cannot be reached. Needs no installation setting. One exception: the built-in GitHub tools for pushing, writing pull requests and reading repositories skip the classifier, and the server decides them (push approval, the release's tool list, the session grant). Reviews and workflows still go through the classifier. The platform adds rules by kind of action. The push, pull request and repository-read items in the allowance below are what the classifier sees only on an older worker that does not get this exception. Allowed: committing in the session's workspace and pushing through the platform's push flow to the session's own work branch (agent/<session-id>) in the session's own repository, opening a pull request from that branch and editing the title and body of, commenting on, closing or reopening a pull request the session opened, and listing granted repositories and attaching one to read it. Outside that allowance: pushing to the default branch, a protected branch or a branch the platform did not create, force pushes, merging or approving pull requests, submitting a review, editing, commenting on or closing a pull request the session did not open, pushing to any other repository, including one attached to the session, and secrets. Changing repository settings, branch protection, webhooks, deploy keys or Actions secrets, deleting branches or repositories on GitHub, and merging pull requests lean toward denial by any route
bypassPermissionsRuns every call with nothing judging it. A profile with this mode is left out unless the API has PLATFORM_ALLOW_UNATTENDED_PERMISSIONS=true, and only an owner or admin can pick it for a workspace profile

The mode exists only in the catalog and cannot be changed by a session request. A profile made in the console starts on auto when its base allows it. A profile already saved as default is not moved automatically. On a current worker, the push and pull request tools do not ask in any mode except plan, where the engine still asks as before. To stop the agent's engine tools such as Bash from asking, make an auto version and have the agent's next release use it. For an operator profile, the owner saves the permission mode as auto under New version on its detail page. A workspace profile cannot pick a mode wider than its base: if the base allows auto, save auto through Edit; if the base is default, make a new workspace profile over a base that allows auto (such as the auto operator profile version above). Sessions already running keep their mode. Push approval is separate from the mode: with push approval on, or the attached-repository push policy on ask, even an auto agent asks a person before every push to any repository. The agent's GitHub tab starts its first save with push approval off, but a release with no GitHub settings (such as one made before this setting existed) reads it as on. To push without approval, check on the GitHub tab that Approve before push is off and save. Even then a push goes without approval only if the attached-repository push policy (allow, ask or deny) is also allow. A deny policy blocks the push outright; otherwise a push to any repository follows the stricter of this switch and that policy. To have a person approve pushes, turn the switch on or set the policy to ask.

Approval: only a person does it

Answering permission requests and questions needs the sessions:approve scope. The credential held by the agent inside the worker gets 401 on /v1, so it cannot approve for itself. An agent key can have sessions:approve, but it never approves requests of a session it started. How to answer is described in Waiting for approval and resuming.

Seeing a session does not mean answering for it. Only these people answer its permission requests and questions and approve its pushes and pull requests:

  • the person who created the session (for a session an API key created, that key)
  • the admins of the session's agent
  • the workspace's owners and admins

Someone who only sees the session through its share gets 403 FORBIDDEN (reason_code: role) when they answer; the request stays open and the session stays needs_input. A session they cannot see is still 404. answerable in GET /v1/sessions/{id}/pending-requests says whether the caller may answer, and when it is false the console shows allow and deny disabled, with the reason.

Agent administrators

The person who creates an agent becomes that agent's administrator. An administrator, even if only a member, can edit the agent's card, create releases, activate, connect credentials, assign administrators, and issue agent keys. You can add a person or a team as an administrator, and for a team, every member of that team is an administrator. Only an owner or admin, who manage teams, can newly add a team. An agent administrator who is a member cannot add a team, but can still remove one and add or remove people.

Every agent has one responsible team, which you can change but not clear. The responsible team's leads (lead) administer the agent; its members (member) do not. Unless you are an owner or admin, you can choose only a team you belong to. A team that is responsible for an agent cannot be deleted until that agent is archived or moved to another team.

Team roles show on the console's team screen. The member table's Team role column tells a Team lead (lead) from a Member (member), and Your teams shows your own role. Owners and admins pick a role with Change role on a member's row and Save it (PATCH /v1/teams/{team_id}/members/{user_id} {"role"}). Deleting a team that is responsible for agents leaves the team in place, and the console names those agents with links (409 TEAM_MANAGES_AGENTS). Give each of them another responsible team first.

Who sees what of an agent:

WhoInformation (name, slug, description, responsible team, status, active release)Reading settings (card, releases, administrators, credential connections, GitHub, Slack)Changing settings
Owners and adminsReadReadChange
Agent administrators (the responsible team's lead included)ReadReadChange
The responsible team's memberReadReadNo (403)
Any other memberRead (hidden when the setting below is none)No (403, or 404 when the setting is none)No (403)

Before, a member could not list agents they did not administer, and the responsible team's member could not read the settings either. Now every member finds the workspace's agents with GET /v1/agents and GET /v1/agents/counts, and the responsible team's member reads the settings. The include=summary summary (administrators, credential connections) comes only on the agents whose settings the caller reads. A receipt that changed an agent's settings is likewise read only by those who read the settings.

What members outside an agent can do

Owners, admins, the agent's administrators and its responsible team's members are inside the agent; every other member is outside it. Two workspace settings decide what those outside can do with it. Only owners and admins change them, with PATCH /v1/workspaces/{id}/agent-access, and every change leaves a receipt. The current values are workspace.settings in GET /v1/auth/me.

SettingValueFor a member outside
agent_outsider_accessuse (default)Reads the agent's information and starts its sessions
viewOnly reads its information. Starting a session is 403 FORBIDDEN (reason_code: role)
noneThe agent is missing from lists and reading it is 404; starting a session is 403
agent_outsider_session_visibilitymanaging_team (default)workspace-shared sessions of an agent are left out of lists, counts, details, events and the usage recent list for members outside its responsible team
workspaceEvery member sees those sessions too
  • agent_outsider_access and agent_outsider_session_visibility apply to every agent. An agent always has a responsible team. Agents that had no team before it became required went to a memberless holding team (the server recognizes it even if it is renamed), and their workspace sessions stay visible to every member, as before, until the agent moves to the right team.
  • A member outside still sees the sessions they started and the ones shared with them or their team (people, team).
  • Both settings apply from the next request, to sessions already made too. An open event stream ends at its next check. Changing the responsible team, or leaving it, works the same way.
  • Usage totals (session count, cost, achievement count) stay the same; only the rows of sessions the caller cannot see drop out of the recent achievements list.
  • A session shared with workspace that members outside cannot see reads audience_limited: true in GET /v1/sessions/{id}/share.
curl -sS -X PATCH "$API/v1/workspaces/$WID/agent-access" "${WEB[@]}" \
  -d '{"agent_outsider_access": "view"}' | jq -c '{agent_outsider_access, agent_outsider_session_visibility, changed: (.receipt_id != null)}'
{"agent_outsider_access":"view","agent_outsider_session_visibility":"managing_team","changed":true}

WID is workspace.id from GET /v1/auth/me.

In the console, change them under Agent access in Workspace settings. Agent access for members outside the responsible team is View and run (use), View only (view) or Hidden (none); Workspace-shared sessions is Responsible team only (managing_team) or Every member (workspace). Only the changed values are sent, and after a save the screen reads GET /v1/auth/me again. Members who are not owners or admins see the current values without the choices.

Before, every member saw workspace-shared sessions. The default is now managing_team, so the sessions of an agent are seen only by those inside it right away (agents still in a holding team are the exception). To see them as before, set agent_outsider_session_visibility to workspace.

This continues with AID and WEB from Create.

curl -sS "$API/v1/agents/$AID/admins" "${WEB[@]}" | jq '{users: [.users[].email], teams}'
{
  "users": [
    "owner@example.test"
  ],
  "teams": []
}
RequestWhat it does
POST /v1/agents/{id}/admins {"user_id"} or {"team_id"}Assign an administrator
DELETE /v1/agents/{id}/admins/users/{user_id}, …/teams/{team_id}Remove
PUT /v1/agents/{id}/managing-team {"team_id"}Set or change the responsible team
GET /v1/administered-agentsAgents I administer

Credential bindings

The card lists only the names of the credentials to use, in capabilities.credential_refs. The values live outside Werft (a secret store, a remote MCP server connection). The binding that says this agent may use that name is created separately by an administrator. If any name listed on the card is not bound, activation returns 409 CREDENTIAL_UNBOUND.

curl -sS "$API/v1/agents/$AID/credential-bindings" "${WEB[@]}" -d '{"credential_ref": "notion-token"}' \
  | jq '{credential_ref: .binding.credential_ref, created}'
curl -sS "$API/v1/agents/$AID/credential-bindings" "${WEB[@]}" | jq -c '[.items[].credential_ref]'
{
  "credential_ref": "notion-token",
  "created": true
}
["notion-token"]

Removing a binding (DELETE /v1/agents/{id}/credential-bindings/{binding_id}) does not stop running sessions. It blocks starting with the next session. To block immediately, revoke that credential at the external service.

Next steps

On this page