Skip to content
Select themeSelect language

put_api_management_v1_workspaces_slug_state

PUT
/api/management/v1/workspaces/{slug}/state
Media typeapplication/json
object
agent_profiles

#1152 — agent-profile runbooks, upserted per slug. The READ side of this document has carried agent_profiles since #998; this is the write half, so a provisioning run can seed the WHO of a launch (harness, model, effort, allowlist tier, closeout/check gates) alongside the projects and credentials it already seeds. Additive like every list here: a profile absent from the payload is never deleted server-side. Requires management:agent_profiles:write.

Array

The desired state of ONE agent profile. Identified by the URL {key} (its slug) on the item route, or by slug inside the declarative state document.

object
avatar_kind
string | null
avatar_value
string | null
capability_allowlist
Array<string> | null
color
string | null
default_agent_type
string | null
description
string | null
effort_default

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.

string | null
enabled
boolean | null
execution_mode

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.

string | null
fixed_model
string | null
interactive_approval

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).

boolean | null
max_subagents

#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).

integer | null format: int32
max_turns

#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).

integer | null format: int32
mcp_memory_create

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.

boolean | null
mcp_ops_per_hour_cap

#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.

integer | null format: int32
mcp_ops_per_task_cap

#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.

integer | null format: int32
memory_policy
model_policy
string | null
name
required
string
persona_prompt
string | null
post_flight_checks
pr_template
string | null
pre_flight_checks
resource_class
One of:
null
retry_policy
review_required
boolean | null
script_allowlist

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.

Array<string> | null
slug
string | null
tags
Array<string> | null
tool_approval_deny_minutes

#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].

integer | null format: int32
autonomy_level
integer | null format: int32
autonomy_overrides
workspace_default

Promote this profile to the workspace default (true), clear the workspace default entirely (false), or leave it untouched (absent). #242/ADR 0033 — a task with no agent_profile_id resolves to the workspace default, so this is the WHO of every unpinned launch.

boolean | null
ai_credentials
Array<object>
object
key
required
string
label
string | null
provider
required
string
budget
One of:
null
capabilities
One of:
null
git_credentials
Array<object>
object
auth_type
required
string
credential_data
required
string
git_provider
string | null
label
required
string
invites
Array<object>
object
email
required
string
role
required
string
mailbox_credentials
Array<object>
object
allowed_login_providers
Array<string>
email
required
string
imap_host
required
string
imap_port
integer format: int32
label
required
string
password_secret_ref
required
string
provider
string | null
use_tls
boolean
username
required
string
marketplace_installs

#1214 — official-catalog listings this workspace carries, by slug. ADDITIVE like every list here, and here that is load-bearing rather than conventional: there is no uninstall path at all, so a removed entry cannot be honoured even in principle. Requires management:credentials:write — an install materialises the listing’s resources into the workspace.

Array<object>

One OFFICIAL-catalog listing this workspace carries.

ADDITIVE, deliberately: a listing that disappears from the document is NOT uninstalled, because no uninstall path exists (neither route nor query, measured 2026-08-31). Saying so here is the point — a document that reads as a full desired state while only ever adding is the more dangerous shape.

object
optional_members

#1333 — the OPTIONAL bundle members to materialise (ADR 0072 D7). Absent installs the required set. A mapping on an optional member’s binding hole is refused unless that member is selected here. Honoured on INSTALL only: an update never grows the selection, exactly as the marketplace service states for every other caller.

Array<string> | null
resource_mappings

#1333 / ADR 0072 D8-Ausnahme — the resource bindings this listing is to CREATE or WIDEN, named by resource NAME so the document stays portable.

A present, NON-EMPTY list makes the apply a SUBMITTED grant (D8): it may fill a workflow member’s ADR 0058 §7 binding hole and arm its write half. Absent or empty stays STORED — the apply may only preserve bindings that are already in force, never create or widen one. The asymmetry is deliberate: an IaC template that renders an unset list as [] must not retract the bindings a running install carries, so “empty” is read as “send nothing”, not as an explicit “bind nothing”.

Array<object> | null

One resource binding a management document asks an install to CREATE or WIDEN.

The browser install body names a resource_id (a UUID minted per instance); a portable document cannot carry one, for the very reason [ManagementRepoSyncSpec::credential_resource] states one block up: ids apply only to the instance they were minted against. This keeps the document in the vocabulary it already uses — the NAME of a workspace resource declared in its own resources block — and the apply resolves the name to this instance’s id.

object
binding_name

The in-guest binding NAME this mapping supplies authority for, when it fills a workflow member’s ADR 0058 §7 hole. None is the pre-#1313 per-kind mapping, which fills no hole.

string | null
grant_write

The WRITE grant for binding_name. None and false are the same safe answer: only true asks for write, and only where the requirement asks it too (the effective allow_write is the AND of the two).

boolean | null
requirement_kind
required

The requirement KIND this mapping answers (seafile_webdav, postgresql, …) — the same token the manifest requirement carries.

string
resource_name
required

The NAME of a workspace resource holding the authority — never a UUID. An unknown or ambiguous name is an ERROR, not a silent non-binding.

string
schema

#1313 / ADR 0073 D2 — the postgresql schema a schema_per_workspace listing’s migrations apply into, for a workspace that has no recorded namespace row yet.

The catalog ships a neutral placeholder and the INSTALL REQUEST names the real schema, exactly as ResourceMappings.schema does for the browser wizard (ADR 0073 D2). Absent or empty is the pre-#1313 shape and changes nothing: a listing that does not opt in keeps its manifest literal, and an opted-in listing with no recorded row is refused by name rather than guessed. The value is never defaulted to a fixed schema name — the wrong schema would write one tenant’s data into another’s.

skip_serializing_if matches ResourceMappings.schema: an absent schema is “no opinion”, not an explicit erasure.

string | null
slug
required

The catalog slug (scan-capture, finance-overview-app, …), resolved against the official source. Same reason as credential_resource above: item ids are per-instance, slugs are not.

string
version

Pin to one version. Absent means “track the newest installable version”, which is what a provisioning run wants — re-applying then carries a catalog update into the workspace.

string | null
members
Array<object>
object
admin
boolean
approved
boolean
display_name
string | null
email
required
string
role
required
string
oauth_credentials
Array<object>
object
access_token
required
string
account_email
string | null
expires_at
string | null format: date-time
id_token
string | null
provider
required
string
refresh_token
required
string
projects
Array<object>
object
auto_developer_agent_type
string | null
auto_developer_enabled

#708 X2b — the PR-flow arming fields the management apply honors. auto_developer_enabled arms the pr_review automation arm; auto_developer_agent_type stamps its source_filter.harness. The legacy auto_developer_prompt/_modes/_draft_first/… spec fields were removed with the dropped projects.auto_developer_* columns.

boolean | null
auto_issue_debate_enabled

#708 M1/X5 — DEPRECATED. The standalone issue-debate lane was retired (#708 B3/X5): the council capability is the per-stage council flag, and the auto_issue_debate_* columns were dropped (migration 302). This field is KEPT only so a stale IaC manifest that tries to ARM the lane fails LOUDLY: an apply that sets it true is REJECTED with a 422 (see apply_project) sourced from THIS request field. false / absent stays accepted for backward-compatible manifests. The other auto_issue_debate_* spec fields were removed.

boolean | null
automations

#1161 D2 — the project’s AUTOMATION ARMS, reaching parity with the browser project PUT’s automations block. This is the config home of review_enabled (the forensic audit tail, mig 231) and pipeline_config (the return-to-sender loop’s max_rounds / max_cost_usd ceilings + the per-stage overrides) — both of which the management apply previously had NO door for, so an IaC caller could set a cost ceiling, receive HTTP 200 and run with no ceiling at all.

ONE SOURCE, not a second truth (the #1152 rule for agent profiles applies verbatim): each entry is the very SaveAutomationInput the browser PUT deserializes, applied through the very project_automations::upsert_automation the browser calls — so the Class-B PATCH-merge, the kind validation, the autonomy clamp and the workflow RECONCILE (which materializes the arm’s template) are shared, not re-implemented. Absent / null leaves every arm untouched.

Array<object> | null

What a save supplies for one automation. webhook_secret = None keeps any stored secret on update (the persistence COALESCE) — a config edit need not re-type it. autonomy_override is the REQUESTED level; the service clamps it to the workspace ceiling before persist.

object
agent_profile_id
string | null format: uuid
autonomy_override
integer | null format: int32
<= 255
candidate_profile_ids

#534 WS-2 — the multi-agent work-lane candidate pool (backlog/simple lane). Some(None) clears; Some(Some([])) = the All wildcard (every keyed workspace profile, dynamic); Some(Some([ids])) = an explicit set; an omitted key (outer None) keeps the stored pool.

Array<string> | null
close_source_issue
boolean | null
completion_action
One of:
null
denied_tools
Array<string> | null
enabled
boolean | null
environment_scope
One of:
null
kind
required
string
Allowed values: pr_review intake backlog tracker
pipeline_config

#538 feinschliff — the dev-pipeline knobs JSONB (return_to_sender, convergence_level, max_rounds, max_cost_usd, per-stage overrides; mig 231).

post_completion_comment

#708 C1 — the two ADDITIVE completion booleans (post the closing comment × close/ transition the source issue). An omitted key keeps the stored value; when set, the write derives + dual-writes completion_action (lossy for “silent close”).

boolean | null
review_enabled

#538 feinschliff — the Review toggle (the gated audit tail).

boolean | null
severity_min
One of:
null
source_filter
trigger
One of:
null
webhook_secret

Write-only HMAC secret; an omitted/empty value keeps the stored secret (the persistence COALESCE), a non-empty value overwrites it.

string | null
workflow_id

#794 Stretch — selecting an existing workflow (including a Marketplace install) binds it directly to this arm. null restores the platform managed template only when the arm was previously selected; omitted keeps the current binding.

string | null format: uuid
closeout_config
One of:
null
git_credential_label
string | null
git_default_branch
string | null
git_provider
string | null
git_repo_url
string | null
key
required
string
memory_governance_policy
One of:
null
name
required
string
pipeline_config
review_enabled

#1161 D2 — MISPLACED-LEVEL TRAPS, kept for the same reason auto_issue_debate_enabled is kept: so a manifest that puts them here fails LOUDLY instead of being answered 200 and thrown away. Both belong to ONE AUTOMATION ARM, not to the project, so they live inside automations[] — a project-level pipeline_config cannot even say WHICH arm’s ceilings it means. They were the measured defect: a caller set max_cost_usd here, got HTTP 200, and ran with no ceiling. A PRESENT value — true, false or an object — is rejected with a 422 naming the field and the place it moved to.

An explicit JSON null is NOT rejected: serde maps null onto None for any Option<T>, so it is indistinguishable here from an omitted key, exactly as the closeout_config sibling above documents for itself. That is deliberate rather than merely accepted — null carries no configuration intent (it cannot express a cost ceiling), so it is not the failure this trap exists to catch, and IaC templates that render an unset variable as null keep working. Pinned by management_project_apply_treats_a_null_arm_field_as_absent.

boolean | null
repo_sync
One of:
null
resources

#918 — workspace resources (postgresql / seafile_webdav / telegram_bot / custom / …) with write-only secrets. Additive like every list here: entries absent from the payload are never deleted server-side.

Array<object>

#918 — one workspace resource in the desired state. Mirrors the resource create/update API surface; the secret is WRITE-ONLY (never echoed back; the apply result carries a fingerprint at most).

object
config
kind
required

Resource kind (closed catalogue + custom, validated by the resources service). Immutable on update, like the user-facing API.

string
name
required

Resource name — the upsert identity within the workspace (bindings and scripts reference resources by name).

string
secret

Write-only secret. Absent/null → leave the stored secret alone (additive contract — IaC can re-apply without re-supplying secrets). A string is stored as-is; a JSON object (#914 custom structured secrets) is stored as its canonical JSON serialisation.

schedules
Array<object>
object
agent_profile_id
string | null format: uuid
agent_type
required
string
cadence
required

once | daily | weekly | monthly | interval.

string
config
interval_minutes

#908 — the step width of an interval cadence in minutes (5–1440). Required iff cadence = 'interval'; rejected on any other cadence.

integer | null format: int32
>= 5 <= 1440
key
required
string
next_run_at
required
string format: date-time
project_key
string | null
prompt
required
string
timezone
string | null
title
required
string
task_scopes
One of:
null
tasks
Array<object>
object
agent_profile_id
string | null format: uuid
agent_type
required
string
closeout
One of:
null
config
object
key
additional properties
issue_id

Optional issue-link intent. When BOTH issue_id and issue_url are present, the launch path links the task to that existing issue by merging link_issue_id/link_issue_url into config via the typed services::tasks::IssueLinkConfig builder (ADR 0032, Issue #248). Either field alone is a no-op, keeping legacy callers that only send config unchanged.

string | null
issue_url
string | null
key
required
string
project_key
string | null
prompt
required
string
title
required
string
variables
Array<object>
object
is_secret
boolean
key
required
string
project_key
string | null
value
required
string
workflow_schedules

#918 — workflow schedules: one spec creates/updates the schedule row AND its linked schedule-kind trigger on the workflow.

Array<object>

#918 — one workflow schedule in the desired state: upserts the schedule row (cadence incl. interval per #908) and ensures the linked schedule-kind trigger on the workflow exists and is enabled.

object
cadence
required

once | daily | weekly | monthly | interval.

string
interval_minutes

Required iff cadence = 'interval' (5–1440 minutes, #908).

integer | null format: int32
>= 5 <= 1440
key
required

Stable reconcile identity (task_schedules.management_key).

string
next_run_at
required

First-fire hint — preserved across re-applies unless the cadence changes (same semantics as managed task schedules).

string format: date-time
project_key
string | null
timezone
string | null
workflow
required

The workflow’s name (repo-sync slug / display name).

string
workflow_templates

#987 Stage B — workflows MATERIALIZED FROM A SHIPPED TEMPLATE, upserted per (project, key) via the workflows.management_key column (migration 337). The blocks above can only RESOLVE an existing workflow by name; this one CREATES it, which is what makes a mail / scan workspace provisionable in one declarative request.

Array<object>

#987 Stage B — one workflow MATERIALIZED FROM A SHIPPED TEMPLATE in the desired state.

This is the block that makes a mail / scan workspace provisionable through the product path: before it, the state-apply could only RESOLVE an already-existing workflow by name (workflow_triggers / workflow_schedules), never create one, so an operator had to hand-compose the graph in the builder or via repo-sync first. Reconciled per (project, key) on workflows.management_key (migration 337) — deliberately NOT on the workflow NAME, which cannot survive a rename and is ambiguous across a workspace.

Additive like every list in the document: an entry that disappears from the payload is never deleted server-side, and a re-apply of an entry whose workflow already exists is a no-op reporting changed: false (the graph belongs to the user once it is materialized — the apply never overwrites their edits).

object
config

The template’s ${{ config.* }} substitution map, plus the per-slug materialization parameters (e.g. mail_intake’s mailbox_resource_id). Missing keys fall back to the template’s registry defaults.

A value of the form ${{ resources.<name>.id }} is resolved against the resources applied EARLIER IN THE SAME REQUEST, so an operator binds a mailbox or a document library by NAME and never pastes a UUID into a manifest. An unknown resource name fails the item loudly.

object
key
additional properties
string
key
required

Stable reconcile identity within the project (workflows.management_key).

string
name

Optional display name for the materialized workflow. Absent → the template’s own name:. Purely cosmetic: the reconcile identity is key, so renaming here never orphans the row.

string | null
project_key

Project that owns the workflow (ManagementProjectSpec.key). Absent → the workspace’s single project; ambiguous when the workspace has several.

string | null
template
required

The shipped template slug to materialize (mail_intake, scan_intake, autodev, …). An unknown slug fails the apply LOUDLY with a 404 rather than materializing nothing.

string
workflow_triggers

#918 — workflow triggers, upserted per (workflow, key) via the management_key column (migration 325). repo-sync deliberately never applies triggers: on pull — this is the declarative path.

Array<object>

#918 — one workflow trigger in the desired state, upserted per (workflow, key) (migration 325 management_key).

object
config
enabled
boolean
key
required

Stable reconcile identity within the workflow.

string
kind
required

Trigger kind (webhook, schedule, telegram, …) — validated by the triggers service.

string
project_key

Project that owns the workflow (ManagementProjectSpec.key). Absent → the workflow is looked up across the workspace’s landing project.

string | null
workflow
required

The workflow’s name (repo-sync slug / display name).

string
workspace
required
object
name
required
string
owner_email
string | null

Workspace desired state applied

Media typeapplication/json
object
changed
required
boolean
results
required
Array<object>
object
changed
required
boolean
fingerprint
string | null
id
string | null format: uuid
key
required
string
resource_type
required
string
workspace_id
required
string format: uuid
Examplegenerated
{
"changed": true,
"results": [
{
"changed": true,
"fingerprint": "example",
"id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0",
"key": "example",
"resource_type": "example"
}
],
"workspace_id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0"
}