FeaturesAgents

Choose a profile

The profile that decides which model, tools, and permission mode a session runs with

A profile decides how a session runs: the runtime and its version, the model, the tool list, the permission mode, the model provider, and whether it uses a repository. Operator profiles set the widest range every workspace on the installation can use. They live in the installation's database, and users choose one by id. Each profile's row also holds the repositories it can run against. A new installation has no profiles. The installation owner adds a connection under Settings > Agent connections > Model connections and picks it when making a profile. The Profiles screen only picks a connection; it does not create or change one. Connections live in the installation's database too. The add-connection dialog fills in the official endpoint for the kind you pick (https://api.anthropic.com for anthropic). Change it only for a proxy or a compatible server; leaving it empty stores the same address. A kind with no official address (litellm) needs its endpoint typed in. An endpoint cannot be changed after the connection is made. Profiles and agents that reach their model only through a disabled connection cannot run until it is turned back on. The confirmation dialog shows the impact counts it could read (profiles, agents on an active release, sessions not yet ended). A disabled connection comes back with Enable again on the same screen, unless its stored key is gone or another connection with the same definition is in use. If the connection a profile used is no longer available, the profile form asks you to pick one instead of switching to the first connection.

List profiles

The SDK examples use the werft client made in Install the SDK, and the HTTP API examples use the shell variables API (the server address) and KEY (the key).

const profiles = await werft.profiles.list();
for (const profile of profiles.items) {
  console.log(profile.id, profile.model, profile.repository_mode);
}
claude-coding-auto claude-sonnet-5 required
claude-coding-local claude-sonnet-5 required
general claude-sonnet-5 none
FieldMeaning
modelDefault model
toolsTools a session can use. Tools outside the list are not run (Permissions)
denyThe built-in tools this profile blocks: the built-in tools this installation supports for that runtime version that are not in tools. null for a version with no supported list (Tool deny list)
billingWith metered, cost counts toward the session limit. With none, cost is not counted
repository_moderequired (a repository is mandatory), optional, or none (no repository)

The provider address and credentials do not appear in the list. Those values do not go to the worker either.

Choose in a session

Choose with profile_id when you create a session. If the profile allows several models, choose one of them with model; if you leave it out, the session runs with the default model.

If the model of a running session leaves the allow-list, its model requests are cut shortly after. The session's events get one error event (code: MODEL_NOT_ALLOWED, model), and the session detail timeline reads "Model requests for this session were blocked because … is no longer allowed by its profile". Restore the model to the list, or start a new session on another model.

Every path that blocks because a model left the list names it.

  • A running turn is the error event above. When the whole profile or model connection is gone, the same event is written with cause: profile_removed (model_removed when only the model left), and model is the model the session was running.
  • When the next message fails to start a worker, the session becomes failed. The same error event (cause: model_removed) and a reason saying the model "was taken off the allowed list of profile …" land in the session's events and the input receipt. The session detail's attention (CATALOG_MISMATCH) names the model as well.
  • Creating a session on a model that is not on the list answers 422 UNSUPPORTED_CAPABILITY with the model's name. That includes a request that names no model and so runs the release's model.
  • Activating a release whose model left the list, or creating a session from it, answers PROFILE_DIGEST_MISMATCH saying the release's model was taken off the allowed list, with its name.

The profile detail warns above "agents on this profile", and the agent overview above its readiness steps, which model left and what to do: put the model back on the list, or change the model to an allowed one in the agent's Settings and save.

Moving to the next model of the same series

Write model_families on an operator profile (profiles in the settings document) and a session whose model left the list moves to the next model of its series instead of being blocked. Nothing is guessed from model names; only the values you write count.

"model_families": {
  "claude-sonnet-5-0": { "family": "sonnet", "rank": 0 },
  "claude-sonnet-5-1": { "family": "sonnet", "rank": 1 }
}
  • Models with the same family are one series, and a higher rank is newer. Two models of one family with the same rank are refused as a setting.
  • Keep the entry of a model after it leaves the list. Without it nothing knows what series the model belonged to, and its sessions are blocked as before.
  • The candidate is a listed model of the same family with a higher rank; with several, the nearest (lowest) one. It never moves down to a lower rank or across to another family. With no candidate the session is blocked as described above.
  • model_families is not part of what a session is pinned to (its fingerprint). Adding or changing it leaves sessions and releases already made as they are.

The move happens at the claim that starts the session's next turn. Only a session waiting for a turn moves; a turn already running is left alone, and its model requests are cut as described above. A move writes a system session event (subtype: model_migrated, from, to), which the timeline shows as "moved from … to …". A checkpoint made on the earlier model still restores after the move.

When a release's model left but a candidate exists, the release still starts sessions. The session starts on the candidate; creating a session does not change the release. A request that names model itself must name one on the allowed list. model_successors in the profile summary (GET /v1/profiles) shows each dropped model and where it moves, and the agent overview then shows a notice instead of the blocked warning.

The agent's active release moves to the candidate too. The server creates and activates a new release with the reason model_migration, and the audit receipt records the earlier and later model with automatic: true. No person triggers it, so the actor is the person who last activated that release (an owner or admin is required). If that person is inactive or no longer an owner or admin, nothing moves, the agent stays blocked, and the administrators get a failure DM. After a move the agent's administrators get a DM. Setting auto_model_migration on the agent to false (PATCH /v1/agents/{id}, default true) stops the release rewrite, the session move and the use of a candidate for new sessions, and the agent stays blocked. The warning on the agent overview has the same switch.

This covers a change to the model list of an operator profile. Workspace profile versions keep their model list once made.

The first block also sends one Slack DM, and the DM names the model too. It goes to the person who created the session and to the agent's administrators (the administrators set directly, members of an administrator team, and the managing team's leads), each only if they have linked a Slack account in the team where this agent's Slack app is installed. Each person gets one per session, so a later attempt of the same session being blocked sends nothing more. Sessions that ended for other reasons send none.

const created = await werft.sessions.create({
  profile_id: "claude-coding-local",
  model: "claude-sonnet-5",
  repository_id: "sample-app",
  message: 'GATE-SPEC {"id":"profile","steps":[],"final":"ok"}',
});
const sessionId = created.session_id;
console.log((await werft.sessions.get(sessionId)).runtime);
{
  kind: 'claude_agent_sdk',
  version: '0.3.319',
  profile_id: 'claude-coding-local',
  model: 'claude-sonnet-5',
  permission_mode: 'default'
}

A profile that is not in the catalog, a profile that cannot run with that repository, and a model the profile does not allow all return 422.

When you create an agent release, choose with runtime_profile_id. The card's model must be one of the models that profile can run, and all of the card's tools must be in the profile (Create).

Tool deny list

Instead of listing the tools to use, a profile version can name only the built-in tools to block, in deny. For an operator profile, when it saves the version, the server stores as tools the built-in tools this installation supports for that runtime version that are not in deny. A workspace profile is worked out within its base's tools, as below. Sessions and releases see only the stored tools.

  • No limit means within the supported list. On an operator profile, deny: [] opens every built-in tool on the supported list. A tool the engine ships but this installation does not support stays closed. An owner reads the supported list, and where each tool runs (boundary), in tool_catalogs of GET /v1/installation-profiles/options.
  • Where a tool runs. local runs inside the worker, egress goes out through the platform's outbound proxy, and provider runs on the model provider's side. The outbound proxy cannot stop a provider-side tool (WebSearch); only the profile's block does. So the first version of a new name should start with provider-side tools in deny. The API blocks only what deny names, so deny: [] opens WebSearch too. The console blocks provider-side tools by default on a first version.
  • Tools added later stay blocked. A version fixes its tools from the supported list when it is saved, and existing versions, releases and sessions gain no new tools. Upgrading the engine changes the runtime version, so you make a new profile version. Tools the previous version (previous_id for an operator profile, the latest version of the same name for a workspace profile) never weighed enter the new tools only when named in accept_added_tools. Such a tool in neither deny nor accept_added_tools is 400 (reason_code: added_tool_not_accepted). If the previous version was made with tools, every tool outside that list counts.
  • deny takes built-in tool names and mcp__<server>__<tool> names. An MCP name must point at one tool in the mcp__<server>__<tool> form; a whole server or a pattern cannot be blocked. A name in another form is 400 (mcp_tool_name), and a tool of the built-in GitHub server is 400 (github_server). A server this installation does not list is 400 (unknown_mcp_server). A name the previous version already blocked is not judged again, even when its server has left the list. In a response the MCP names are in mcp_deny, sorted, and deny holds the built-in tools only.
  • accept_added_tools takes built-in tool names only. An MCP name there is 400 (mcp_tool). In both deny and accept_added_tools, a pattern such as Bash(rm:*) is 400 (tool_pattern) and a name not on the list is 400 (unknown_tool). A version saved with an allow list (tools) still keeps the mcp__ names the previous version blocked. An MCP tool that the active release's profile blocks also cannot be newly allowed in an agent's MCP settings (mcp_tool_denied).
  • MCP blocks carry to the next version and are lifted only by unblock_mcp_tools. A new version keeps every mcp__ name the previous version blocked. To lift one, put it in unblock_mcp_tools. A name the previous version did not block is 400 (mcp_unblock_unknown), a name also in deny is 400 (mcp_unblock_conflict), and a version with no previous one is 400 (mcp_unblock_no_previous). A new version of a workspace profile (POST /v1/workspace-profiles) names the version before it in previous_id; if a newer version of the same name has appeared since, the answer is 409 (stale_previous).
  • Every version over a base writes the base's blocked MCP names into deny itself. Leaving one out, or lifting it with unblock_mcp_tools, is 400 (base_mcp_deny_dropped).
  • One of tools and deny. A request sends exactly one of them. Both, or neither, is 400. accept_added_tools goes only with deny. Sent as tools, the list is stored as given, as before. A runtime version with no supported list takes only tools; deny is 400 (no_tool_catalog).
  • A workspace profile never goes past its base. Its tools are the base profile's tools that are on the supported list, minus deny. So even deny: [] opens no tool the base does not run. When the base is a version made with deny on the same supported list, a request that leaves out a tool the base blocks is 400 (base_denies).
  • Existing versions stay as they are. A version made with tools also shows a deny in the list: the supported list minus its tools. To widen one, make a new version with deny.

The examples below are called by an owner with the WEB header from Create. First, list the tools on the supported list that leave the worker.

curl -sS "$API/v1/installation-profiles/options" "${WEB[@]}" \
  | jq -c '.tool_catalogs[0].tools | map(select(.boundary != "local"))'
[{"name":"WebFetch","boundary":"egress"},{"name":"WebSearch","boundary":"provider"}]

Make the local profile's next version, blocking only WebSearch. The local profile is a version made with tools, so NotebookEdit and WebFetch are tools it never weighed.

next=$(jq -nc '{name: "coding-local", previous_id: "claude-coding-local",
  connection_id: "fake-messages", runtime_kind: "claude_agent_sdk", runtime_version: "0.3.319",
  model: "claude-sonnet-5", models: ["claude-sonnet-5"], permission_mode: "default",
  repository_ids: ["sample-app"], deny: ["WebSearch"]}')
curl -sS "$API/v1/installation-profiles" "${WEB[@]}" -d "$next" | jq -c '.error.details'
curl -sS "$API/v1/installation-profiles" "${WEB[@]}" \
  -d "$(echo "$next" | jq -c '.accept_added_tools = ["NotebookEdit", "WebFetch"]')" \
  | jq -c '.profile | {id, tools, deny}'
{"reason_code":"added_tool_not_accepted","field":"accept_added_tools"}
{"id":"op_coding-local_v1","tools":["Read","Edit","Write","Glob","Grep","NotebookEdit","Bash","AskUserQuestion","WebFetch"],"deny":["WebSearch"]}

Reference: POST /v1/installation-profiles

Change the profile of a running session

Change it with sessions.changeProfile (POST /v1/sessions/{id}/profile). The running turn finishes with the profile it started with, and input from then on runs with the new profile. The conversation and working tree continue from the last checkpoint. Changing to the same profile changes nothing and only leaves a receipt.

import { waitForTurn } from "@werft-ai/sdk";

await waitForTurn(werft, sessionId, created.turn_id);
const { revision } = await werft.sessions.get(sessionId);
const changed = await werft.sessions.changeProfile(sessionId, {
  expected_revision: revision,
  profile_id: "claude-coding-local",
});
console.log(changed.receipt_status);
succeeded

If the new profile differs in runtime kind, version, or billing mode, or does not pair with that repository, the response is 422 UNSUPPORTED_CAPABILITY. For a session created from an agent release, the release decides the profile, so the response is 409 RELEASE_PROFILE_MISMATCH. The scope is sessions:control.

Make a workspace profile

Workspace owners and admins can make a profile that only this workspace uses, based on an operator profile. In the console, open Workspace > Profiles and press Create profile.

  • It can only reduce. Models and tools are picked from the base profile's, and the permission mode is the base profile's own or one that has a person check more. A new profile starts at auto when the base profile allows it (a base in auto or bypassPermissions), and Create profile picks such a base first. auto and bypassPermissions that the base does not allow show as unavailable choices. A bypassPermissions base exists only where the operator turned on the installation setting. Picking bypassPermissions shows a risk notice. The runtime, provider, credential and repository pairing stay the base profile's.
  • A saved version never changes. Each save after Edit makes a new version id ws_<workspace slug>_<name>_v<n>, which the screen shows before you save. A release on an earlier version changes only when you make a new release on the new version. The list shows one row per name with its latest version; press Earlier versions (N) to unfold the rest.
  • Pressing a row opens its detail (/profiles/<id>): models, tools, permission mode, base profile, the versions under the same name, and the agents whose active release uses that version. Only people who can manage agents see the agent list. On an Available version, an owner or admin opens the editor with the current values from Edit (Recreate when the base profile changed; a turned-off version or one without its base profile has neither). An owner or admin starts a new workspace profile from an operator profile's detail with Create a workspace profile from this one.
  • The list (GET /v1/profiles) holds the operator's profiles (source: "operator") and this workspace's versions (source: "workspace") together. The console cannot change an operator profile. The operator catalog cannot use ids starting with ws_, so the two never collide.
  • When the operator changes the base profile, the version becomes Base profile changed; when the operator removes it, Base profile removed; when an owner blocks it, Base profile blocked. No new release or session can use that version. Use Recreate on a version whose base profile changed to make a new one.
  • A version that is Turned off cannot be picked by new releases or sessions. Releases and sessions that already use it keep running. Turning a version off cannot be undone.
  • A release's profile list shows operator profiles and workspace profiles in separate groups, and a version that takes no new release cannot be picked.
  • Members only read the list. Only owners and admins signed in to the console can make, edit and turn off versions (POST /v1/workspace-profiles, POST /v1/workspace-profiles/{profile_id}/retire); an API key gets 403.

Auto rules and the script ceiling have a place on the screen but cannot be changed yet.

Block an operator profile

An owner can stop one operator profile, including the sessions running on it now. Turning a profile off (retire) only keeps new releases and sessions away; blocking stops everything that runs on it. Only an owner does this, with the web login cookie; an admin, a member or an API key gets 403. In the console, use Block or Unblock on the operator profile's detail page and confirm. The example below uses the WEB header from Create.

curl -sS -X POST "$API/v1/installation-profiles/claude-coding-local/block" "${WEB[@]}" \
  | jq -c '{state: .profile.installation.state, receipt_id}'
{"state":"blocked","receipt_id":"…"}
  • The profile becomes Blocked (blocked), and the workspace profiles on it become Base profile blocked (base_blocked).
  • New releases, release activations and sessions are refused with 409 PROFILE_BLOCKED. error.details.reason_code is blocked (the profile you picked is blocked) or base_blocked (its base is), and error.details.profile_id is the profile you picked. An existing session on the profile refuses a new message with 409 PROFILE_BLOCKED (resending a message accepted before the block with the same Idempotency-Key returns the original response), and its attention in GET /v1/sessions/{id} becomes PROFILE_BLOCKED. A turn queued before the block ends with catalog_mismatch when it starts, and a turn running now loses its model and repository requests within about a second.
  • POST /v1/installation-profiles/{profile_id}/unblock puts the profile back as it was. Sessions and turns that ended do not run again on their own; send the input again.
  • When the profile is already in that state nothing changes and receipt_id is null.

Next steps

  • Permissions: What the tool list and permission mode do
  • Start: The session creation request

On this page