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| Field | Meaning |
|---|---|
model | Default model |
tools | Tools a session can use. Tools outside the list are not run (Permissions) |
deny | The 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) |
billing | With metered, cost counts toward the session limit. With none, cost is not counted |
repository_mode | required (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
errorevent above. When the whole profile or model connection is gone, the same event is written withcause: profile_removed(model_removedwhen only the model left), andmodelis the model the session was running. - When the next message fails to start a worker, the session becomes
failed. The sameerrorevent (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'sattention(CATALOG_MISMATCH) names the model as well. - Creating a session on a model that is not on the list answers
422 UNSUPPORTED_CAPABILITYwith 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_MISMATCHsaying 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
familyare one series, and a higherrankis newer. Two models of onefamilywith the samerankare 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
familywith a higherrank; with several, the nearest (lowest) one. It never moves down to a lowerrankor across to anotherfamily. With no candidate the session is blocked as described above. model_familiesis 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), intool_catalogsofGET /v1/installation-profiles/options. - Where a tool runs.
localruns inside the worker,egressgoes out through the platform's outbound proxy, andproviderruns 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 indeny. The API blocks only whatdenynames, sodeny: []opensWebSearchtoo. The console blocks provider-side tools by default on a first version. - Tools added later stay blocked. A version fixes its
toolsfrom 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_idfor an operator profile, the latest version of the same name for a workspace profile) never weighed enter the newtoolsonly when named inaccept_added_tools. Such a tool in neitherdenynoraccept_added_toolsis400(reason_code: added_tool_not_accepted). If the previous version was made withtools, every tool outside that list counts. denytakes built-in tool names andmcp__<server>__<tool>names. An MCP name must point at one tool in themcp__<server>__<tool>form; a whole server or a pattern cannot be blocked. A name in another form is400(mcp_tool_name), and a tool of the built-in GitHub server is400(github_server). A server this installation does not list is400(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 inmcp_deny, sorted, anddenyholds the built-in tools only.accept_added_toolstakes built-in tool names only. An MCP name there is400(mcp_tool). In bothdenyandaccept_added_tools, a pattern such asBash(rm:*)is400(tool_pattern) and a name not on the list is400(unknown_tool). A version saved with an allow list (tools) still keeps themcp__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 everymcp__name the previous version blocked. To lift one, put it inunblock_mcp_tools. A name the previous version did not block is400(mcp_unblock_unknown), a name also indenyis400(mcp_unblock_conflict), and a version with no previous one is400(mcp_unblock_no_previous). A new version of a workspace profile (POST /v1/workspace-profiles) names the version before it inprevious_id; if a newer version of the same name has appeared since, the answer is409(stale_previous). - Every version over a base writes the base's blocked MCP names into
denyitself. Leaving one out, or lifting it withunblock_mcp_tools, is400(base_mcp_deny_dropped). - One of
toolsanddeny. A request sends exactly one of them. Both, or neither, is400.accept_added_toolsgoes only withdeny. Sent astools, the list is stored as given, as before. A runtime version with no supported list takes onlytools;denyis400(no_tool_catalog). - A workspace profile never goes past its base. Its
toolsare the base profile'stoolsthat are on the supported list, minusdeny. So evendeny: []opens no tool the base does not run. When the base is a version made withdenyon the same supported list, a request that leaves out a tool the base blocks is400(base_denies). - Existing versions stay as they are. A version made with
toolsalso shows adenyin the list: the supported list minus itstools. To widen one, make a new version withdeny.
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);succeededIf 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
autowhen the base profile allows it (a base inautoorbypassPermissions), and Create profile picks such a base first.autoandbypassPermissionsthat the base does not allow show as unavailable choices. AbypassPermissionsbase exists only where the operator turned on the installation setting. PickingbypassPermissionsshows 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 withws_, 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_codeisblocked(the profile you picked is blocked) orbase_blocked(its base is), anderror.details.profile_idis the profile you picked. An existing session on the profile refuses a new message with409 PROFILE_BLOCKED(resending a message accepted before the block with the sameIdempotency-Keyreturns the original response), and itsattentioninGET /v1/sessions/{id}becomesPROFILE_BLOCKED. A turn queued before the block ends withcatalog_mismatchwhen it starts, and a turn running now loses its model and repository requests within about a second. POST /v1/installation-profiles/{profile_id}/unblockputs 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_idisnull.
Next steps
- Permissions: What the tool list and permission mode do
- Start: The session creation request