Skip to main content

Advanced: Productionize a Morning-Briefing Robot

Take Managed Agents to production — MCP toolkit integration, configuration injection (session environment variables and the vault), Deployment scheduling, human-in-the-loop review, and resource lifecycle management, all with curl.

Overview

This page covers the production hardening of Managed Agents: MCP toolkit integration (the marketplace registry model), configuration injection (session environment variables + the vault), platform-managed triggers (Deployment: manual / cron / initial_events), human review (HITL), resource lifecycle management, and the current data-residency behavior. Examples use curl throughout. The deep dive on credential injection (vault placeholder security model, egress gateway, allowed_hosts) lives in Advanced: Credential Vault Security and the Egress Gateway — this page only wires credentials up by the shortest path.

Scenario: Yunling Tea's morning briefing robot

"Yunling Tea" is a fictional chain tea-drink brand. The first thing its ops team does every morning is skim industry news: competitor launches, supply-chain ripples, any negative press involving tea drinks. It is repetitive, time-consuming, easy to miss — a textbook automation target. The morning-briefing robot strings together all the production infrastructure:
NeedCapability
Run automatically at 7:30 every morning, unattendedDeployment (cron trigger)
Carry the day's task description when triggeredDeployment (initial_events delivered at trigger)
Search industry newsMCP toolkit (marketplace WebSearch service)
Push the briefing to the company GitHub repoVault (inject GITHUB_TOKEN; placeholder + gateway replacement)
Pause for human confirmation on major negative pressHITL (requires_action + polling)
Seasonal prompt changes, quarterly shutdown, cleanupVersioning + resource lifecycle
End-to-end: create a vault, store a GitHub token credential, create an agent with the WebSearch toolkit → pilot manually with a session (prove the tool call really happens and the credential path really works) → put scheduling + trigger-and-run onto the platform with a Deployment → verify version locking and the ops verbs (pause / archive) → finally clean up in dependency order.
Why the vault instead of session environment variables on the scheduled path? Because Deployment does not support environment_variables (silently ignored when passed; the triggered session has none) — vault_ids is the only credential path for unattended pipelines. For application-created sessions, environment variables come first (see Step 4).

Prerequisites

  • A Bailian workspace and its workspace ID;
  • one DashScope API Key (one key covers all resources in the workspace);
  • one GitHub PAT (the dummy value GITHUB_TOKEN below — this page only verifies the injection, and a fake token is safer).
Agreed environment variables:
export AGENTSTUDIO_URL="https://{workspace_id}.cn-beijing.maas.aliyuncs.com/api/v1/agentstudio"
export DASHSCOPE_API_KEY="<your DashScope API Key>"
export GITHUB_TOKEN="ghp_DUMMY_TOKEN_FOR_SMOKE_TEST"
Managed Agents is currently single-region (cn-beijing). All requests carry Authorization: Bearer $DASHSCOPE_API_KEY; responses carry x-request-id for troubleshooting (the body also echoes request_id).

The model: two extension lines + one trigger line

Two mutually independent lines matter most in production for extending an agent:
  • MCP toolkits: the agent declares one or more marketplace services; at runtime the platform calls those services' tools on the agent's behalf. Fits "service on the public internet, hosted by the Bailian marketplace" — no connection details to manage, and usually no key either.
  • Configuration / credential injection (two routes, layered by sensitivity):
    • Session environment variables (environment_variables, first choice): key-value pairs passed at session creation, cleartext straight through to the sandbox process; the agent reads real values via os.environ in bash / python. Fits non-sensitive config and interactive sessions — simplest, nothing to pre-create. Limitation: Deployment does not support them.
    • The vault: credentials are stored in the vault beforehand; sessions only carry vault_ids. Inside the sandbox you get a placeholder (BMA_SECRET_PLACEHOLDER_*); the real value is substituted into the Authorization header only when the egress gateway matches the credential's allowed_hosts whitelist. Fits high-sensitivity secrets and unattended pipelines. Full security model in Advanced: Credential Vault Security and the Egress Gateway.
The combination is the complete picture: platform-level external capabilities via MCP; user-level private credentials via vault (secrets) or environment variables (config). There is also a third pattern the getting-started page did not expand: custom tools — your own services. The agent throws the call back to your application as a function_call event; you compute and backfill with function_call_output. Use this when the service is only reachable on your internal network. This page adds the trigger line: in the getting-started usage, your application creates the session and sends the messages — "who sends the task at 7:30 every morning" can only be a homegrown cron script. Deployment hands that role to the platform: declaratively configure "which agent, locked to which version, which messages delivered at trigger, which credentials attached, what schedule" — from then on the platform does the triggering, session creation, and initial_events delivery; your application only reads the results.
Security boundary: once a token is stored in the vault it never enters the sandbox — printenv shows only a placeholder; at egress the gateway substitutes per the allowed_hosts whitelist. The placeholder guarantee covers environment variables and outbound requests (before substitution); the response body of the called service (e.g. an echo endpoint returning Authorization verbatim) enters the event stream as tool output — that is the external service's own behavior and outside the placeholder guarantee. Two more boundaries: the gateway only replaces the Authorization header; sandbox egress HTTPS passes through a gateway MITM, but the sandbox preinstalls the gateway CA (SSL_CERT_FILE / CURL_CA_BUNDLE / REQUESTS_CA_BUNDLE all point to it) — curl and requests pass default validation; investigate CA configuration only when a specific environment hits certificate errors. Details and troubleshooting in Advanced: Credential Vault Security and the Egress Gateway.

Step 1 · Create the vault

A vault has two key fields: display_name (for the console) and metadata (your own internal user ID etc.):
curl -X POST "$AGENTSTUDIO_URL/vaults" \
  -H "Authorization: Bearer $DASHSCOPE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "display_name": "云岭茶饮 运营部",
    "metadata": {
      "internal_user_id": "u_yunling_ops",
      "team": "operations"
    }
  }'
The response returns an id shaped like vlt_01M1G0W54ARXJ4W61B0N4281TK:
export VAULT_ID="vlt_xxx"

Step 2 · Store the GitHub token credential

Credentials live under the vault at the nested endpoint /vaults/{vault_id}/credentials (there is no top-level /credentials; it 404s). Each credential = variable name + value + egress whitelist. auth.type currently supports only environment_variable (static_bearer / mcp_oauth return CREDENTIAL_AUTH_TYPE_ERROR). allowed_hosts is required and must sit inside auth.networking (omitting it → 409 CREDENTIAL_AUTH_NETWORKING_ERROR "egress network address must not be empty"):
curl -X POST "$AGENTSTUDIO_URL/vaults/$VAULT_ID/credentials" \
  -H "Authorization: Bearer $DASHSCOPE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "display_name": "GitHub PAT(晨报推送)",
    "auth": {
      "type": "environment_variable",
      "secret_name": "GITHUB_TOKEN",
      "secret_value": "'"$GITHUB_TOKEN"'",
      "networking": { "allowed_hosts": ["api.github.com", "github.com", "*.github.com"] }
    }
  }'
Points:
  • secret_name: the variable name. Inside the sandbox, os.environ["GITHUB_TOKEN"] yields the placeholder BMA_SECRET_PLACEHOLDER_GITHUB_TOKEN — the real value is substituted into the Authorization header only for egress requests hitting whitelisted hosts.
  • secret_value: the real secret. Write-only — a GET returns only secret_name / type / networking in auth, never the value.
  • networking.allowed_hosts: the domain whitelist the secret may travel to, with wildcards (*.github.com). The token is only substituted into requests to these hosts — requests to other domains keep the placeholder, preventing exfiltration.
  • The response id is prefixed vcrd_ (not cred_).
export CRED_ID="vcrd_xxx"

Step 3 · Create the agent with an MCP toolkit

Three things to prepare separately: declare the MCP server and toolkit in the agent definition, the credential in the vault (Step 2), and wire with vault_ids at session / deployment creation (Steps 4 / 5). Create the agent. mcp_servers declares the marketplace service with type: "official" and name set to the marketplace service code; enable tools via mcp_toolkit:
curl -X POST "$AGENTSTUDIO_URL/agents" \
  -H "Authorization: Bearer $DASHSCOPE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "morning-briefing",
    "description": "云岭茶饮行业晨报机器人:定时搜索行业动态并汇总",
    "model": { "id": "qwen3.7-plus" },
    "system": "你是云岭茶饮(连锁茶饮品牌)的晨报助理 [v1]。收到任务后:1) 用 bailian_web_search 搜索过去 24 小时茶饮/餐饮行业的重要动态;2) 把发现汇总成不超过 5 条的晨报,每条一句话带来源。输出开头带 [v1] 标记。不要打印任何凭证的值,除非任务明确要求验证注入。",
    "mcp_servers": [
      { "type": "official", "name": "WebSearch" }
    ],
    "tools": [
      {
        "type": "builtin_toolkit",
        "default_config": { "enabled": true, "permission_policy": { "type": "always_allow" } },
        "configs": [ { "name": "bash", "enabled": true } ]
      },
      {
        "type": "mcp_toolkit",
        "mcp_server_name": "WebSearch",
        "default_config": { "enabled": true },
        "configs": [ { "name": "bailian_web_search", "enabled": true } ]
      }
    ]
  }'
Two key details:
configs must enable tools one by one. mcp_toolkit mirrors builtin_toolkit: default_config.enabled: true alone is not enough — every tool you want (here bailian_web_search) must be explicitly enabled: true in configs[ ], otherwise the agent does not have the tool at runtime, with no error raised.
mcp_servers.name is not existence-checked at declaration time. A name absent from the marketplace still creates successfully; the tool is silently missing at runtime and the agent replies it has no tools. To troubleshoot: send a message that forces a tool call and check for mcp_call in the event stream. List available services with the CLI: bl mcp list (activated MCP servers with code / tools).
Marketplace services are called through the platform proxy at endpoints like https://{workspace_id}.cn-beijing.maas.aliyuncs.com/api/v1/mcps/{code}/sse — you generally provide no credentials; for services needing your own key, store it in the vault and call directly from bash with an Authorization header (bash sees the placeholder; the gateway substitutes the real value).
export AGENT_ID="agent_xxx"
If you also need to run scripts (read credentials, process files) besides MCP tools, add a builtin_toolkit (bash/read/write, etc.) as covered in the getting-started page — the two coexist fine. A pure-MCP agent can even skip the environment — a session with an empty environment_id runs too.

Step 4 · Manual pilot: verify tools and the credential path

Before scheduling, run one plain turn verifying two things: the tool call really happens, and the credential path really works.

First choice: session environment variables (non-sensitive config / interactive sessions)

For application-created sessions, the first choice for config injection is environment_variables — cleartext key-value pairs straight into the sandbox, no vault to pre-create, real values directly available via os.environ:
curl -X POST "$AGENTSTUDIO_URL/sessions" \
  -H "Authorization: Bearer $DASHSCOPE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "agent": "'"$AGENT_ID"'",
    "title": "Morning briefing pilot",
    "environment_variables": { "BRIEF_MAX_ITEMS": "5", "LOG_LEVEL": "info" }
  }'
Verify: have the agent run printenv BRIEF_MAX_ITEMS → outputs 5 (the real value). The creation response and GET echo the field — only put non-sensitive content there. Interactive session + non-sensitive config → environment variables are enough. Need secret-grade security (no cleartext in the sandbox) or the Deployment unattended path → wire the vault below.

Required on the scheduled path: vault_ids wiring

Deployment does not support environment_variables, so scheduled-path credentials (Step 5) can only use the vault. Attach credentials at session creation with vault_ids (the agent field takes the agent ID string; the session locks that agent's current version snapshot):
curl -X POST "$AGENTSTUDIO_URL/sessions" \
  -H "Authorization: Bearer $DASHSCOPE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "agent": "'"$AGENT_ID"'",
    "title": "Morning briefing pilot",
    "vault_ids": [ "'"$VAULT_ID"'" ]
  }'
export SESSION_ID="sesn_xxx"
sessions.create accepts no initial input field; the standard pattern is two steps — create the session, then POST one message event. (For "trigger with the first message attached", use Deployment below, which supports initial_events.)
Send a message to verify placeholder injection — have it run printenv GITHUB_TOKEN verbatim:
curl -X POST "$AGENTSTUDIO_URL/sessions/$SESSION_ID/events" \
  -H "Authorization: Bearer $DASHSCOPE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "input": [
      {
        "type": "message",
        "role": "user",
        "content": [
          { "type": "text", "text": "Run exactly this bash command and report the raw output, nothing else: printenv GITHUB_TOKEN" }
        ]
      }
    ]
  }'
Observe over the live SSE stream (consumption as in the getting-started page; the event: line is always message, comment lines include :connected / :HTTP_STATUS/200 / :keepalive):
curl -N "$AGENTSTUDIO_URL/sessions/$SESSION_ID/events/stream" \
  -H "Authorization: Bearer $DASHSCOPE_API_KEY" \
  -H "Accept: text/event-stream"
The placeholder-verification turn's stream:
session_status   {"session_status": "running"}
message          (user message echo)
model_request_start
tool_call        bash  {"command": "printenv GITHUB_TOKEN", ...}
tool_call_output bash  {"stdout":"BMA_SECRET_PLACEHOLDER_GITHUB_TOKEN\n","exit_code":0}
message          (assistant report)
session_status   {"stop_reason": {"type": "end_turn"}, "session_status": "idle"}
Seeing the placeholder is the sign injection worked. For the end-to-end secret check (that egress replacement really happens): send a request to a whitelisted host and observe what the recipient sees, per Advanced: Credential Vault Security and the Egress Gateway. The real GitHub push works the same way — the agent puts the placeholder in the Authorization header, the gateway swaps in the real PAT, and GitHub authenticates. Send another message to trigger the MCP tool ("search the latest tea-drink industry news with the tool"); MCP calls produce their own event types, parallel to tool_call:
mcp_call        role=assistant  data: {"name": "bailian_web_search",
                                 "server_label": "WebSearch",
                                 "arguments": "{\"query\": \"茶饮行业 最新动态\", \"count\": 10}",
                                 "call_id": "call_xxx"}
mcp_call_output role=tool       data: {"output": "<JSON returned by the MCP server>"}
That is: built-in tools appear as tool_call / tool_call_output; MCP tools as mcp_call / mcp_call_output. The turn ends when session_status = idle and stop_reason.type == end_turn. Pilot passed — both foundations work. Next, hand "who sends the task at 7:30" to the platform.

Step 5 · Deployment: scheduling on the platform

A Deployment is a resource at the same level as agent / environment / session: declare "which agent version to lock, what to deliver at trigger, which credentials to attach, what schedule" — the platform then does the triggering, session creation, and initial_events delivery. Console operations are covered in Deployments.

Create: version-locked agent + initial_events + vault_ids

curl -X POST "$AGENTSTUDIO_URL/deployments" \
  -H "Authorization: Bearer $DASHSCOPE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "morning-briefing-prod",
    "description": "云岭茶饮行业晨报:手动触发试点",
    "agent": { "id": "'"$AGENT_ID"'", "version": 1 },
    "initial_events": [
      {
        "type": "message",
        "role": "user",
        "content": [
          { "type": "text", "text": "这是今天的晨报任务:1) 用 bailian_web_search 搜索「茶饮行业 最新动态」,只采用今天日期往前 24 小时内发布的内容,每条附发布日期与来源 URL,找不到满足时间窗的资讯就明确说明;2) 用 bash 执行 printenv GITHUB_TOKEN 验证密钥已注入:只需确认输出是否以 BMA_SECRET_PLACEHOLDER_ 开头(沙箱内看不到真实值,不要尝试打印它);3) 把通过时间窗的资讯整理成不超过 3 条的晨报,每条一句话。" }
        ]
      }
    ],
    "vault_ids": [ "'"$VAULT_ID"'" ],
    "metadata": { "team": "operations", "purpose": "daily-briefing" }
  }'
  1. agent must be an object {id, version}; an ID string is rejected (400 PARAMS_ILLEGAL). This differs from sessions.create (a string = lock latest). A Deployment is a long-lived resource and locks the agent version explicitly at creation — the response echoes the full snapshot of that version (system / tools / model). Version semantics in "Version semantics: locking and the drift trap" below.
  2. initial_events is the input array delivered at trigger time, shaped exactly like the input used to message a session. It fills the gap left by sessions.create not accepting initial input: in the generated session, the first user message is it — "trigger and run", with no follow-up message needed from your application.
  3. vault_ids is wired at the deployment level: every session generated by a trigger automatically carries these credentials (injection behavior is identical to manual session creation).
Omitting schedule gives the manual mode (trigger only via POST /run); providing it enables scheduling (see the cron section below). A successful creation (200) echoes all fields with status: "active" and an id like depl_xxx (prefix depl_).
export DEPLOYMENT_ID="depl_xxx"

Trigger: run and deployment_run

curl -X POST "$AGENTSTUDIO_URL/deployments/$DEPLOYMENT_ID/run" \
  -H "Authorization: Bearer $DASHSCOPE_API_KEY"
Returns a deployment_run (prefix drun_); session_id is null until async generation completes:
{
  "id": "drun_xxx",
  "type": "deployment_run",
  "agent": { "id": "agent_xxx", "version": 1 },
  "status": "running",
  "error": null,
  "deployment_id": "depl_xxx",
  "session_id": null,
  "trigger_source": "manual",
  "started_at": "2026-09-02T03:05:16.763Z",
  "finished_at": null
}
Query the run history a few seconds later to get the session_id (also how production decides "is it done" — status becomes succeeded, finished_at set):
curl "$AGENTSTUDIO_URL/deployments/$DEPLOYMENT_ID/runs" \
  -H "Authorization: Bearer $DASHSCOPE_API_KEY"
The response shape matches the versions API: {data: [...], next_page, request_id}, newest first. trigger_source distinguishes "manual" from "schedule" (cron).

Trigger and run: one full trace

Pull events for the generated session (GET /sessions/{id}/events?order=asc, or SSE); a typical trigger yields about 26 events, key frames:
session_status   running
message          (user) 这是今天的晨报任务:1) 用 bailian_web_search 搜索……   ← initial_events delivered
mcp_call         bailian_web_search  {"query": "茶饮行业 最新动态", "count": 10}
tool_call        bash  printenv GITHUB_TOKEN
mcp_call_output  (industry-news JSON from WebSearch)
tool_call_output (BMA_SECRET_PLACEHOLDER_GITHUB_TOKEN)
message          (assistant) [v1] 云岭茶饮晨报 · 2026年9月2日 + 3 news items
session_status   idle  stop_reason={"type": "end_turn"}
One trigger accomplishes three things: initial_events enters the session as the first user message, the MCP tool is invoked, and vault credentials inject into the deployment-generated session (placeholder in the sandbox, gateway replacement at egress). Your application sent no messages at all — triggering and execution are fully handled by the platform.

Version semantics: locking and the drift trap

A deployment locks {id, version} at creation; later agent upgrades do not affect it. Upgrade morning-briefing's prompt from v1 to v2 (POST /agents/{id}; name + current version required, see Advanced: Prompt Versioning and Rollback), then trigger the same deployment:
deployment_run.agent.version = 1          ← still v1
session output prefix = [v1]               ← ran the v1 prompt
To upgrade the version a deployment runs, update it with POST /deployments/{id} (PATCH semantics; omitted fields are preserved). But beware a production trap:
When an update omits the agent field, the agent drifts to the latest version. Changing only description (no agent in the body) flipped agent.version from 1 to 2 in the response — the prompt silently upgraded. The safe practice: always explicitly carry agent {id, version} on every deployment update:
curl -X POST "$AGENTSTUDIO_URL/deployments/$DEPLOYMENT_ID" \
  -H "Authorization: Bearer $DASHSCOPE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "description": "云岭茶饮行业晨报:手动触发试点(更新描述)",
    "agent": { "id": "'"$AGENT_ID"'", "version": 2 }
  }'
(Explicitly passing agent {id, version: 1} locks it back to v1 too.)
For contrast: a session locks "latest-or-specified at creation" as a snapshot; a deployment locks "explicitly specified at creation", but updates without agent drift to latest.

cron: scheduled triggering

Adding schedule switches the deployment to scheduled mode. To verify scheduling use an every-minute expression (switch to your business timetable for real use):
curl -X POST "$AGENTSTUDIO_URL/deployments" \
  -H "Authorization: Bearer $DASHSCOPE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "morning-briefing-cron",
    "agent": { "id": "'"$AGENT_ID"'", "version": 1 },
    "initial_events": [ ... same as above ... ],
    "schedule": { "type": "cron", "expression": "* * * * *", "timezone": "Asia/Shanghai" }
  }'
Behavior (expression * * * * *, every minute):
  • Auto-triggers precisely on the minute; a new run with trigger_source: "schedule" appears in the runs list, structurally identical to a manual run (same session creation, same initial_events).
  • The schedule object in the response carries next_run_at / last_run_at — to know "is the schedule alive, when is the next run", query the deployment object instead of computing cron yourself.
  • The real briefing scenario: "expression": "30 7 * * *" (07:30 daily), "timezone": "Asia/Shanghai".

Ops verbs: pause / unpause / archive

VerbEndpointBehavior
Pause schedulePOST /deployments/{id}/pausestatus: "paused", paused_reason: {type: "manual"}; cron stops firing, next_run_at freezes
ResumePOST /deployments/{id}/unpausestatus: "active"; resumes from the next schedule point; missed triggers during pause are not replayed
ArchivePOST /deployments/{id}/archivearchived_at set, next_run_at cleared (schedule removed); GET still works, lists filter archived by default
DeleteDELETE /deployments/{id}405 not supported — like agents, archive-only
Three confusable points:
  • pause blocks only the schedule, not manual triggers: POST /run against a paused deployment still triggers and runs normally. Use pause when you need to stop the schedule but keep manual triggering.
  • archive is the full stop: POST /run against an archived deployment returns 409 DEPLOYMENT_STATE_CONFLICT "deployment archived" — manual triggers fail too.
  • Concurrent triggers are not limited: issuing POST /run again while a previous run is still in progress creates another run, and both run to completion. Control concurrency on the trigger side (or adjust the schedule in an update) if you need limits.

Observability summary

QuestionWhere to look
Is the schedule alive; when does it next runGET /deployments/{id} → status + schedule.next_run_at
Did a given run finishGET /deployments/{id}/runs → status / finished_at
What did a run dothe run's session_id → GET /sessions/{id}/events (or SSE)
Did it failthe run's error field + the session's session_status events
For "notify me when it finishes", this page's approach is active querying (poll runs / the deployment object); the platform-side push (Webhook) is covered in Advanced: Webhook Event Notifications — subscribe to session.status_idled and let the platform POST your receiver.

Step 6 · HITL in production

Production human review should not depend on long connections. An approval window can span minutes to hours; holding an SSE connection across it neither scales (connections bind to instances) nor survives restarts (one deploy drops all in-flight approvals). The right shape: let the session park in a waiting state; your service fetches status, decides, and backfills when needed. Managed Agents sessions natively support this "stop and wait" semantics — when the agent needs human confirmation for a tool call, the session turns idle with requires_action and stays there until you backfill the decision.

Option A: poll history + requires_action

Your service periodically pulls the latest history event and checks whether the session is parked in "needs human/tool intervention": the last event shows session_status: idle with stop_reason: requires_action, carrying pending_batch_id and the pending pending_call_ids.
# Pull the latest history event (last one, descending)
curl -X GET "$AGENTSTUDIO_URL/sessions/$SESSION_ID/events?order=desc&limit=1" \
  -H "Authorization: Bearer $DASHSCOPE_API_KEY"
Polling loop (pseudocode):
every N seconds:
  GET /sessions/{id}/events?order=desc&limit=1
  read the last event's session_status / stop_reason
  if stop_reason == requires_action:
      extract the pending tool call (batch_id + pending_call_ids)
      escalate it into your human review queue
  if stop_reason == end_turn:
      turn complete, stop polling
Once a human decides, backfill with a tool_approval_response event (batch_id from stop_reason.pending_batch_id, call_id from the matching entry in pending_call_ids):
curl -X POST "$AGENTSTUDIO_URL/sessions/$SESSION_ID/events" \
  -H "Authorization: Bearer $DASHSCOPE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "input": [
      {
        "type": "tool_approval_response",
        "role": "user",
        "content": [
          {
            "type": "data",
            "data": {
              "batch_id": "<pending_batch_id>",
              "call_id": "<matching pending_call_ids entry>",
              "result": "allow"
            }
          }
        ]
      }
    ]
  }'
For a rejection, set result to deny. When a batch has multiple pending calls, backfill them one by one (one call_id each); tools execute only after the whole batch is collected. To backfill a self-hosted function result, use function_call_output (with call_id + output). In the briefing scenario: before the agent writes a "negative-press alert" into the company repo, have it call an action requiring confirmation; the session parks at requires_action; your service notices via polling, pushes to the ops on-call group; a human clicks allow/deny; you backfill tool_approval_response; the agent continues. Tool approval configuration is in Built-in Tools.
On cost caps: sessions currently provide no budget field and no over-limit notification event. Control cost at the Deployment layer via trigger frequency and concurrency limits, plus after-the-fact usage reconciliation.

Option B: Deployment trigger + query afterwards

If the trigger is "scheduled" or "one task fired by an external system", use the Step 5 Deployment directly: the external system just calls POST /deployments/{id}/run (or leaves it to cron), then collects results via GET /deployments/{id}/runs — status reaching succeeded only means execution finished; also verify the artifact content programmatically (e.g. check each briefing item's publish date falls inside the time window and the source URL parses) instead of treating run status as content acceptance. session_id leads to the artifacts. No long connection anywhere.

Step 7 · Data residency and region

Current behavior: Managed Agents is single-region (cn-beijing); agent inference and sandbox execution both happen in that region. There is no inference-region parameter and no per-session region override at sessions.create. This section therefore needs no configuration: residency is guaranteed by the single-region fact. If your compliance requirement is "data stays in a region", the current shape satisfies it; multi-region selection will follow official releases.

Step 8 · Resource lifecycle: list / retrieve / update / archive / delete

Each resource has a set of verbs, but not every resource supports the full set:
ResourceList/getUpdateArchiveDelete
agentsGET /agents, /agents/{id}POST /agents/{id} (auto version bump)POST /agents/{id}/archivenot supported (DELETE 405)
environmentsGET /environments, /{id}POST /environments/{id}POST /{id}/archiveDELETE supported
sessionsGET /sessions, /{id}—POST /{id}/archiveDELETE supported
deploymentsGET /deployments, /{id} (lists filter archived)POST /deployments/{id} (mind agent drift)POST /{id}/archivenot supported (405)
vaultsGET /vaults, /{id}POST /vaults/{id}POST /{id}/archiveDELETE supported
credentialsGET /vaults/{id}/credentials——DELETE /vaults/{vid}/credentials/{cid}
List agents:
curl -X GET "$AGENTSTUDIO_URL/agents?limit=5" \
  -H "Authorization: Bearer $DASHSCOPE_API_KEY"
Get one agent:
curl -X GET "$AGENTSTUDIO_URL/agents/$AGENT_ID" \
  -H "Authorization: Bearer $DASHSCOPE_API_KEY"
Update an agent. Three notes: the verb is POST /agents/{id}, not PATCH (405); the body must carry the current version for optimistic concurrency; it must carry name (otherwise AGENT_005 agent name must not be empty). A successful update bumps the version by 1:
curl -X POST "$AGENTSTUDIO_URL/agents/$AGENT_ID" \
  -H "Authorization: Bearer $DASHSCOPE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "morning-briefing",
    "version": 1,
    "system": "你是云岭茶饮(连锁茶饮品牌)的晨报助理 [v2]。……(新 prompt)"
  }'
List historical versions:
curl -X GET "$AGENTSTUDIO_URL/agents/$AGENT_ID/versions" \
  -H "Authorization: Bearer $DASHSCOPE_API_KEY"
Archive vs delete: archive keeps records for audit, tears down the container, stops metering; delete removes entirely. Prefer archive in production. Agents and deployments are archive-only; version history and run records remain queryable after archiving.

Step 9 · Cleanup

Cleanup has a dependency order, plus one trap: deleting a vault does not cascade to its credentials. Delete the vault first and the credential endpoint still works (orphan data). The correct order is child-before-parent:
# 1. Delete the pilot session (DELETE supported, 200)
curl -X DELETE "$AGENTSTUDIO_URL/sessions/$SESSION_ID" -H "Authorization: Bearer $DASHSCOPE_API_KEY"

# 2. Archive the deployment (DELETE 405, archive only; archiving removes the schedule)
curl -X POST "$AGENTSTUDIO_URL/deployments/$DEPLOYMENT_ID/archive" -H "Authorization: Bearer $DASHSCOPE_API_KEY"

# 3. Delete the environment (DELETE supported)
curl -X DELETE "$AGENTSTUDIO_URL/environments/$ENV_ID" -H "Authorization: Bearer $DASHSCOPE_API_KEY"

# 4. Archive the agent (DELETE 405, archive only)
curl -X POST "$AGENTSTUDIO_URL/agents/$AGENT_ID/archive" -H "Authorization: Bearer $DASHSCOPE_API_KEY"

# 5. Delete the credential (child) first, then the vault (parent) — order matters
curl -X DELETE "$AGENTSTUDIO_URL/vaults/$VAULT_ID/credentials/$CRED_ID" -H "Authorization: Bearer $DASHSCOPE_API_KEY"
curl -X DELETE "$AGENTSTUDIO_URL/vaults/$VAULT_ID" -H "Authorization: Bearer $DASHSCOPE_API_KEY"
Deployment-generated sessions are deletable the same way: collect all session_id values from GET /deployments/{id}/runs and DELETE each (note: a running session cannot be deleted — wait for idle).

Summary

This page assembled the production-operations skeleton:
  • MCP toolkits (mcp_servers official + mcp_toolkit configs enabling tools one by one) let agents call marketplace services through the platform proxy, visible as mcp_call / mcp_call_output in the stream.
  • Config injection has two layers: non-sensitive config via session environment variables (cleartext into the sandbox, first choice for interactive sessions); high-sensitivity secrets via the vault (placeholder injection + gateway replacement per allowed_hosts; the token never touches your application, the agent config, or the sandbox) — the scheduled (Deployment) path supports only the latter.
  • Deployment absorbs triggering into the platform: agent {id, version} locks explicitly, initial_events delivers the first message at trigger, cron fires automatically, pause (blocks schedule, not manual) / unpause (missed runs not replayed) / archive (full stop) manage the lifecycle, GET runs collects results.
  • Production HITL two ways: poll requires_action + backfill tool_approval_response, or Deployment trigger + query runs afterwards.
  • Lifecycle verbs differ by resource: agents and deployments are archive-only; sessions / environments / vaults are deletable; delete credentials before vaults (no cascade).
Six current behaviors to keep in mind: deployment updates without agent drift to latest (always pass {id, version}), no concurrency rejection on triggers (rate-limit yourself), no session budget field (throttle at the deployment layer), single region cn-beijing, mcp_servers.name is not existence-checked (typos fail silently), sandbox egress goes through gateway MITM (gateway CA preinstalled, default TLS validation passes; investigate CA configuration only for environment-specific errors).

Next steps

Agent Context
Agent Governance & Evolution
Store
Application Marketplace