diff --git a/.agents/README.md b/.agents/README.md index 881cde0..0350352 100644 --- a/.agents/README.md +++ b/.agents/README.md @@ -23,6 +23,8 @@ receipts and variable browser cold-install observations remain unchanged. [Template startup ownership documentation](notes/proposed/process/2026-10-25-template-startup-ownership.md) clarifies preparation versus connection; [verification](evidence/verify/2026-10-25-template-startup-ownership.md) records the bounded public-a6 startup/auth observation separately from full catalog acceptance. +[Environment-variable guidance](notes/proposed/process/2026-10-25-environment-variable-guidance.md) corrects process inheritance, terminal-only flags and authenticated application examples without changing SDK6; [verification](evidence/verify/2026-10-25-environment-variable-guidance.md) retains scoped passes and the initial local startup failure. + ## Previous public a4 delivery — 2026-10-25 [Public-a4 example acceptance](evidence/verify/2026-10-25-public-a4-examples.md) diff --git a/.agents/evidence/verify/2026-10-25-environment-variable-guidance.md b/.agents/evidence/verify/2026-10-25-environment-variable-guidance.md new file mode 100644 index 0000000..47daf0c --- /dev/null +++ b/.agents/evidence/verify/2026-10-25-environment-variable-guidance.md @@ -0,0 +1,63 @@ +# Verification: Environment-variable guidance + +- Date: 2026-10-25 +- Result: PARTIAL — local document checks and actual-public-interface/local-HTTP + checks pass; latest full PR CI and normal review/merge remain pending. +- Issue: https://github.com/Alibaba-Agent-Sandbox/python-sdk/issues/135 +- Baseline: source `676ef0483706a2b1af717db8d6d08f19acd8d804`, public + client/server0.4.0a6 and official standaloneCLI0.5.6, Python3.13. +- Scope: two bilingual environment guides and their engineering/changelog records; + no source, docstring, package metadata, historical command-map or cloud change. + +## Confirmed documentation errors + +Official `abx --provider alicloud-express create --help` exposes a positional +platform template and describes `--env` as terminal-session configuration. Its +current dispatcher rejects the former `--template` example before any cloud +call. The standard create implementation passes env only to the terminal; the +detached create request does not receive it as creation envs. + +Actual public SDK inspection confirms asynchronous ProviderSandbox.create, +run_code and run; synchronous root Sandbox.create; and real NetworkModule.get_url +and get_access_headers. Interpreter execution separately requires the selected +provider's declared operation. ServerClient.system.env(names) and +set_env(variables) use authenticated application transport. ExecProvider still +sends resolved config through EPP initialize and starts with an allowlisted +process environment plus optional explicitly declared passthrough. + +## Local execution + +- Both guides' Python blocks compile with top-level-await support: PASS. +- Actual official public SDK6 imports/signatures and local synthetic auth-header + and exec-environment helpers: PASS; no network or provider lifecycle. +- First local HTTP attempt: FAIL before readiness. The initial health probe + raised RemoteProtocolError (server disconnected without a response). Finally + invoked owned subprocess terminate/wait. No environment-method PASS or host + root cause is inferred; this attempt did not persist an independent absence + receipt. A later run, if any, receives a separate result. +- A separately recorded local HTTP repeat: PASS. The same ten-second readiness + budget encountered two pre-ready protocol resets; only pre-ready health probes + retried. After readiness, typed GET/set/readback, wrong-application-token + rejection, secret-name filtering and unchanged parent environment all passed + without API retries. Owned local subprocess absence was independently checked. +- Local links, `git diff --check`, `make hygiene`: PASS. +- `bash scripts/check-repo-health.sh --quiet`: PASS106/WARN1/FAIL0 (existing + missing maintainer release-guide warning). +- Public SDK imports and ten Python blocks in each translation compile: PASS. +- Local full make-ci was not run: this environment has no ruff/mypy/pytest and + this scoped docs task does not install new dependencies. Normal full thirteen + SDK PR checks remain required; local snippets do not replace that matrix. + +Safe scope-specific receipts: +[initial startup failure](2026-10-25-environment-variable-local-initial-failure.json) +and [later actual local HTTP](2026-10-25-environment-variable-local-http.json). +The later local HTTP run uses the published ServerClient against a real local +server. It does not claim a cloud ProviderSandbox lifecycle or new provider +application-endpoint verification. + +## Boundaries + +No new cloud resource was created for this documentation fix. Existing bounded +platform environment observations remain in their owning template record and +are not copied here as a new SDK runtime or all-provider verification. The +public package stays0.4.0a6; no SDKa7 or additional runtime guarantee is claimed. diff --git a/.agents/evidence/verify/2026-10-25-environment-variable-local-http.json b/.agents/evidence/verify/2026-10-25-environment-variable-local-http.json new file mode 100644 index 0000000..bdf76bd --- /dev/null +++ b/.agents/evidence/verify/2026-10-25-environment-variable-local-http.json @@ -0,0 +1,25 @@ +{ + "readiness_connection_errors": 0, + "readiness_protocol_errors": 2, + "scope": "Actual published SDK6 imports and local real HTTP environment APIs; no cloud, no provider lifecycle claim", + "client": "0.4.0a6", + "server": "0.4.0a6", + "en_python_blocks_compile": 10, + "zh_python_blocks_compile": 10, + "actual_public_interfaces": { + "ProviderSandbox_create_async": true, + "root_Sandbox_create_sync": true, + "run_code_envs": true, + "named_run_exists": true, + "network_get_url_exists": true, + "network_get_access_headers_exists": true, + "system_env_names": true, + "system_set_env_variables": true + }, + "wrong_application_token_rejected": true, + "real_local_http_typed_get_set": true, + "secret_name_filtered": true, + "parent_environment_unchanged": true, + "owned_local_pid_absent": true, + "passed": true +} diff --git a/.agents/evidence/verify/2026-10-25-environment-variable-local-initial-failure.json b/.agents/evidence/verify/2026-10-25-environment-variable-local-initial-failure.json new file mode 100644 index 0000000..72e62d0 --- /dev/null +++ b/.agents/evidence/verify/2026-10-25-environment-variable-local-initial-failure.json @@ -0,0 +1,10 @@ +{ + "result": "FAIL", + "scope": "First no-cloud local HTTP attempt, before readiness; not a typed-environment API result", + "phase": "initial health readiness probe", + "error_type": "httpx.RemoteProtocolError", + "safe_error": "server disconnected without sending a response", + "cleanup": "finally invoked owned subprocess terminate and wait; this attempt did not persist an independent absence receipt", + "cause": "not established", + "source_or_product_fix_claimed": false +} diff --git a/.agents/notes/proposed/process/2026-10-25-environment-variable-guidance.md b/.agents/notes/proposed/process/2026-10-25-environment-variable-guidance.md new file mode 100644 index 0000000..c9d6554 --- /dev/null +++ b/.agents/notes/proposed/process/2026-10-25-environment-variable-guidance.md @@ -0,0 +1,58 @@ +# Decision: Explain environment injection with current public interfaces + +Status: proposed + +## Problem + +The bilingual environment guide promises universal creation-time inheritance, +assumes two independent process trees and describes Express terminal `--env` as +sandbox-lifetime injection. Its application HTTP examples omit the application +credential and its snippets do not identify the asynchronous ProviderSandbox. + +## Decision + +Correct only the bilingual environment guides. Describe creation inputs, +per-process overrides and an already running application's environment using +ordinary process inheritance. Show explicit ProviderSandbox ownership/cleanup +and authenticated application binding with existing typed environment methods. +Keep real interpreter and network APIs, with their provider-specific scope, and +keep the exec plugin's actual initialize/allowlist contract. Historical command +maps retain their explicit historical scope and current-guide links. + +## API Design + +N/A — no runtime, docstring, API, dependency or package-version change. + +## Alternatives considered + +- Change the runtime or add a global environment API: unnecessary for correcting + misleading documentation and outside this change. +- Replace every interpreter/network/plugin reference as obsolete: those public + interfaces are real; their scope and credentials need accurate explanation. +- Update external-product historical comparison tables: outside this guide fix. + +## Dependencies + +- https://github.com/Alibaba-Agent-Sandbox/python-sdk/issues/135 +- https://github.com/Alibaba-Agent-Sandbox/python-sdk/releases/tag/v0.4.0a6 +- https://github.com/Alibaba-Agent-Sandbox/agent-sandbox/releases/tag/v0.5.6 + +## Test Strategy + +Use official SDK6 imports/signatures and the official CLI6 help/parser without +cloud creation. Compile both guides' Python fragments, exercise typed environment +methods against a real local server, verify document links/health/hygiene and run +normal full SDK PR gates. Record a startup-probe failure separately from any +subsequent local HTTP result. No new cloud run or package publication is requested. + +## Acceptance criteria + +- The two translations describe the same current behavior and owned cleanup. +- No terminal flag is presented as lifetime creation injection. +- Application examples supply explicit credentials through the existing binding. +- Provider interpreter support and exec initialization remain accurately scoped. +- Owner record, scoped real-component evidence, latest full CI and review pass. + +## Evidence + +- [Verification](../../../evidence/verify/2026-10-25-environment-variable-guidance.md) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca2fd9b..76c3168 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/). ### Changed +- Correct bilingual environment guidance for ProviderSandbox ownership, Express terminal-only env flags and authenticated application environment calls; retain provider-specific interpreter and exec initialization contracts. Documentation only, without runtime or version changes. / 修正双语环境说明,明确 ProviderSandbox 资源归属、Express 终端环境参数和带认证的应用环境调用,保留提供方解释器与 exec 初始化契约;仅改文档,不改运行行为或版本。 + - Clarify CLI-started application connection versus Python-owned prepared-reference startup in both template guides; correct the SDK Skill's specification number to 5. No runtime, API or package version change. / 双语模板指南区分 CLI 启动后连接与 Python 根据已准备引用启动,避免重复启动同一个服务;SDK 技能的规范编号更正为 5,不改运行行为、接口或版本。 - Select verified public client/server a6 in maintained bilingual installs, Skills and runtime fixtures. Preserve immutable publication hashes, exhaustive help checks and real current-design cleanup separately from earlier a4/a5 receipts. No runtime/API change or new package publication. / 维护中的双语安装、技能与运行夹具统一使用已验证公开 a6;另存不可变产物摘要、完整帮助检查与真实设计示例清理结果,不改旧 a4/a5 证据,不改运行接口或另发包。 diff --git a/docs/en/guide/environment-variables.md b/docs/en/guide/environment-variables.md index 2516a89..114fa0b 100644 --- a/docs/en/guide/environment-variables.md +++ b/docs/en/guide/environment-variables.md @@ -4,69 +4,60 @@ This guide describes asynchronous `ProviderSandbox` provider extensions. Root `S For current SDK installation, use the [installation guide](install-python.md). -> **Note:** The `abx` commands shown on this page are provided by the Node.js CLI in the `cli` repository ([CLI binary installation](https://github.com/Alibaba-Agent-Sandbox/agent-sandbox/blob/main/docs/en/guide/binary-installation.md)). CLI-specific documentation (CLI design, tutorial, reference) lives there too; this repository documents the Python SDK (`agent-sandbox-client`) and the container-side runtime (`agent-sandbox-server`). +> **Note:** The `abx` commands shown on this page are provided by the CLI in the `agent-sandbox` repository ([CLI binary installation](https://github.com/Alibaba-Agent-Sandbox/agent-sandbox/blob/main/docs/en/guide/binary-installation.md)). CLI-specific documentation (CLI design, tutorial, reference) lives there too; this repository documents the Python SDK (`agent-sandbox-client`) and the container-side runtime (`agent-sandbox-server`). -This guide explains how environment variables work inside Agent Sandbox, covering the two distinct process scopes, injection methods, the direct-exec execution model, and recommended patterns. +This guide distinguishes creation-time environment input, per-process overrides and the environment of an already running application. The examples use the asynchronous `ProviderSandbox`; snippets after the first example assume a live `sandbox` inside an async function. -> **Design decision:** There is no `sandbox.set_env()` API. The envd process tree and the SandboxServer `os.environ` are two separate, non-unifiable scopes. Attempting to provide a single "set env" abstraction would be misleading. Use the scope-specific injection methods described below instead. +## Where Values Apply ---- - -## Two Process Scopes - -Inside every sandbox there are **two independent process trees**, each with its own environment: - -```text -┌─────────────────────────────────────────────────────┐ -│ Sandbox Container │ -│ │ -│ ┌────────────────────┐ ┌───────────────────────┐ │ -│ │ envd (PID 1) │ │ SandboxServer │ │ -│ │ ├─ commands.run() │ │ (optional, port 9000)│ │ -│ │ ├─ run_code() │ │ ├─ GET/POST /env │ │ -│ │ └─ terminal PTY │ │ ├─ POST /commands/* │ │ -│ │ │ │ └─ child processes │ │ -│ └────────────────────┘ └───────────────────────┘ │ -└─────────────────────────────────────────────────────┘ -``` +| Input | Applies to | +|-------|------------| +| `ProviderSandbox.create(envs=...)` | Creation input interpreted by the selected provider and runtime. Ordinary variables can reach subsequent processes, but the runtime may retain the platform `PATH`. | +| `sandbox.commands.run(..., env=...)` | That process and its children. It does not update other running processes. | +| Application `POST /env` | The running application's `os.environ` and subsequently created children. It does not update its parent or sibling processes. | -| Scope | Process owner | How env vars propagate | -|-------|--------------|----------------------| -| **envd scope** | The `envd` daemon (PID 1) | Variables are set at sandbox creation or passed per-call; every process started via `commands.run()`, `run_code()`, or a PTY terminal inherits from envd. | -| **Server scope** | The `SandboxServer` Python process | `POST /env` modifies `os.environ` of the Server process; only child processes spawned by the Server (e.g. `POST /commands/{name}` handlers) inherit those changes. | - -**Key boundary:** `POST /env` on the SandboxServer **does not** affect processes started through the SDK's `commands.run()` or `run_code()` — those go through envd, which has its own environment. The two scopes are completely independent. +An application can be a child process started through envd. Each process has its own environment; later changes do not rewrite existing parent or sibling processes. There is no global `sandbox.set_env()` API. For a required process `PATH`, pass an explicit per-call `env` rather than assuming creation-time replacement. --- -## envd Scope — SDK & CLI Injection +## Creation and Per-Process Environment ### At Creation Time -Variables passed at creation are injected into the envd root process and inherited by **all** subsequent commands: +Use creation-time `envs` for ordinary application variables. **SDK:** ```python -sandbox = await Sandbox.create( - template="base", - envs={"DATABASE_URL": "postgres://...", "APP_ENV": "staging"}, -) +import asyncio +from agent_sandbox import ProviderSandbox + +async def main(): + sandbox = await ProviderSandbox.create( + template="base", envs={"APP_ENV": "staging"}, timeout=300, + ) + try: + result = await sandbox.commands.run("printenv APP_ENV") + print(result.stdout.strip()) + finally: + await sandbox.kill() # This function owns the instance. + +asyncio.run(main()) ``` -**CLI:** +On the default Express CLI path, `abx create base --env KEY=value` sets the new interactive terminal's environment; it is not a sandbox-lifetime creation-env API and has no terminal to configure with `--detach`. Use `ProviderSandbox.create(envs=...)` for the creation input above. For template applications, declare runtime defaults in the template and configure required values before `abx template launch`. See the [CLI guide](https://github.com/Alibaba-Agent-Sandbox/agent-sandbox/blob/main/docs/en/guide/cli-tutorial.md). -```bash -abx create --template base --env DATABASE_URL=postgres://... --env APP_ENV=staging -``` +When attaching to an instance you do not own, release the local connection with `await sandbox.close()` instead of deleting the instance. **`@sandbox` decorator:** ```python -@sandbox(template="base", envs={"DATABASE_URL": "postgres://..."}) +from agent_sandbox import sandbox as sandboxed + +@sandboxed(template="base", envs={"APP_ENV": "staging"}) def my_task(): import os - return os.environ["DATABASE_URL"] + return os.environ["APP_ENV"] ``` ### Per-Execution Override @@ -87,6 +78,8 @@ result = await sandbox.run_code( ) ``` +`run_code()` is a real interpreter API and requires the selected provider to declare `code.execute` for this mode; an ordinary Express `base` instance does not establish that support. Interpreter environment behavior belongs to that provider, rather than universally to envd. + > **Note the parameter naming difference:** `commands.run()` uses `env=` (singular); `run_code()` uses `envs=` (plural). This follows the E2B naming convention. --- @@ -124,11 +117,11 @@ result = await sandbox.commands.run("echo $HOME") # ✅ CORRECT — use sh -c for variable expansion result = await sandbox.commands.run("sh -c 'echo $HOME'") -# stdout: "/root" +# stdout: the selected process user's home directory # ✅ CORRECT — use printenv to read a single variable result = await sandbox.commands.run("printenv HOME") -# stdout: "/root" +# stdout: the selected process user's home directory ``` ### Glob Expansion Still Requires Manual Wrapping @@ -164,36 +157,24 @@ If you only need to **read** a single environment variable's value, prefer `prin When a `SandboxServer` is running inside the sandbox (typically on port 9000), the `/env` endpoints manage the **Server process's** `os.environ`: -### Read Variables - -```python -import httpx - -url = sandbox.network.get_url(9000) -headers = sandbox.network.get_access_headers() +### Read and Set Variables -resp = httpx.get(f"{url}/env", headers=headers) -print(resp.json()["variables"]) -# Only non-sensitive variables are returned (names containing -# TOKEN, SECRET, KEY, PASSWORD, CREDENTIAL are redacted) -``` - -### Set Variables +The application must enable and authorize the environment endpoints in its policy. Bind the application with its explicit token; provider gateway access and the application token are distinct credentials. ```python -resp = httpx.post( - f"{url}/env", - headers={**headers, "Content-Type": "application/json"}, - json={"vars": {"MY_CONFIG": "new_value"}}, -) -print(resp.json()["updated"]) # ["MY_CONFIG"] +import os + +async with await sandbox.application_client( + 9000, token=os.environ["ABX_SERVER_TOKEN"] +) as application: + print(await application.system.env(["APP_ENV"])) + print(await application.system.set_env({"FEATURE_FLAG_X": "enabled"})) + print(await application.system.env(["FEATURE_FLAG_X"])) ``` -### Server Scope Boundaries +These existing typed methods call `GET /env` and `POST /env`, preserving provider URL prefixes, signed query parameters and authentication. Direct HTTP callers can use `application.auth_headers()`; `network.get_url()` and `get_access_headers()` are real provider APIs, but gateway headers alone do not supply an application token. See the [application binding guide](template-workflows.md). -- `POST /env` modifies **only** `os.environ` of the SandboxServer process. -- Child processes spawned by the Server (e.g. custom command handlers via `POST /commands/{name}`) inherit these changes. -- Processes started via `commands.run()`, `run_code()`, or PTY terminals are **not affected** — they run under envd, which has a separate environment. +The changes affect the application process and future children; they do not modify envd or sibling processes. Settings already read and cached at startup, including model configuration, are not automatically reinitialized by `POST /env`. ### Protected Variables @@ -211,17 +192,11 @@ The Server rejects attempts to overwrite: Best for variables that should be available throughout the sandbox's lifetime: ```python -sandbox = await Sandbox.create( +async with await ProviderSandbox.create( template="base", - envs={ - "DATABASE_URL": db_url, - "API_KEY": api_key, - "APP_ENV": "production", - }, -) - -# All subsequent commands see these variables -result = await sandbox.commands.run("printenv DATABASE_URL") + envs={"DATABASE_URL": db_url, "APP_ENV": "production"}, +) as sandbox: + result = await sandbox.commands.run("printenv APP_ENV") ``` ### Pattern 2: Per-Call env Dict (Scoped Overrides) @@ -246,36 +221,20 @@ await sandbox.commands.run("sh -c 'python seed.py'", env=shared_env) await sandbox.run_code("import os; print(os.environ['LOG_LEVEL'])", envs=shared_env) ``` -### Pattern 4: Server-Scoped Config (SandboxServer Only) - -When using a SandboxServer with custom commands, use `POST /env` for runtime config that only the Server needs: +### Pattern 4: Application-Scoped Configuration -```python -import httpx - -url = sandbox.network.get_url(9000) -headers = sandbox.network.get_access_headers() - -httpx.post( - f"{url}/env", - headers={**headers, "Content-Type": "application/json"}, - json={"vars": {"FEATURE_FLAG_X": "enabled"}}, -) - -# This variable is now visible to Server command handlers: -result = await sandbox.run("my_handler") -``` +Use the authenticated application binding above for values read by a running application. Set startup-only values before launching that application. --- ## Client Configuration Variables Are Not Sandbox Variables -The variables that configure the SDK and CLI on **your** machine (`ABX_SANDBOX_API_KEY`, `ABX_PROVIDER`, `ALIBABA_CLOUD_REGION_ID`, ...) are read by the client and are never copied into a sandbox. To make a value visible inside a sandbox, pass it explicitly with `envs=` / `--env`. Each client setting has one variable. Names and precedence are in the [Configuration Reference](../reference/configuration.md#environment-variables). +The variables that configure the SDK and CLI on **your** machine (`ABX_SANDBOX_API_KEY`, `ABX_PROVIDER`, `ALIBABA_CLOUD_REGION_ID`, ...) are read by the client and are not automatically copied into a sandbox. To make a value visible inside a sandbox, pass it explicitly through the applicable creation or per-process API. Each client setting has one variable. Names and precedence are in the [Configuration Reference](../reference/configuration.md#environment-variables). Two consequences: - Inside the Server, every name starting with `ABX_` is protected (see above). Do not use `ABX_*` for your own sandbox variables. -- [Provider plugins](providers.md) do not inherit your shell environment either. An `exec` plugin process starts with a scrubbed environment; the configuration values it needs are sent in its `initialize` request. A `node` provider is not that process: the Python SDK sends the resolved options on each call to the local engine. +- An [`exec` provider plugin](providers.md) starts with an allowlisted environment plus any explicitly declared `env_passthrough` names. Its resolved configuration is sent through the existing `initialize` request. A `node` provider uses the CLI local engine; the SDK sends resolved options with its requests. --- @@ -287,7 +246,7 @@ Two consequences: 3. **Protected Server variables**: `POST /env` blocks writes to `PATH`, `HOME`, `USER`, `SHELL`, and all `ABX_*` variables to prevent accidental breakage. -4. **No cross-scope leakage**: Variables set via `POST /env` on the Server are invisible to envd processes, and vice versa. This is a security feature, not a bug. +4. **Process inheritance**: `POST /env` does not rewrite parent or sibling environments. Do not treat this ordinary process boundary as a security-isolation guarantee. 5. **Prefer `printenv`** over `echo $VAR`: Since envd uses direct exec, `echo $VAR` prints the literal string `$VAR`. Use `printenv VAR` or `sh -c 'echo $VAR'` instead. @@ -297,11 +256,10 @@ Two consequences: | Method | Parameter | Scope | Lifetime | |--------|-----------|-------|----------| -| `Sandbox.create(envs=)` | `envs` | envd | Sandbox lifetime | +| `ProviderSandbox.create(envs=)` | `envs` | Instance creation input | Sandbox lifetime | | `commands.run(env=)` | `env` | envd | Single command | -| `run_code(envs=)` | `envs` | envd | Single execution | -| `@sandbox(envs=)` | `envs` | envd | Decorator call | -| CLI `--env KEY=VAL` | `--env` | envd | Sandbox lifetime | +| `run_code(envs=)` | `envs` | Provider interpreter, when supported | Single execution | +| `@sandbox(envs=)` | `envs` | Provider creation input | Decorator-owned sandbox | | `POST /env` | `vars` | Server | Server process lifetime | --- @@ -310,6 +268,6 @@ Two consequences: - [SDK Usage Guide](sdk-usage.md) — `commands.run()` and `run_code()` usage - [Declarative Usage](declarative-usage.md) — `@sandbox(envs=)` decorator -- [CLI Tutorial](https://github.com/alibaba-agent-sandbox/agent-sandbox/blob/main/docs/en/guide/cli-tutorial.md) — `abx create --env` +- [CLI Tutorial](https://github.com/alibaba-agent-sandbox/agent-sandbox/blob/main/docs/en/guide/cli-tutorial.md) — CLI terminal environment - [Configuration Reference](../reference/configuration.md) — SDK/CLI environment variables (one name per key) - [Providers](providers.md) — selecting and installing sandbox providers diff --git a/docs/zh/guide/environment-variables.md b/docs/zh/guide/environment-variables.md index 7d9117f..0403d15 100644 --- a/docs/zh/guide/environment-variables.md +++ b/docs/zh/guide/environment-variables.md @@ -4,69 +4,60 @@ 当前 SDK 安装使用[安装指南](install-python.md)。 -> **说明:** 本页出现的 `abx` 命令由 `cli` 仓库中的 Node.js CLI 提供([CLI binary installation](https://github.com/Alibaba-Agent-Sandbox/agent-sandbox/blob/main/docs/en/guide/binary-installation.md))。CLI 专属文档(CLI 设计、教程、参考)也在该仓库;本仓库记录 Python SDK(`agent-sandbox-client`)与容器侧运行时(`agent-sandbox-server`)。 +> **说明:** 本页出现的 `abx` 命令由 `agent-sandbox` 仓库中的 CLI 提供([CLI binary installation](https://github.com/Alibaba-Agent-Sandbox/agent-sandbox/blob/main/docs/en/guide/binary-installation.md))。CLI 专属文档(CLI 设计、教程、参考)也在该仓库;本仓库记录 Python SDK(`agent-sandbox-client`)与容器侧运行时(`agent-sandbox-server`)。 -本指南说明环境变量在 Agent Sandbox 中的工作方式,涵盖两种独立的进程作用域、注入方法、直接执行(direct-exec)语义以及推荐实践。 +本指南区分创建时的环境输入、单次进程覆盖和已启动应用的环境。示例使用异步 `ProviderSandbox`;首个完整示例之后的代码片段假定处于异步函数中,并持有仍存活的 `sandbox`。 -> **设计决策:** SDK 不提供 `sandbox.set_env()` API。envd 进程树和 SandboxServer 的 `os.environ` 是两个独立且不可统一的作用域,提供单一的"设置环境变量"抽象会造成误导。请使用下文描述的作用域特定注入方法。 +## 变量在哪里生效 ---- - -## 两种进程作用域 - -每个沙箱容器内部有**两棵独立的进程树**,各自拥有独立的环境变量: - -```text -┌─────────────────────────────────────────────────────┐ -│ 沙箱容器 │ -│ │ -│ ┌────────────────────┐ ┌───────────────────────┐ │ -│ │ envd(PID 1) │ │ SandboxServer │ │ -│ │ ├─ commands.run() │ │ (可选,端口 9000) │ │ -│ │ ├─ run_code() │ │ ├─ GET/POST /env │ │ -│ │ └─ 终端 PTY │ │ ├─ POST /commands/* │ │ -│ │ │ │ └─ 子进程 │ │ -│ └────────────────────┘ └───────────────────────┘ │ -└─────────────────────────────────────────────────────┘ -``` +| 输入 | 生效范围 | +|------|----------| +| `ProviderSandbox.create(envs=...)` | 由所选提供方和运行时处理的创建输入。普通变量可以传给后续进程,但运行时可能仍采用平台的 `PATH`。 | +| `sandbox.commands.run(..., env=...)` | 该次进程及其子进程,不修改其他已运行进程。 | +| 应用的 `POST /env` | 已运行应用的 `os.environ` 及其后续创建的子进程,不修改父进程或兄弟进程。 | -| 作用域 | 进程所有者 | 环境变量传播方式 | -|--------|-----------|----------------| -| **envd 作用域** | `envd` 守护进程(PID 1) | 变量在沙箱创建时设置或按调用传递;通过 `commands.run()`、`run_code()` 或 PTY 终端启动的所有进程均从 envd 继承。 | -| **Server 作用域** | `SandboxServer` Python 进程 | `POST /env` 修改 Server 进程的 `os.environ`;仅由 Server 派生的子进程(如 `POST /commands/{name}` 处理器)继承这些变更。 | - -**关键边界:** SandboxServer 上的 `POST /env` **不会**影响通过 SDK 的 `commands.run()` 或 `run_code()` 启动的进程——这些进程经由 envd 启动,拥有独立的环境。两个作用域完全隔离。 +应用可以是经由 envd 启动的子进程。每个进程持有自己的环境,后续修改不会改写已有父进程或兄弟进程。SDK 没有全局 `sandbox.set_env()` 接口。进程需要特定 `PATH` 时,显式使用单次调用的 `env`,不要假设创建时一定能覆盖平台默认值。 --- -## envd 作用域 — SDK 与 CLI 注入 +## 创建与单次进程环境 ### 创建时注入 -创建时传入的变量会注入到 envd 根进程,被**所有**后续命令继承: +通过创建参数 `envs` 传入普通应用变量。 **SDK:** ```python -sandbox = await Sandbox.create( - template="base", - envs={"DATABASE_URL": "postgres://...", "APP_ENV": "staging"}, -) +import asyncio +from agent_sandbox import ProviderSandbox + +async def main(): + sandbox = await ProviderSandbox.create( + template="base", envs={"APP_ENV": "staging"}, timeout=300, + ) + try: + result = await sandbox.commands.run("printenv APP_ENV") + print(result.stdout.strip()) + finally: + await sandbox.kill() # 本函数拥有该实例。 + +asyncio.run(main()) ``` -**CLI:** +默认 Express CLI 路径中,`abx create base --env KEY=value` 设置的是新交互终端的环境,不是实例生命周期内的创建环境接口;使用 `--detach` 时也没有终端可配置。上面的创建环境输入使用 `ProviderSandbox.create(envs=...)`。应用模板应声明运行环境默认值,并在 `abx template launch` 前配置所需值。详见 [CLI 指南](https://github.com/Alibaba-Agent-Sandbox/agent-sandbox/blob/main/docs/zh/guide/cli-tutorial.md)。 -```bash -abx create --template base --env DATABASE_URL=postgres://... --env APP_ENV=staging -``` +连接不属于自己的实例时,用 `await sandbox.close()` 释放本地连接,不要删除该实例。 **`@sandbox` 装饰器:** ```python -@sandbox(template="base", envs={"DATABASE_URL": "postgres://..."}) +from agent_sandbox import sandbox as sandboxed + +@sandboxed(template="base", envs={"APP_ENV": "staging"}) def my_task(): import os - return os.environ["DATABASE_URL"] + return os.environ["APP_ENV"] ``` ### 按调用覆盖 @@ -87,6 +78,8 @@ result = await sandbox.run_code( ) ``` +`run_code()` 是真实的解释器接口,本模式要求所选提供方声明 `code.execute`;普通 Express `base` 实例不代表支持该接口。解释器环境行为由对应提供方负责,不能统一归为 envd 进程环境。 + > **注意参数命名差异:** `commands.run()` 使用 `env=`(单数);`run_code()` 使用 `envs=`(复数)。这沿用 E2B 的命名约定。 --- @@ -124,11 +117,11 @@ result = await sandbox.commands.run("echo $HOME") # ✅ 正确 — 使用 sh -c 进行变量展开 result = await sandbox.commands.run("sh -c 'echo $HOME'") -# stdout: "/root" +# stdout: 所选进程用户的主目录 # ✅ 正确 — 使用 printenv 读取单个变量 result = await sandbox.commands.run("printenv HOME") -# stdout: "/root" +# stdout: 所选进程用户的主目录 ``` ### 通配符展开仍需手动包裹 @@ -164,36 +157,24 @@ result = await sandbox.commands.run("sh -c 'ls *.py'") 当沙箱内运行了 `SandboxServer`(通常监听端口 9000)时,`/env` 端点管理的是 **Server 进程的** `os.environ`: -### 读取变量 - -```python -import httpx - -url = sandbox.network.get_url(9000) -headers = sandbox.network.get_access_headers() +### 读取与设置变量 -resp = httpx.get(f"{url}/env", headers=headers) -print(resp.json()["variables"]) -# 仅返回非敏感变量(名称中包含 TOKEN、SECRET、KEY、 -# PASSWORD、CREDENTIAL 的变量会被隐藏) -``` - -### 设置变量 +应用策略需要启用环境端点,并允许当前身份访问。通过显式令牌绑定应用;提供方网关访问凭据与应用令牌是不同的凭据。 ```python -resp = httpx.post( - f"{url}/env", - headers={**headers, "Content-Type": "application/json"}, - json={"vars": {"MY_CONFIG": "new_value"}}, -) -print(resp.json()["updated"]) # ["MY_CONFIG"] +import os + +async with await sandbox.application_client( + 9000, token=os.environ["ABX_SERVER_TOKEN"] +) as application: + print(await application.system.env(["APP_ENV"])) + print(await application.system.set_env({"FEATURE_FLAG_X": "enabled"})) + print(await application.system.env(["FEATURE_FLAG_X"])) ``` -### Server 作用域边界 +这些现有方法调用 `GET /env` 和 `POST /env`,保留提供方 URL 前缀、签名查询参数与认证。直接调用 HTTP 时可以使用 `application.auth_headers()`;`network.get_url()` 和 `get_access_headers()` 都是真实提供方接口,但只有网关头不代表已经提供应用令牌。详见[应用绑定指南](template-workflows.md)。 -- `POST /env` **仅**修改 SandboxServer 进程的 `os.environ`。 -- 由 Server 派生的子进程(如通过 `POST /commands/{name}` 执行的自定义命令处理器)会继承这些变更。 -- 通过 `commands.run()`、`run_code()` 或 PTY 终端启动的进程**不受影响**——它们在 envd 下运行,拥有独立的环境。 +修改影响应用进程及其后续子进程,不会修改 envd 或兄弟进程。已经在启动时读取并缓存的配置(包括模型配置)不会因为 `POST /env` 自动重新初始化。 ### 受保护变量 @@ -211,17 +192,11 @@ Server 拒绝覆写以下变量: 适用于需要在整个沙箱生命周期内可用的变量: ```python -sandbox = await Sandbox.create( +async with await ProviderSandbox.create( template="base", - envs={ - "DATABASE_URL": db_url, - "API_KEY": api_key, - "APP_ENV": "production", - }, -) - -# 后续所有命令都能看到这些变量 -result = await sandbox.commands.run("printenv DATABASE_URL") + envs={"DATABASE_URL": db_url, "APP_ENV": "production"}, +) as sandbox: + result = await sandbox.commands.run("printenv APP_ENV") ``` ### 模式 2:按调用传 env Dict(局部覆盖) @@ -246,36 +221,20 @@ await sandbox.commands.run("sh -c 'python seed.py'", env=shared_env) await sandbox.run_code("import os; print(os.environ['LOG_LEVEL'])", envs=shared_env) ``` -### 模式 4:Server 作用域配置(仅 SandboxServer) - -使用 SandboxServer 的自定义命令时,通过 `POST /env` 设置仅 Server 需要的运行时配置: +### 模式 4:应用进程配置 -```python -import httpx - -url = sandbox.network.get_url(9000) -headers = sandbox.network.get_access_headers() - -httpx.post( - f"{url}/env", - headers={**headers, "Content-Type": "application/json"}, - json={"vars": {"FEATURE_FLAG_X": "enabled"}}, -) - -# 该变量现在对 Server 命令处理器可见: -result = await sandbox.run("my_handler") -``` +运行中的应用按上文使用带认证的应用绑定。只在启动时读取的配置应在启动应用前设置。 --- ## 客户端配置变量不是沙箱变量 -在**你的机器上**配置 SDK 和 CLI 的变量(`ABX_SANDBOX_API_KEY`、`ABX_PROVIDER`、`ALIBABA_CLOUD_REGION_ID` 等)由客户端读取,永远不会被复制进沙箱。要让某个值在沙箱内可见,请用 `envs=` / `--env` 显式传入。每项客户端设置只有一个变量。名字和优先级见[配置参考](../reference/configuration.md#环境变量)。 +在**你的机器上**配置 SDK 和 CLI 的变量(`ABX_SANDBOX_API_KEY`、`ABX_PROVIDER`、`ALIBABA_CLOUD_REGION_ID` 等)由客户端读取,不会自动复制进沙箱。要让某个值在沙箱内可见,请通过对应的创建或单次进程接口显式传入。每项客户端设置只有一个变量。名字和优先级见[配置参考](../reference/configuration.md#环境变量)。 由此有两点: - 在 Server 内部,所有以 `ABX_` 开头的名字都受保护(见上文)。不要把 `ABX_*` 用作你自己的沙箱变量。 -- [提供方插件](providers.md)同样不继承你的 shell 环境。`exec` 插件进程以清理过的环境启动,它需要的配置值通过 `initialize` 请求发送。`node` 提供方不是这个进程:Python SDK 把解析好的选项随每次调用发给本机引擎。 +- [`exec` 提供方插件](providers.md) 使用允许列表中的环境变量,以及显式声明的 `env_passthrough` 名称;解析后的配置通过现有 `initialize` 请求传入。`node` 提供方使用 CLI 本机引擎,SDK 随请求传递解析后的选项。 --- @@ -287,7 +246,7 @@ result = await sandbox.run("my_handler") 3. **受保护的 Server 变量**:`POST /env` 禁止写入 `PATH`、`HOME`、`USER`、`SHELL` 及所有 `ABX_*` 变量,防止意外破坏。 -4. **无跨作用域泄漏**:通过 `POST /env` 在 Server 上设置的变量对 envd 进程不可见,反之亦然。这是安全特性,而非缺陷。 +4. **进程继承**:`POST /env` 不会改写父进程或兄弟进程的环境;不要把普通进程边界当成安全隔离保证。 5. **推荐 `printenv`** 而非 `echo $VAR`:由于 envd 使用直接执行,`echo $VAR` 会输出字面字符串 `$VAR`。请使用 `printenv VAR` 或 `sh -c 'echo $VAR'`。 @@ -297,11 +256,10 @@ result = await sandbox.run("my_handler") | 方法 | 参数 | 作用域 | 生命周期 | |------|------|--------|---------| -| `Sandbox.create(envs=)` | `envs` | envd | 沙箱生命周期 | +| `ProviderSandbox.create(envs=)` | `envs` | 实例创建输入 | 沙箱生命周期 | | `commands.run(env=)` | `env` | envd | 单次命令 | -| `run_code(envs=)` | `envs` | envd | 单次执行 | -| `@sandbox(envs=)` | `envs` | envd | 装饰器调用 | -| CLI `--env KEY=VAL` | `--env` | envd | 沙箱生命周期 | +| `run_code(envs=)` | `envs` | 支持该操作的提供方解释器 | 单次执行 | +| `@sandbox(envs=)` | `envs` | 提供方创建输入 | 装饰器管理的沙箱 | | `POST /env` | `vars` | Server | Server 进程生命周期 | --- @@ -310,6 +268,6 @@ result = await sandbox.run("my_handler") - [SDK 使用指南](sdk-usage.md) — `commands.run()` 与 `run_code()` 用法 - [声明式用法](declarative-usage.md) — `@sandbox(envs=)` 装饰器 -- [CLI 教程](https://github.com/alibaba-agent-sandbox/agent-sandbox/blob/main/docs/zh/guide/cli-tutorial.md) — `abx create --env` +- [CLI 教程](https://github.com/alibaba-agent-sandbox/agent-sandbox/blob/main/docs/zh/guide/cli-tutorial.md) — CLI 终端环境 - [配置参考](../reference/configuration.md) — SDK/CLI 环境变量(每个键一个名字) - [提供方](providers.md) — 选择和安装沙箱提供方