Strands Studio (方式C) is the platform's visual creation method: a
drag-and-drop canvas that generates Strands Agent SDK code. It lives inside the
platform console at /create/studio (frontend/src/pages/CreateAgentStudio.tsx
frontend/src/studio/), is fully bilingual, publishes throughPOST /api/agents, and persists the flow graph for edit/re-publish. See Native canvas below.
Strands Studio(方式C)是平台的可视化创建方式:拖拽式画布生成 Strands Agent
SDK 代码。画布内置在平台控制台的 /create/studio,完整双语,经
POST /api/agents 发布,并持久化画布图以支持再编辑。
Earlier versions also shipped a vendored copy of the upstream app under
apps/studio/ (own frontend :5273 and backend). It was removed because its
backend served POST /api/execute*, /api/conversations/*/messages* and
/api/deploy/* without any authentication, ran submitted code in-process with
exec(), and its Vite server listened on 0.0.0.0 while proxying /api/* to
that backend — anyone who could reach the frontend port could execute code with
the backend's privileges and AWS credentials. The native canvas covers the same
creation flow, and its local-debug execution runs behind the console session,
out-of-process, and is disabled in production by default (see
architecture.md, Studio local debug).
早期版本在 apps/studio/ 下附带上游应用的 vendored 副本(独立前端 :5273 与后端)。
该副本已删除:其后端的 POST /api/execute*、/api/conversations/*/messages*
与 /api/deploy/* 没有任何鉴权,提交的代码以进程内 exec() 执行,且 Vite 监听
0.0.0.0 并把 /api/* 转发给该后端,能访问前端端口的任何人都可以以后端的权限和
AWS 凭证执行代码。原生画布覆盖同样的创建流程,其本地调试执行需要控制台会话、在独立
进程中运行,且在生产模式下默认关闭。
- Upstream: xiehust/strands_studio_ui. The native canvas ports its code generators/validators and the local-debug execution and conversation services; see Porting invariants for the current baseline.
- Attribution: "Open Studio for Strands Agent" by xiehust. The upstream repository declares no OSS license file; the ported code is included with this attribution, and an upstream license, if later added, governs it.
/create/studio canvas ──generate code──▶ Publish
│ POST /api/agents {name, method: "studio", code, studio_flow}
▼
platform pipeline (zip fast path)
generate – adapt_studio_code(): verbatim module + entrypoint wrapper
package – pip (aarch64 manylinux wheels) → zip → S3
provision – shared execution role
deploy – CreateAgentRuntime → poll READY
register – A2A registry record, auto-submitted
The canvas follows the platform job like every other creation method; the
agent then appears in the platform launch feed with a STUDIO method chip.
backend/app/templates/studio_agent/adapt_studio_code() converts the studio
script into an AgentCore module without rewriting user code:
- Tools the platform no longer packages are stripped first. Canvas code
generated before
0b380650(2026-08-10) importsstrands_tools.mem0_memory, whosemem0extra the package stage stopped installing, so a rebuild (redeploy, canary mint or rollback) would die at import behind a generic 30 s init timeout. The name is dropped from the generator's one-linestrands_toolsimport and from any list literal (tools=[…]); string literals are never touched. This edits generator-owned lines only, never user-written code. - If the code already contains
@app.entrypoint, it is used as-is. - Otherwise the module is kept verbatim (imports, MCP clients, model
config,
async def main(...)), the trailing argparse__main__block is cut, and aBedrockAgentCoreAppentrypoint is appended that callsmain()(arity-probed:main(),main(user_input), ormain(user_input, messages)) and captures its streamed stdout as the result. - The platform config-bundle shim (
launchpad_config_bundle()) is injected unless the code already readsget_config_bundle— studio authors can call it to opt into A/B config bundles (system prompt overrides are never forced onto arbitrary user code).
An earlier iteration used upstream's code_adapter.py section extractor; it
was dropped because its keyword heuristics silently lose module-level MCP
client definitions.
Requirements added on top of the studio code: the platform zip baseline
(strands-agents[otel], bedrock-agentcore, aws-opentelemetry-distro for
ADOT traces) plus strands-agents-tools (no extras), which studio's
generated import line always references. When any node uses the OpenAI or
Amazon Bedrock (Mantle) provider, CreateAgentStudio.tsx adds
strands-agents[openai] to spec.requirements — both providers import
openai at module top level (shipped only via that extra, which also pulls
the Bedrock token generator Mantle auth needs). Prompt caching, adaptive
thinking, and skills need no extra.
Mantle auth is IAM by default — no key. A Mantle node with no apiKey
generates OpenAIResponsesModel(bedrock_mantle_config={"region": …}), and the
Strands SDK mints a short-lived bearer token from the ambient AWS credential
chain (the Runtime execution role, which already holds bedrock:InvokeModel) on
every request, deriving the endpoint itself. A node that does carry an apiKey
keeps the older override form,
client_args={"api_key": os.environ.get("BEDROCK_API_KEY"), "base_url": …}, so
flows published before this became the default generate byte-identical code. The
SDK raises if both are given, so the emitter (mantleModelArgs in
studio/lib/models.ts, shared by all three code generators) picks exactly one.
OpenAI-provider nodes still genuinely need a key, and only they raise the
"missing API key" publish warning.
Generated code reads OPENAI_API_KEY / BEDROCK_API_KEY from the runtime env;
the publish body maps each node's apiKey onto spec.env (first non-empty per
provider), which the deploy stage passes as environmentVariables
(platform-injected keys like LAUNCHPAD_MEMORY_ID win over same-named user env).
When a key is entered it lives in studio_flow → ledger spec in plaintext,
same exposure class as upstream's localStorage — acceptable for this demo
platform, and avoidable entirely on the default IAM path.
The canvas Skill node attaches a launchpad AGENT_SKILLS registry record
(the picker lists APPROVED records from GET /api/registry/attachables) to an
agent. The generator emits
plugins=[AgentSkills(skills=[os.path.join(_skills_dir, "<name>")])] plus a
module-level _skills_dir = os.environ.get("STUDIO_SKILLS_DIR") or str(Path(__file__).parent / "skills"). At runtime STUDIO_SKILLS_DIR is
unset, so the code resolves skills/<name>/ next to main.py.
build_zip() (backend/app/deployer/zip_runtime.py) bundles those dirs at
package time (studio method only):
- Regex the adapted code for
os\.path\.join\(\s*_skills_dir\s*,\s*"([a-z0-9-]+)"\s*\)(the same pattern upstream'sagentcore_deployment_serviceuses) → unique referenced skill names. - Resolve each name against the APPROVED AGENT_SKILLS records
(
registry_console.attachable_records(),name == s3 prefix segment,path = s3://{bucket}/skills/{name}/). - Download every object under that prefix into
pkg_dir/skills/{name}/(path-traversal guarded, 50 MB/skill cap) — the zip walk picks it up.
Any skill issue (missing/unapproved record, oversize, download error) logs +
skips, never fails the deploy — mirroring upstream. adapt_studio_code is
untouched (the _skills_dir line survives verbatim). AgentSkills /
CacheConfig / OpenAIResponsesModel all resolve from the zip pin
strands-agents[otel]>=1.0,<2 (→ 1.47.0), so no SDK bump is needed. No schema
change: skill refs are derived from the generated code, not a new field.
画布 Skill 节点把 launchpad 的 AGENT_SKILLS 注册表记录附加到 agent(选择器
仅列出 GET /api/registry/attachables 的 APPROVED 记录)。生成代码通过
plugins=[AgentSkills(...)] 引用 skills/<name>/;build_zip() 在打包阶段
(仅 studio 方式)按正则从生成代码提取被引用的技能名,解析 APPROVED 记录的 S3
前缀,下载到 pkg_dir/skills/{name}/(含路径穿越防护与 50 MB 上限),缺失/超限
/下载失败均记录并跳过,绝不阻断部署。无需 schema 改动或 SDK 升级。
The root lifecycle (./start.py, ./start.py --prod, or make dev) starts
only the platform processes:
| Service | Port | Override |
|---|---|---|
| platform backend | 8000 | PLATFORM_API_PORT |
| platform frontend | 5173 | PLATFORM_UI_PORT |
There is no separate Studio process: the Create Agent 方式C card links to the
canvas at /create/studio, an internal route of the platform frontend.
The canvas is a first-class platform page and is fully bilingual (studio.*
namespace, en + zh-CN parity enforced by scripts/verify.sh).
画布是平台一等页面,完整双语(studio.* 命名空间,en 与 zh-CN 平价由
scripts/verify.sh 校验)。
Code-spec for the native canvas publish contract (task
07-11-strands-studio-canvas; research + design in that task dir).
Cross-layer contract: the canvas page composes a flow, generates Strands code
client-side, and publishes via the platform API. One additive schema field
(AgentSpec.studio_flow) carries the graph for later edit/re-publish.
- Frontend codegen (pure, no IO):
generateStrandsAgentCode(nodes: Node[], edges: Edge[], graphMode = false) → { code: string; imports: string[]; errors: string[] }(frontend/src/studio/lib/code-generator.ts). Final file =imports.join('\n') + '\n\n' + code.errors.length > 0 ⇒ code === ''and publish must be blocked. - API:
POST /api/agents(create) /POST /api/agents/{id}/redeploy(re-publish;nameandmethodimmutable — backend 400s on change). - Schema:
AgentSpec.studio_flow: dict | None = None(backend/app/schemas/agent.py) — stored verbatim inside theAgent.specJSON column; every pipeline stage ignores it.
Publish body assembled by CreateAgentStudio.tsx:
| Field | Value | Constraint |
|---|---|---|
name |
user input (locked on re-publish) | ^[a-z][a-z0-9-]{2,47}$ |
method |
"studio" |
fixed |
system_prompt |
execution agent's systemPrompt (the agent/orchestrator/swarm reached from the input node), fallback "Strands Studio generated agent" |
trimmed to 20000; doubles as the registry A2A card description |
code |
imports + '\n\n' + code |
≤ 200000 chars (client-checked; schema max) |
requirements |
["strands-agents[openai]"] iff any node data.modelProvider is 'OpenAI' or 'Amazon Bedrock (Mantle)', else omitted |
base reqs + strands-agents-tools come from the backend, never sent by the client |
env |
{OPENAI_API_KEY?, BEDROCK_API_KEY?} — first non-empty apiKey per provider; omitted when empty (the Mantle default sets nothing — it authenticates with IAM) |
passed to the runtime as environmentVariables; platform keys win same-named conflicts |
memory |
{short_term: false, long_term: false} |
generated code manages no launchpad memory |
studio_flow |
{nodes, edges, graphMode} (React Flow arrays verbatim) |
round-trips into edit mode; carries skill nodes for the package-stage bundler |
Edit mode (/create/studio?agent=<id>): GET /api/agents/{id} →
spec.studio_flow restores the canvas. Studio agents WITHOUT studio_flow
(published by the former standalone app) degrade: banner + read-only spec.code, publish
disabled until a flow exists.
| Condition | Behavior |
|---|---|
generation errors[] non-empty |
toast + code drawer opens; publish blocked |
| name fails regex | publish button disabled |
| full code > 200000 chars | toast error, no POST |
| invalid canvas connection | onInvalidConnection callback → toast (never alert()) |
| redeploy with changed name/method | backend 400 (client locks name field instead) |
?agent= id missing/non-studio |
toast + redirect /agents |
- Good: input+agent+tool+output flow → publish →
active, chat/eval work (verified end-to-end 2026-07-11, agentstudio-canvas-e2e). - Base: agent-only edits (prompt/model) re-published to the same ARN, new runtime version, revision +1.
- Bad: flow without an input or output node → generator returns errors, code empty, publish blocked.
scripts/verify.sh(backend ruff/pytest, frontend eslint/tsc/build, i18n parity) — the standing gate.- E2E (manual/browser): canvas build → publish → LaunchSequence completes → chat responds → eval run completes → edit restores flow → re-publish.
- Round-trip assertion:
GET /api/agents/{id}echoesspec.studio_flowwith the exact{nodes, edges, graphMode}that was posted.
Editing a studio agent through the wizard (startEdit → buildSpec()):
buildSpec() does not carry code, so redeploy would silently replace the
canvas-generated module with template code.
CreateAgent.tsx routes Edit for method === "studio" to
/create/studio?agent=<id>; the canvas regenerates code from the restored
flow and posts it together with the updated studio_flow.
frontend/src/studio/lib/* (code-generator.ts, graph-code-generator.ts,
connection-validator.ts, graph-validator.ts, models.ts, sample-flows/*)
are copied from upstream xiehust/strands_studio_ui (eslint-ignored, still
tsc-checked). Current baseline: PR #31, merge 69318ab. The @/lib/models
alias is the only path rewrite → relative ./models. Local edits are tracked
in a TWO-CLASS LEDGER — re-apply ALL of it on every upstream re-sync:
Deviations (restore upstream behavior we keep): file_write in both
generators' static strands_tools import line AND tool map — upstream drops it
(silent calculator fallback); launchpad keeps it so saved graphs' tool nodes
don't downgrade. mem0_memory was kept the same way until 0b380650
(2026-08-10) and is now gone from both generators; a saved mem0_memory tool
node regenerates as calculator, and code published before then is cleaned up
by the adapter (see Artifact adaptation).
Extensions (launchpad-only features, every block comment-marked
// launchpad extension: in the generators; grep for that marker to relocate
them after a re-copy):
cacheSystemnode key →cache_prompt="default"in the Bedrock model config (system-prompt cache; deprecated-but-functional in strands 1.47, silent no-op under ~1k tokens — probed live).- Bedrock reasoning effort:
additional_request_fieldsgains"output_config": {"effort": "<low|medium|high|xhigh>"}besidethinking:{type:adaptive}(the ONLY accepted shape — probed);xhighis clamped tohighunless the model id containsclaude-sonnet-5/claude-opus-4-8(Claude 4.6-gen rejects it). max_tokensfallbacks 4000→32000 + emit-time clamps (nova-premier→32000,nova-pro→10000 — Nova Pro hard-caps at 10000, probed).- streaming gate fallback
?? true(execution agent only; explicitfalsein saved graphs respected). sample-flows/agent-with-mcp.tspoints at the public aws-knowledge MCP (https://knowledge-mcp.global.api.aws, streamable_http, no auth) instead of upstream's localhost placeholder.
Node components must preserve every React Flow Handle id/type/Position
and every node data key + destructuring default — the generators read them.
Styling is launchpad CSS tokens only (namespaced studio.css); no Tailwind.
上游库文件的本地改动采用两级台账:偏差(file_write 工具映射,
上游移除、launchpad 保留;mem0_memory 已于 2026-08-10 移除)与扩展(生成器中全部以 // launchpad extension: 注释
标记:cacheSystem→cache_prompt 系统缓存、Bedrock output_config.effort
推理档位(xhigh 仅 Sonnet 5/Opus 4.8,其余钳制为 high)、max_tokens 32000 默认
- Nova 钳制(Pro 10000/Premier 32000)、streaming 默认开、aws-knowledge MCP 样例)。重新同步上游时须完整重放两级台账。
The canvas debugs UN-deployed flows through one local-chat surface (ported
from upstream PR #31's conversation/fix subsystems; task
07-11-studio-local-debug-and-defaults):
- Exec env:
scripts/setup_exec_env.shprovisionsdata/exec-venv(strands-agents[openai] ≥1.47 + strands-agents-tools + mcp + bedrock-agentcore). Settings:studio_exec_python(both run and chat spawn THIS interpreter — upstream'ssys.executablevsuv runsplit is deliberately unified),execute_timeout_s(300). The control-plane backend venv stays lean (no strands, no openai) — debugging a flow locally requires this script, notuv sync. The[openai]extra is what carriesaws-bedrock-token-generator, so a Bedrock Mantle node debugs against your ambient AWS profile with noBEDROCK_API_KEY. The ≥1.47 floor tracks the verified SDK; the true minimum is 1.46, where theopenai.gpt-5.*→/openai/v1base-path split the default model needs landed (bedrock_mantle_configitself is older, but 1.45 served every Mantle id from/v1). A venv built without the[openai]extra fails withImportError: bedrock_mantle_config requires the 'aws-bedrock-token-generator' package— re-run the script (it upgrades in place). POST /api/execute[/stream]: backend compatibility endpoint only (not exposed in the native canvas UI); subprocess in a temp workdir (process-group kill,--user-inputflag, referenced registry skills bundled intoworkdir/skills/via the samebundle_skills_intothe deploy zip uses). Stream framing: each\nbecomes an emptydata:line; sentineldata: [STREAM_COMPLETE:<seconds>]./api/conversations*(8 endpoints): LOCAL debug chat — in-memory sessions, whole-history--messagesreplay (Bedrock Converse shape), failed turns pair-marked and excluded from replay, sentinels[CHAT_ERROR:<json>]/[CHAT_COMPLETE:<id>],PUT .../coderewrites the session's agent.py after an applied fix. Distinct namespace from/api/chat/*(deployed agents).- AI Fix:
POST /api/fix-code/stream(eventsprogress|agent_activity|validation|done|error|end) — diagnosis (code|config|environment; environment edits are reverted), ≤2 repair rounds, staged validators (contract AST → ruff → import smoke in the exec venv), revert-to-original when validation fails. Coding agent =claudeCLI via claude-agent-sdk over Bedrock (CLAUDE_CODE_USE_BEDROCK=1, modelcodegen_model).GET /api/generate-code/statusgates the UI button. Upstream's AI code GENERATION path is deliberately not ported. - Frontend CodeState
{code, source: template|ai, flowStale}inCreateAgentStudio: canvas changes regenerate template code; an applied fix flips source toai(canvas changes then only markflowStale); publish ships the ACTIVE code (fixed code publishable, drawer notes the divergence); "Regenerate from flow" discards fixes.
画布只通过本地对话调试未部署的流程:data/exec-venv 专用解释器运行生成代码,
技能目录与部署 zip 同源打包;/api/conversations* 提供内存会话、全量
--messages 重放和失败轮成对剔除,与已部署 agent 的 /api/chat/* 完全分离。
/api/execute[/stream] 仅作为后端兼容接口保留,原生画布不再提供独立运行面板。
AI 修复(/api/fix-code/stream)从本地对话失败进入,由 Bedrock 上的 Claude 编码
代理诊断并修复;环境类问题不改代码、校验不过则回滚原码。修复后的代码可继续
对话或直接发布,"从画布重新生成"随时丢弃修复。