---
title: "Partially update an agent"
method: PATCH
path: "/v1/projects/{project_id}/agents/{agent_id}"
tags: ["Agents"]
---

# Partially update an agent

`PATCH /v1/projects/{project_id}/agents/{agent_id}`

## Request

- Base URL: `https://api.naturali.ai`
- URL: `https://api.naturali.ai/v1/projects/{project_id}/agents/{agent_id}`
- Auth: one of:
  - HTTP bearer
  - OAuth 2 (authorization code, authorization URL `https://api.naturali.ai/authorize`, token URL `https://api.naturali.ai/token`, scopes: `mcp:access`)

Partially updates an existing agent. Identical to PUT — both perform partial updates.

## Path parameters

- `agent_id` string, required

## Headers

- `If-Match` string

## Request body

- UpdateAgentRequest: The post-update state must still set exactly one of `ai_provider_id` or `model_route_id`. To switch a pinned agent to a route, send `model_route_id` together with `ai_provider_id: null` in the same request (and vice versa).
  - `ai_provider_id` string, nullable: Public ID of the AI provider to pin. Mutually exclusive with `model_route_id`; set to `null` when switching to a route.
  - `model_route_id` string, nullable: Model route in the same project. Mutually exclusive with `ai_provider_id` and `model`; set to null to clear.
  - `name` string, nullable: Display name; `null` clears it.
  - `instructions` string, nullable: System instructions, sent as the system message of every generation; `null` clears them.
  - `model` string, nullable: Model identifier on the pinned AI provider; `null` falls back to the provider's default model. Cannot be combined with `model_route_id`.
  - `tool_bindings` ToolBinding[], nullable: Tools attached to the agent — the only attachment field. Replaces the whole binding list; set to `null` to clear. See [Tool Bindings](/docs/modules/agents#tool-bindings).
    - `tool_id` string: Public ID of a persisted tool in the agent's own project (`400 TOOL_NOT_FOUND` otherwise). Exactly one of `tool_id`/`tool`.
    - `tool` CreateToolRequest
      - `name` string, required: Tool name
      - `type` 'http' | 'client' | 'mcp' | 'pipeline' | 'naturali': Tool type (default http)
      - `description` string: What the tool does
      - `parameters` object: JSON Schema for tool input
      - `execute` ToolExecuteConfig, nullable: Execution config for http tools. Supported fields: `url` (required), `method` (default `POST`), `headers`, `body_mode` and `response_mode`. The `url` may contain `{paramName}` placeholders (e.g. `/users/{userId}`) that are replaced at call time with the corresponding tool argument value (URL-encoded). Arguments consumed as path parameters are excluded from the query string and request body. `body_mode` is `json` (default) or `multipart`. In `multipart` mode the merged tool arguments are sent as a `multipart/form-data` body: scalar fields become plain form fields and a field shaped like `{ content_type, filename, data_base64 }` is decoded from base64 and attached as a file part (the hardcoded `Content-Type: application/json` is dropped so `fetch` sets the multipart boundary itself). `response_mode` is `json` (default: the body parsed as JSON, else returned as text) or `base64`, which returns a 2xx body as `{ content_type, filename, data_base64 }` — the same file shape `multipart` accepts, so a pipeline step can relay a downloaded file. `filename` comes from `Content-Disposition` and is omitted when the target names none; a body over `TOOL_RESPONSE_MAX_BYTES` is `502 TOOL_RESPONSE_TOO_LARGE`. `auth` adds a computed request credential, for targets whose `Authorization` value cannot be expressed as a static header. Supported `auth.type` values: - `aws_sigv4` — signs the request with AWS Signature Version 4. Requires `region`, `service`, `access_key_id` and `secret_access_key`; `session_token` is optional (temporary credentials). Incompatible with `body_mode: multipart`, whose body bytes are not known at signing time. - `gcp_service_account` — mints a Google OAuth 2.0 access token from a signed service account assertion and sends it as a bearer token. Requires `credentials` (the service account key file JSON, as a string) and `scopes` (a non-empty array). Tokens are cached per service account and scope set until shortly before they expire. Credential fields accept `{{secret:...}}` references and should use them — a tool is readable by anyone who can `GET /tools`, and the stored reference is what is echoed back, never the resolved value. A credential slot takes a reference, never a literal: a literal `secret_access_key`, `session_token` or `credentials` under `auth`, a literal credential-named `headers` value (`Authorization`, `Cookie`, or a name containing `api-key`/`token`/`secret`/`password`), or a `url` carrying a password or a literal credential-named query parameter (those names, plus `key` and `sig`/`signature`) is refused with `400 LITERAL_CREDENTIAL`, `meta.paths` naming each one. A stored literal reads back as `{"no_echo": true}` (a `url` holding one is masked whole), an object rather than a string so a read-edit-write round trip fails the schema check instead of writing the placeholder in as the credential. A value carrying a `{{secret:...}}` reference is the wiring, not the credential, and stays readable. `headers` values additionally accept `{{context:<key>}}` references, resolved per call from the caller's `tool_context`, so a per-user credential can be placed in the real header the target expects (`Authorization: Bearer {{context:ocaToken}}`) instead of only in an `X-Naturali-Context-<key>` header. Valid **only** inside `headers` — a context value is caller-supplied, so it may not steer the `url` — and a key missing from the `tool_context` at call time fails the tool call with `MISSING_TOOL_CONTEXT_KEY` rather than sending an empty credential. See the Tool Context reference.
        - `url` string: Endpoint URL. May carry `{paramName}` placeholders resolved from the tool arguments at call time.
        - `method` string, nullable: HTTP method (default: `POST`)
        - `headers` object, nullable: Static headers sent on every request. Values accept `{{secret:...}}` and `{{context:<key>}}` references.
        - `body_mode` 'json' | 'multipart' | 'null', nullable: Request body encoding. Incompatible with `auth.type: aws_sigv4`.
        - `response_mode` 'json' | 'base64' | 'null', nullable: How a 2xx response body is returned. `base64` returns `{ content_type, filename, data_base64 }` for binary targets.
        - `auth` ToolExecuteAuthConfig, nullable: Computed request credential, for a target whose `Authorization` value cannot be expressed as a static header. `aws_sigv4` requires `region`, `service`, `access_key_id` and `secret_access_key`; `gcp_service_account` requires `credentials` and `scopes`. Every credential field takes a `{{secret:...}}` reference; a literal is refused with `400 LITERAL_CREDENTIAL`.
          - `type` 'aws_sigv4' | 'gcp_service_account', required: Which credential is computed.
          - `region` string, nullable: `aws_sigv4`: the signing region, e.g. `us-east-1`.
          - `service` string, nullable: `aws_sigv4`: the signing service, e.g. `execute-api`.
          - `access_key_id` string, nullable: `aws_sigv4`: the access key id.
          - `secret_access_key` string, nullable: `aws_sigv4`: the secret access key, as a `{{secret:...}}` reference; a literal is refused.
          - `session_token` string, nullable: `aws_sigv4`: the session token of a temporary credential, as a reference, like `secret_access_key`.
          - `credentials` string, nullable: `gcp_service_account`: the service account key file JSON, as a reference to a secret holding it, like `secret_access_key`.
          - `scopes` string[], nullable: `gcp_service_account`: the OAuth scopes to mint the token for.
      - `mcp` ToolMcpConfig, nullable: MCP server config (`url`, `headers`). `headers` values accept `{{secret:...}}` and `{{context:<key>}}` references, resolved right before the outbound MCP request; `url` accepts `{{secret:...}}` only. A literal credential in `headers` or `url` is refused, exactly as in `execute`.
        - `url` string: MCP server URL. Accepts a `{{secret:...}}` reference.
        - `headers` object, nullable: Headers sent on every MCP request. Values accept `{{secret:...}}` and `{{context:<key>}}` references.
      - `actions` string[]: Allowlist of actions. For `mcp` tools: an optional allowlist of MCP tool names to scope the server surface — omit or set `null` to expose every tool the MCP server offers. Ignored for other tool types.
      - `denied_actions` string[]: For `mcp` tools: an optional denylist of MCP tool names to hide. Applied after `actions` and taking precedence over it. Use it to scope a read+write MCP server read-only by denying just the write tools. Omit or set `null` to deny nothing. Ignored for other tool types.
      - `context_keys` string[], nullable: Optional allowlist of `tool_context` keys that may be forwarded to this tool as prefixed context headers (`X-Naturali-Context-<key>` by default). When `null` or omitted, every key in the caller's `tool_context` is forwarded. When set, only the listed keys are, so a per-user credential in `tool_context` can be confined to the tools that need it; `[]` forwards none. The server-pinned identity keys (`session_id`, `actor_id`, `actor_external_id`) are always forwarded. A key consumed by a `{{context:<key>}}` token in this tool's own headers is substituted regardless of this list — the tool declared that header itself.
      - `preset_parameters` object: Fixed parameters pinned on every call this tool makes, whatever its type. Keys matching fields in the input schema are removed from the schema shown to the model, and a pinned value wins over one the model or a direct caller supplies for the same key. On an `mcp` tool a preset reaches each action whose input schema declares it; an action whose schema declares no properties receives every one. Values accept `{{context:<key>}}` references, resolved per call from the caller's `tool_context`, so a pin can be the run's own value — the one account this run may act on — rather than one fixed when the tool was created. A resolved value is retyped to the parameter's declared schema type; a key missing from the call's `tool_context` fails the call with `MISSING_TOOL_CONTEXT_KEY` rather than sending the literal placeholder. `{{secret:...}}` is not resolved here. See the Tool Context reference.
      - `pipeline` object: Pipeline definition for `pipeline` tools. See the `pipeline` field on the Tool schema for the full structure.
      - `output_mapping` object: Universal JSON Logic mapping applied to the tool's raw result. See the `output_mapping` field on the Tool schema for details.
      - `guardrail_ids` string[], nullable: Guardrails attached at the tool scope.
  - `max_steps` integer, nullable: Maximum agent loop steps per turn; `null` restores the default of `20`.
  - `tool_choice` unknown
  - `stop_conditions` AgentStopCondition[], nullable: Conditions that end the agent's work early, on top of `max_steps`. Two scopes: `{"type": "has_tool_call", "tool_name": "<resolved tool name>"}` ends the **turn** after the step that calls the named tool. It narrows when the loop ends — it never lets it run past `max_steps`. `{"type": "max_chain_generations", "max_generations": <n>}` bounds the **continuation chain** instead: once the chain has spawned that many generations, further resumptions stop with `chain_limit` rather than extending it. It never shortens a turn. The effective ceiling is the smaller of this and the deployment's `MAX_CONTINUATION_CHAIN_GENERATIONS`, so an agent can be stricter than the platform but never looser. An unknown `type`, a `has_tool_call` without a `tool_name`, a `max_chain_generations` whose `max_generations` is not a positive integer, or a non-object entry is rejected with 400.
    - `type` 'has_tool_call' | 'max_chain_generations', required: Which scope the condition ends.
    - `tool_name` string, nullable: The resolved tool name a `has_tool_call` condition matches. Required for that type.
    - `max_generations` integer, nullable: Generations the continuation chain may reach before further resumptions stop with `chain_limit`. Required for `max_chain_generations`, and must be a positive integer.
  - `active_tool_ids` string[], nullable: Persisted tools from `tool_bindings` the model sees on every step. `null` or `[]` leaves every bound tool active. See [Active Tools](/docs/modules/agents#active-tools).
  - `guardrail_ids` string[], nullable: Guardrails attached at the agent scope.
  - `step_rules` AgentStepRule[], nullable: Per-step overrides of `tool_choice` and `active_tool_ids`, numbered from the first step of the turn; `null` clears them. See [Step Rules](/docs/modules/agents#step-rules).
    - `step` integer, required: The 1-indexed step of the turn this rule applies to. The numbering spans a `requires_action` pause.
    - `tool_choice` unknown
    - `active_tool_ids` string[], nullable: Tool IDs active on this step.
  - `boundary_policy` AgentBoundaryPolicy, nullable: Restricts which runtime actions the agent may invoke. Evaluated as the intersection with the caller's own policy, so it can only narrow.
    - `statement` AgentBoundaryPolicyStatement[], required: IAM policy statements, in the policy document grammar.
      - `effect` 'Allow' | 'Deny', required
      - `action` string[], required
      - `resource` string[], nullable: Resource SRN patterns. Omit to match every resource.
      - `condition` object, nullable: Condition block, in the same grammar a policy document uses: keys are condition operators mapping to context-key/value maps.
  - `temperature` number, nullable: Sampling temperature passed to the model; `null` restores the provider's default.
  - `knowledge_config` object, nullable: Knowledge search run before every generation; its matches are prepended as reference context. `null` turns it off. See [Knowledge Config](/docs/modules/agents#knowledge-config).
    - `memory_store_ids` string[]
    - `document_ids` string[]
    - `document_paths` string[]
    - `tags` TagBag: Key-value labels on a resource. A flat object of string values — an array, a nested object or a number is rejected with `400 VALIDATION_FAILED`, never coerced. Keys are opaque and stored verbatim, so `cost_center` and `costCenter` are two different tags. Matched by JSONB containment wherever tags are read: the `?tags=` filter and knowledge search. Keys beginning `system.` are reserved: the platform writes them to record which conversation, actor, agent and role a row came from, and a write naming one is refused with `400 RESERVED_TAG_KEY`. They are read and filtered like any other tag. The bag is bounded, because every pair reaches the IAM context of every access check on the resource: at most 50 keys, each key at most 128 characters and each value at most 256. A write past a bound — including a merge that would grow the stored bag past the key count — is `400 VALIDATION_FAILED` with `meta.limit` naming the bound it crossed. `system.*` keys are the platform's and do not count against the 50.
    - `min_score` number: Minimum raw cosine similarity (0–1) a vector candidate must reach to be ranked, the floor the search endpoint spells `min_similarity`. No default: omitted means no floor at all, and every one of the `limit` nearest chunks is injected however weak it is.
    - `rrf_k` integer: The `k` in the fusion term `1 / (k + rank)`, the same knob knowledge search takes. Smaller weights the top of each ranking more heavily. Omitted, the deployment's `KNOWLEDGE_RRF_K` applies.
    - `recency_half_life_days` number: Half-life in days of the decay applied to **memory** results after fusion, the same knob knowledge search takes. `0` disables it. Omitted, the deployment's `KNOWLEDGE_RECENCY_HALF_LIFE_DAYS` applies.
    - `limit` integer: Maximum number of results to inject. Omitted, 10 are injected.
    - `write_memory_store_id` string, nullable: Public ID of the memory store the agent can write to during generation. When set, a write_memory tool is automatically available to the agent.
  - `output_schema` object, nullable: JSON Schema describing the structured object the model must return. When set, non-streaming generations constrain output to this schema and the parsed value is returned as `output.object`. The schema is enforced on the way back, not just sent to the model: an object that violates it fails the generation with 502 `OUTPUT_SCHEMA_VALIDATION_FAILED`, naming the violated field. Constraints beyond `required`/`type` (`minLength`, `enum`, `pattern`, `minItems`) are honored and are what reject a structurally valid but degenerate answer. See the Structured Output section in the Agents module docs.
  - `prompt_caching` object, nullable: Prompt caching for this agent's turns. `{"enabled": true}` marks two cache breakpoints: the end of the system block, which covers the tool definitions and the instructions, and the last message of each step, which covers the conversation so far. A provider that caches by explicit breakpoint then reads, on every later step and every later turn of the session, the whole prefix the previous one sent, and writes only what was appended. Null or omitted is off: a cache write costs more than an uncached token, so an agent whose prefix is never re-read would pay for the privilege. A prefix below the model's minimum cacheable length is not cached. Cache reads are reported as `cached_tokens` and cache writes as `cache_write_tokens` on usage; a write with no `cache_write_tokens` price bills at the input rate.
    - `enabled` boolean: Whether the breakpoint is marked. Defaults to false.
  - `max_context_messages` integer, nullable: Maximum number of recent messages included in the context window. Null means no limit.
  - `max_output_tokens` integer, nullable: Maximum number of tokens the model may generate per step of a turn. A response that reaches it stops with `stop_reason` `length`. Null leaves the model's default.
  - `reasoning` object, nullable: How much the model reasons before answering. Translated per provider (Anthropic thinking, Gemini thinking config, OpenAI reasoning effort) against what the model supports; a model that cannot reason ignores it. `none` turns reasoning off where the model allows it. Reasoning tokens are metered as output. Null leaves the model's default.
    - `effort` 'none' | 'low' | 'medium' | 'high', required
  - `single_session_per_actor` boolean, nullable: When true, only one open session per actor_id is allowed for this agent.
  - `trace_content_mode` 'full' | 'none' | 'null', nullable: Zero-retention opt-in for this agent. `null` inherits the project's setting; `none` means trace and generation content is never written. Setting `full` under a project whose own mode is `none` is refused with 400.
  - `on_approval_expiry` 'terminate' | 'react' | 'null', nullable: What happens when one of this agent's held tool calls expires un-approved. `null` (the default) and `terminate` end the chain there — the expired approval, its `approvals.expired` event and the auto-filed `approval_expired` exception are the whole record. `react` spawns a continuation that reports the staleness to the agent, for an agent that acts on it.
  - `version_label` string, nullable: Optional tag for the config version this write archives (e.g. `pre-tone-change`). Annotates the version only — it is not stored on the agent and is not part of the config, so labelling a change is never itself a change. Ignored when the write changes nothing, since no version is created.
  - `expected_version` integer, nullable: The version the caller believes the resource holds. When the resource is at any other version the write is refused with `409 VERSION_CONFLICT` and nothing is written; `meta.current_version` on that response names the version in force. Omit it to write unconditionally. Omitting it does not make the write unordered: two writes that reach the server together are still serialized, and the one whose version was taken first is refused the same way. What the field adds is refusing a write whose author read the resource some time ago and has not seen what happened since. The `If-Match` header carries the same precondition for a client that prefers the HTTP spelling. Sending both with different versions is `400 VALIDATION_FAILED`.

## Response `200`

Agent updated

- Agent
  - `id` string: Public ID of the agent
  - `project_id` string: Public ID of the owning project
  - `ai_provider_id` string, nullable: Public ID of the pinned AI provider. Null when the agent resolves its model through `model_route_id` instead.
  - `model_route_id` string, nullable: Public ID of the model route that resolves this agent's completion model. Null when the agent pins a provider through `ai_provider_id`. Mutually exclusive with `ai_provider_id` and `model`.
  - `name` string, nullable: Display name
  - `instructions` string, nullable: System instructions guiding behavior
  - `model` string, nullable: Model identifier
  - `tool_bindings` ToolBindingRead[], nullable: Tools attached to this agent, one binding object per tool — the canonical attachment field. See [Tool Bindings](/docs/modules/agents#tool-bindings).
    - `tool_id` string: Public ID of a persisted tool in the agent's own project.
    - `tool` object: Inline tool definition, in the `CreateToolRequest` shape. A literal credential is refused on write and a stored one is masked as `{"no_echo": true}`, exactly as on a persisted tool. A `{{secret:...}}` reference stays readable.
  - `max_steps` integer, nullable: Maximum agent loop steps before stopping. The budget bounds a **turn**: a generation that pauses at `requires_action` and resumes after `submit-tool-outputs` continues the same turn and spends what is left of it, so a turn that arrives with nothing left completes with `stop_reason: "max_steps"` instead of calling the model again.
  - `tool_choice` unknown
  - `stop_conditions` object[], nullable: Conditions that end the agent's work early, on top of `max_steps` — turn-scoped (`has_tool_call`) or chain-scoped (`max_chain_generations`). See the create request body for the accepted shapes.
  - `active_tool_ids` string[], nullable: Subset of the bound tools that are active
  - `guardrail_ids` string[], nullable: Guardrails attached at the agent scope, governing every tool call the agent makes.
  - `step_rules` object[], nullable: Per-step overrides of `tool_choice` and `active_tool_ids`. Steps are numbered from the first step of the **turn**, and that numbering spans a `requires_action` pause — a rule fires once per turn, not once per resumption.
  - `boundary_policy` object, nullable: Allowed/denied runtime actions
  - `temperature` number, nullable: Sampling temperature
  - `knowledge_config` object, nullable: Knowledge retrieval config injected before every generation
    - `memory_store_ids` string[]
    - `document_ids` string[]
    - `document_paths` string[]
    - `tags` TagBag: Key-value labels on a resource. A flat object of string values — an array, a nested object or a number is rejected with `400 VALIDATION_FAILED`, never coerced. Keys are opaque and stored verbatim, so `cost_center` and `costCenter` are two different tags. Matched by JSONB containment wherever tags are read: the `?tags=` filter and knowledge search. Keys beginning `system.` are reserved: the platform writes them to record which conversation, actor, agent and role a row came from, and a write naming one is refused with `400 RESERVED_TAG_KEY`. They are read and filtered like any other tag. The bag is bounded, because every pair reaches the IAM context of every access check on the resource: at most 50 keys, each key at most 128 characters and each value at most 256. A write past a bound — including a merge that would grow the stored bag past the key count — is `400 VALIDATION_FAILED` with `meta.limit` naming the bound it crossed. `system.*` keys are the platform's and do not count against the 50.
    - `min_score` number: Minimum raw cosine similarity (0–1) a vector candidate must reach to be ranked, the floor the search endpoint spells `min_similarity`. No default: omitted means no floor at all, and every one of the `limit` nearest chunks is injected however weak it is.
    - `rrf_k` integer: The `k` in the fusion term `1 / (k + rank)`, the same knob knowledge search takes. Smaller weights the top of each ranking more heavily. Omitted, the deployment's `KNOWLEDGE_RRF_K` applies.
    - `recency_half_life_days` number: Half-life in days of the decay applied to **memory** results after fusion, the same knob knowledge search takes. `0` disables it. Omitted, the deployment's `KNOWLEDGE_RECENCY_HALF_LIFE_DAYS` applies.
    - `limit` integer: Maximum number of results to inject. Omitted, 10 are injected.
    - `write_memory_store_id` string, nullable: Public ID of the memory store the agent can write to during generation. When set, a write_memory tool is automatically available to the agent.
  - `output_schema` object, nullable: JSON Schema describing the structured object the model must return. When set, non-streaming generations constrain output to this schema and the parsed value is returned as `output.object`. The schema is enforced on the way back, not just sent to the model: an object that violates it fails the generation with 502 `OUTPUT_SCHEMA_VALIDATION_FAILED`, naming the violated field. Constraints beyond `required`/`type` (`minLength`, `enum`, `pattern`, `minItems`) are honored and are what reject a structurally valid but degenerate answer. See the Structured Output section in the Agents module docs.
  - `prompt_caching` object, nullable: Prompt caching for this agent's turns. `{"enabled": true}` marks two cache breakpoints: the end of the system block, which covers the tool definitions and the instructions, and the last message of each step, which covers the conversation so far. A provider that caches by explicit breakpoint then reads, on every later step and every later turn of the session, the whole prefix the previous one sent, and writes only what was appended. Null or omitted is off: a cache write costs more than an uncached token, so an agent whose prefix is never re-read would pay for the privilege. A prefix below the model's minimum cacheable length is not cached. Cache reads are reported as `cached_tokens` and cache writes as `cache_write_tokens` on usage; a write with no `cache_write_tokens` price bills at the input rate.
    - `enabled` boolean: Whether the breakpoint is marked. Defaults to false.
  - `max_context_messages` integer, nullable: Maximum number of recent messages to include in the context window sent to the model. When null, all messages are included.
  - `max_output_tokens` integer, nullable: Maximum number of tokens the model may generate per step of a turn. A response that reaches it stops with `stop_reason` `length`. Null leaves the model's default.
  - `reasoning` object, nullable: How much the model reasons before answering. Translated per provider (Anthropic thinking, Gemini thinking config, OpenAI reasoning effort) against what the model supports; a model that cannot reason ignores it. `none` turns reasoning off where the model allows it. Reasoning tokens are metered as output. Null leaves the model's default.
    - `effort` 'none' | 'low' | 'medium' | 'high', required
  - `single_session_per_actor` boolean: When true, only one open session per actor_id is allowed for this agent. Creating a second open session for the same actor returns 409.
  - `trace_content_mode` 'full' | 'none' | 'null', nullable: Agent-scope zero-retention setting. `null` (the default) inherits the project's `trace_content_mode`; `none` means this agent's trace and generation content is never persisted. An agent may tighten a storing project to `none` but cannot loosen a `none` project back to `full`.
  - `on_approval_expiry` 'terminate' | 'react' | 'null', nullable: What happens when one of this agent's held tool calls expires un-approved. `null` (the default) and `terminate` end the chain there — the expired approval, its `approvals.expired` event and the auto-filed `approval_expired` exception are the whole record. `react` spawns a continuation that reports the staleness to the agent, for an agent that acts on it.
  - `version` integer: Current config version. Starts at 1 and increments on every write that changes the config; each increment archives the new config as an `AgentVersion`. A write that changes nothing leaves it untouched.
  - `active_release` AgentRelease: A staged rollout splitting traffic between two archived versions. See [Versioning and Staged Rollout](/docs/modules/agents#versioning-and-staged-rollout).
    - `stable_version` integer, required: Version served to traffic not assigned to the canary
    - `canary_version` integer, required: Version under trial. Must differ from `stable_version`.
    - `canary_percent` integer, required: Percentage of traffic assigned to `canary_version`
    - `promotion_gate` string, nullable: Eval that must have a passing run against `canary_version` before `promote` is allowed, or null for an ungated rollout. The gate constrains only how the rollout ends — traffic is split the same way either way. See [Eval-gated promotion](/docs/modules/agents#eval-gated-promotion).
  - `created_at` string, date-time
  - `updated_at` string, date-time

## Other responses

- `401`: Unauthorized
- `403`: Forbidden
- `404`: Not found
- `409`: The resource has moved past the version this write read (`VERSION_CONFLICT`): a stated `expected_version` or `If-Match` no longer matches, or a concurrent write took the version first. `meta.current_version` names the version in force. Nothing is written.

---

[API](https://skmtc.dev/naturali/apis/naturali-ai-api.md) · [All operations](https://skmtc.dev/naturali/apis/naturali-ai-api/llms.txt) · [OpenAPI document](https://skmtc.dev/naturali/apis/naturali-ai-api/revisions/e4905b78f422?raw)
