`PUT /api/agent-profiles/{id}` — update an agent profile's runbook fields PLUS the optional flattened autonomy override (workspace-admin).
const url = 'https://example.com/api/agent-profiles/2489E9AD-2EE2-8E00-8EC9-32D5F69181C0';const options = { method: 'PUT', headers: { cookie: 'supacloud_session=<supacloud_session>', 'Content-Type': 'application/json' }, body: '{"avatar_kind":"example","avatar_value":"example","capability_allowlist":["example"],"color":"example","default_agent_type":"example","description":"example","effort_default":"example","enabled":true,"execution_mode":"example","fixed_model":"example","interactive_approval":true,"max_subagents":1,"max_turns":1,"mcp_memory_create":true,"mcp_ops_per_hour_cap":1,"mcp_ops_per_task_cap":1,"memory_policy":"example","model_policy":"example","name":"example","persona_prompt":"example","post_flight_checks":"example","pr_template":"example","pre_flight_checks":"example","resource_class":"normal","retry_policy":"example","review_required":true,"script_allowlist":["example"],"slug":"example","tags":["example"],"tool_approval_deny_minutes":1,"autonomy_level":1,"autonomy_overrides":"example","required_worker_group_ids":["2489E9AD-2EE2-8E00-8EC9-32D5F69181C0"]}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request PUT \ --url https://example.com/api/agent-profiles/2489E9AD-2EE2-8E00-8EC9-32D5F69181C0 \ --header 'Content-Type: application/json' \ --cookie supacloud_session=<supacloud_session> \ --data '{ "avatar_kind": "example", "avatar_value": "example", "capability_allowlist": [ "example" ], "color": "example", "default_agent_type": "example", "description": "example", "effort_default": "example", "enabled": true, "execution_mode": "example", "fixed_model": "example", "interactive_approval": true, "max_subagents": 1, "max_turns": 1, "mcp_memory_create": true, "mcp_ops_per_hour_cap": 1, "mcp_ops_per_task_cap": 1, "memory_policy": "example", "model_policy": "example", "name": "example", "persona_prompt": "example", "post_flight_checks": "example", "pr_template": "example", "pre_flight_checks": "example", "resource_class": "normal", "retry_policy": "example", "review_required": true, "script_allowlist": [ "example" ], "slug": "example", "tags": [ "example" ], "tool_approval_deny_minutes": 1, "autonomy_level": 1, "autonomy_overrides": "example", "required_worker_group_ids": [ "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0" ] }'Authorizations
Section titled “Authorizations”Parameters
Section titled “Parameters”Path Parameters
Section titled “Path Parameters”Request Bodyrequired
Section titled “Request Bodyrequired”The PUT body for an agent profile: the runbook fields (flattened at the top
level) PLUS the ADR 0045 Stage 4 autonomy override (autonomy_level /
autonomy_overrides, also top-level). Splitting the autonomy fields into an
[AutonomyPatch] keeps them OUT of [agent_profiles::AgentProfileInput] (whose
many construction sites stay source-compatible) while the FE still sends ONE
flat object. Absent autonomy fields ⇒ no autonomy change.
object
ADR 0045 §3.6 — default reasoning effort
(minimal | low | medium | high | max). Optional; preserves the current
value on update, leaves None (inherit the runner default) on create.
Issue #364 / ADR 0039 (D7); #708 who-3 — normal | workflow (ultracode
RETIRED to the effort axis). Optional; preserves the current value on update,
defaults to normal on create.
ADR 0045 §3.2 — opt this profile into the interactive tool-approval gate
(resolve_interactive_approvals_for_run). Optional; preserves the current
value on update, defaults to false on create. #708 W5 wired this WRITE
path (it was read-only before — the create/update never persisted it).
#838 — per-profile DEFAULT for the subagent concurrency ceiling (migration
358; 1..=64). The middle tier of task.config.max_subagents ?? profile ?? workspace ?? (no env). DOUBLE-Option with the same semantics as
[Self::max_turns]: absent preserves the current value on update / leaves
NULL on create; an explicit null clears it; a value sets it. “Absent =
unchanged” is load-bearing — the simple profiles tab sends a partial payload
and must never null a stored default (the #858 W4a ops-cap pattern).
#1018 — per-profile DEFAULT for the harness turn limit (migration 340,
positive). DOUBLE-Option: absent preserves the current value on update / leaves
NULL (no default — the runner’s 500 applies) on create; an explicit null
clears it; a value sets it. “Absent = unchanged” is load-bearing — the
simple profiles tab sends a partial payload, and it must never null a
stored default (the #858 W4a ops-cap pattern).
ADR 0049 (#547 / #548) — opt-in gate for the agent memory.create MCP
tool. Optional; preserves the current value on update, defaults to false
(fail-closed) on create. A workspace admin sets this on the profile editor
to open the unreviewed direct-write path.
#858 W4a — per-profile override of the profile-hourly MCP Ops budget
(migration 320; range 1–1000). Same DOUBLE-Option semantics as
mcp_ops_per_task_cap. The workspace-daily Ops ceiling stays fixed.
#858 W4a — per-profile override of the per-task/per-tool MCP Ops budget
(migration 320; range 1–500). DOUBLE-Option: absent preserves the current
value on update / leaves NULL (the ADR 0028 default, 10) on create; an
explicit null clears the override back to the default; a value sets it.
Admin-gated — the profile PUT already requires workspace admin.
#1176 — the profile’s RESOURCE CLASS (migration 361; normal | heavy).
The middle tier of task.config.resource_class ?? profile ?? workspace ?? normal.
A class is the profile’s statement about how much MACHINE its work needs:
the container’s memory cap and, inseparably, how many of the workspace’s
concurrency slots the task holds. Declaring heavy therefore does not
raise the host’s peak footprint — it trades parallelism for room.
DOUBLE-Option with the same semantics as [Self::max_turns]: absent
preserves the current value on update / leaves NULL on create; an explicit
null clears it; a value sets it. “Absent = unchanged” is load-bearing —
the simple profiles tab sends a partial payload and must never null a
stored declaration.
The script-run allowlist. Optional; absent preserves the current value on update
/ leaves None (OPEN — every script allowed) on create; a value (incl. [],
fail-closed) sets it. Mirrors capability_allowlist. #708 W9 wired this so a
workspace manifest round-trips it — the manifest apply supplies a fail-closed
Some([]) default (never the open None) when the manifest omits it.
#1175 (migration 363) — the profile’s TOOL-GATE deny budget in minutes,
the middle tier of task.config.tool_approval_deny_minutes ?? profile ?? workspace ?? 1440.
It governs ONLY the deny timeout policy, which a caller must name
explicitly. hold — the default for every runner-raised gate since #1164 —
carries no deadline, and no value here can give it one.
Range [5, 43200], enforced by the migration-363 CHECK and refused BY NAME
at write time rather than clamped, so a stored value is always the value
that applies. DOUBLE-Option, same semantics as [Self::max_turns].
ADR 0046 RUNG 1b — the profile’s worker-group OVERRIDE binding (the head of the
profile→project→task first-non-null chain). DOUBLE-Option: absent leaves it
untouched; null clears it to inherit (column NULL); a value (incl. []) sets
the override. Workspace admin (the update handler already gates on it).
Responses
Section titled “Responses”object
ADR 0045 §3.1 Stage 4 — OPTIONAL profile autonomy override (mig 214, nullable
SMALLINT). None = inherit the project/workspace level (sits between project
and workspace in the §3.1 precedence chain). #[serde(default)] keeps old
AgentProfileVersion JSON snapshots (pre-214) deserializable — they default to
None (a no-op).
ADR 0045 §3.1 — per-axis Advanced override vector (mig 214, JSONB NOT NULL DEFAULT '{}'). Empty {} = no per-axis override; the editor reads an
optional level key. #[serde(default)] keeps old snapshots deserializable.
Runtime stays serde_json::Value; the spec types it as an open map.
object
Avatar rendering kind (color | initial | icon | image). Runtime String;
the spec narrows it to the closed [AvatarKind] the editor offers.
ADR 0045 §3.6 — the profile’s default reasoning effort (migration
208, effort_default TEXT). Canonical lowercase string
(minimal | low | medium | high | max); None means “inherit the
runner default”. #[serde(default)] keeps old AgentProfileVersion
JSON snapshots (written before this column existed) deserializable —
they default to None (a no-op). Runtime stays String; the spec
narrows it to the closed canonical [sc_types::Effort]
(its lowercase wire form matches this column’s DB CHECK set).
Issue #364 / ADR 0039 (D7) — Claude execution mode lever (normal | workflow;
ultracode was RETIRED from this dimension by #708 who-3, migration 285 — it
survives only as the top EFFORT stage). #[serde(default = ...)] keeps old
AgentProfileVersion JSON snapshots (written before this column existed)
deserializable; they default to "normal" (a no-op). Runtime String; the
spec narrows it to the closed [ExecutionMode].
ADR 0045 §3.2 Stage 3 — when true, a task running under this profile arms
the interactive-approval gate (the runner’s canUseTool / pre-exec hook
pauses on a dangerous tool call, an open question, or a visual before/after
and waits for a human Approve/Reject; migration 213). false is today’s
behaviour (no gate). #[serde(default)] keeps old AgentProfileVersion
JSON snapshots (written before this column existed) deserializable — they
default to false (a no-op).
Issue #242 — when true, this profile’s capability_allowlist is the
workspace fallback MCP tool set for profile-less / empty-allowlist
tasks. At most one profile per team carries this flag (enforced by the
partial unique index uq_agent_profiles_workspace_default in
migration 119).
#838 — per-profile DEFAULT for the subagent concurrency ceiling (migration
358, nullable; 1..=64). The env build folds task.config.max_subagents ?? profile.max_subagents ?? workspace_settings.default_max_subagents ?? (no env), so this is the middle tier: the launch override still wins, and with
every tier absent no SUBAGENT_MAX_PARALLEL is emitted and the harness keeps
its own default. None = today’s behaviour.
The ceiling is only as strong as the harness allows — codex takes a real
per-session cap, the rest receive it as an instruction — which is why the UI
labels it from model_capabilities.subagent_cap_level instead of implying a
guarantee. #[serde(default)] keeps pre-358 AgentProfileVersion snapshots
deserializable.
#1018 — per-profile DEFAULT for the harness turn limit (migration 340,
nullable; > 0). The env build folds task.config.max_turns ?? profile.max_turns ?? (no env) — the task config stays the per-launch
override, and with both absent no MAX_TURNS env is emitted so the
runner default (500, DEFAULT_MAX_TURNS) applies. None = today’s
behaviour. #[serde(default)] keeps pre-340 AgentProfileVersion
snapshots deserializable.
ADR 0049 (#547 / #548) — opt-in gate for the agent memory.create MCP tool
(migration 249, BOOLEAN NOT NULL DEFAULT false). When false (the default
for every profile) a task running under this profile CANNOT directly create a
memory — memory.create fails closed; only a workspace admin enabling this on
the profile editor opens the unreviewed direct-write path. Distinct from
memory_policy.allow_propose_create (the default-true review-first gate).
#[serde(default)] keeps old AgentProfileVersion JSON snapshots (written
before this column existed) deserializable — they default to false (a no-op).
#858 W4a — per-profile override of the rolling profile-hourly MCP Ops
budget (migration 320, nullable; range 1–1000). None = the ADR 0028
default (30). The workspace-daily Ops ceiling (100/day) is NOT overridable.
#[serde(default)] keeps pre-320 snapshots deserializable.
#858 W4a — per-profile override of the per-task/per-tool MCP Ops budget
(migration 320, nullable; range 1–500). None = the ADR 0028 default (10).
Admin-gated via the profile PUT; a long-lived coordinator session raises it
deliberately instead of the global default weakening. #[serde(default)]
keeps pre-320 AgentProfileVersion snapshots deserializable.
The fixed memory-policy knobs the editor reads. Runtime stays
serde_json::Value; the spec types it as [AgentMemoryPolicy].
object
Model-selection policy (manual | fixed | auto). Runtime String; the spec
narrows it to the closed [ModelPolicy].
Forward-compatible list of post-flight check entries (e.g. the
kind:"visual_verify" gate). Runtime stays serde_json::Value; spec-typed
as an array of open maps.
object
Forward-compatible list of pre-flight check entries (each an open object,
e.g. a kind-discriminated gate). Runtime stays serde_json::Value; the
spec types it as an array of open maps so the value is not stripped.
object
ADR 0046 RUNG 1b — OPTIONAL profile worker-group affinity override (mig 227,
nullable UUID[]). None = inherit (the head of the first-non-null
profile->project->task chain — NULL contributes nothing); Some(vec![]) = an
explicit “no affinity from this source”. Resolved into the task’s
required_labels by services::runners::groups. #[serde(default)] keeps old
AgentProfileVersion JSON snapshots (pre-227) deserializable.
NB: this binding is intentionally EXCLUDED from agent-profile version snapshot/restore — it is dispatch-routing scope (a live operational binding), not part of the profile’s behavioural definition, so a version restore does not carry or revert it (ADR 0046 RUNG 1b review).
#1176 — the profile’s RESOURCE CLASS (migration 361, nullable;
normal | heavy). The middle tier of task.config.resource_class ?? profile.resource_class ?? workspace_settings.default_resource_class ?? normal.
It sits on the profile because a profile already decides WHO runs — harness, model, effort, MCP allowlist, per-profile Ops caps — and how much machine that work needs is the same kind of fact. “The audit profile is heavy” is a statement about the work, not about one launch.
A class is not just memory: it also weighs more in the workspace
concurrency allowance, so raising it does not raise the host’s peak
footprint. None = today’s behaviour. Runtime stays String; the spec
narrows it to the closed [sc_types::ResourceClass]. #[serde(default)]
keeps pre-361 AgentProfileVersion snapshots deserializable.
Open retry-policy bag. Runtime stays serde_json::Value; spec-typed as an
open map.
object
Issue #217 / ADR 0028 — optional slug whitelist for the script.run
MCP tool. NULL means any script in the workspace is callable; a
non-NULL array (including empty) is an explicit whitelist.
#1175 (migration 363, nullable) — the profile’s TOOL-GATE deny budget in
minutes. The middle tier of task.config.tool_approval_deny_minutes ?? profile ?? workspace_settings.default_tool_approval_deny_minutes ?? 1440.
It sits on the profile for the same reason the resource class does: a
profile already decides WHO runs and how far it may go unattended, and how
long that runbook’s gate may wait before an explicit deny policy expires
is the same kind of fact. None = today’s behaviour.
It NEVER affects a hold gate, which is the default for every
runner-raised gate and carries no deadline at all (#1164).
Example
{ "avatar_kind": "color", "effort_default": "minimal", "execution_mode": "normal", "model_policy": "manual", "resource_class": "normal"}