From 9faec770359a369d359ed6abaade0c1608240c70 Mon Sep 17 00:00:00 2001 From: anycodes <21079031+anycodes@users.noreply.github.com> Date: Tue, 10 Nov 2026 10:42:03 +0000 Subject: [PATCH 1/2] refactor: separate Python client and local server installation --- .agents/README.md | 2 + .../2026-11-10-client-runtime-boundaries.md | 106 ++++++++++++ .../2026-11-10-client-runtime-boundaries.md | 74 +++++++++ .github/CONTRIBUTING.md | 4 +- .github/workflows/publish.yml | 5 +- CHANGELOG.md | 16 ++ README.md | 2 +- README.zh-CN.md | 2 +- agent-sandbox-sdk/SKILL.md | 6 +- agent-sandbox-sdk/references/templates.md | 6 +- client-sdk/README.md | 6 +- client-sdk/pyproject.toml | 7 +- .../agent_sandbox/declarative/decorator.py | 96 +---------- .../tests/test_declarative/test_register.py | 154 +++--------------- .../test_declarative/test_server_missing.py | 9 +- client-sdk/tests/test_packaging.py | 16 +- client-sdk/tests/test_release_validation.py | 6 +- client-sdk/tests/test_version_lockstep.py | 2 +- docs/en/guide/authentication.md | 2 +- docs/en/guide/authoring-templates.md | 10 +- docs/en/guide/build-and-deploy.md | 60 ++----- docs/en/guide/command-map.md | 141 +++------------- docs/en/guide/comparison.md | 4 +- docs/en/guide/conditional-acceptance.md | 36 ++-- docs/en/guide/environment-variables.md | 2 +- docs/en/guide/express-quickstart.md | 2 +- docs/en/guide/install-python.md | 25 ++- docs/en/guide/provider-commands.md | 12 +- docs/en/guide/providers.md | 12 +- docs/en/guide/sdk-skill.md | 2 +- docs/en/guide/sdk-usage.md | 2 +- docs/en/guide/template-server-api.md | 2 +- docs/en/guide/template-workflows.md | 4 +- docs/en/guide/troubleshooting.md | 14 +- docs/en/reference/configuration.md | 6 +- docs/en/reference/error-codes.md | 6 +- docs/zh/guide/authentication.md | 2 +- docs/zh/guide/authoring-templates.md | 10 +- docs/zh/guide/build-and-deploy.md | 57 ++----- docs/zh/guide/command-map.md | 137 ++-------------- docs/zh/guide/comparison.md | 4 +- docs/zh/guide/conditional-acceptance.md | 34 ++-- docs/zh/guide/environment-variables.md | 2 +- docs/zh/guide/express-quickstart.md | 2 +- docs/zh/guide/install-python.md | 23 ++- docs/zh/guide/provider-commands.md | 12 +- docs/zh/guide/providers.md | 12 +- docs/zh/guide/sdk-skill.md | 2 +- docs/zh/guide/sdk-usage.md | 2 +- docs/zh/guide/template-server-api.md | 2 +- docs/zh/guide/template-workflows.md | 4 +- docs/zh/guide/troubleshooting.md | 14 +- docs/zh/reference/configuration.md | 6 +- docs/zh/reference/error-codes.md | 6 +- scripts/check-repo-health.sh | 7 +- scripts/check_dists.py | 115 +++++++++++-- server-sdk/RELEASING.md | 4 +- 57 files changed, 603 insertions(+), 715 deletions(-) create mode 100644 .agents/evidence/verify/2026-11-10-client-runtime-boundaries.md create mode 100644 .agents/notes/proposed/architecture/2026-11-10-client-runtime-boundaries.md diff --git a/.agents/README.md b/.agents/README.md index 6655a1c4..cd4ee599 100644 --- a/.agents/README.md +++ b/.agents/README.md @@ -1,5 +1,7 @@ # `.agents/` — engineering record of the python-sdk repository +- [Client/runtime boundaries](notes/proposed/architecture/2026-11-10-client-runtime-boundaries.md) · [verification](evidence/verify/2026-11-10-client-runtime-boundaries.md). + - [Managed Agents API](notes/proposed/architecture/2026-11-05-managed-agents-api.md) · [initial verification](evidence/verify/2026-11-05-managed-agents-api.md) · [release-candidate verification](evidence/verify/2026-11-06-managed-agents-release-candidate.md) · [CI artifacts](evidence/verify/2026-11-06-managed-agents-ci-artifacts.md). - Managed Agents API cloud continuations: [private invocation/drain](evidence/verify/2026-11-06-managed-agents-fc-private-drain.md), [source business operations](evidence/verify/2026-11-06-managed-agents-fc-business-source.md), [private input boundary](evidence/verify/2026-11-06-managed-agents-empty-private-revision.md). diff --git a/.agents/evidence/verify/2026-11-10-client-runtime-boundaries.md b/.agents/evidence/verify/2026-11-10-client-runtime-boundaries.md new file mode 100644 index 00000000..4cae57a6 --- /dev/null +++ b/.agents/evidence/verify/2026-11-10-client-runtime-boundaries.md @@ -0,0 +1,106 @@ +# Verification: Client installation and local runtime boundaries + +- Date: 2026-11-10 +- Change: [Issue 157](https://github.com/Alibaba-Agent-Sandbox/python-sdk/issues/157) +- Result: PARTIAL — changed-surface checks pass; existing raw HTTP/SSE failures prevent a green local full-suite result. Cloud E2E remains separate. +- Package under test: local client/server 0.4.0a7 sources and built wheels; no version bump or publication. +- Environment: Linux, Python 3.11.15. No cloud credentials used by the local checks. + +## What was verified + +Client-only installation imports the standard synchronous/asynchronous clients, +interpreter clients, ProviderSandbox, ServerClient and remote decorator without +a server distribution. It performs a real localhost HTTP health request. +Registration without a local runtime fails with installation guidance and no +partial local registration. Installing the server extra resolves the exact local +runtime wheel, preserves the shared namespace and registers a real function. +Dynamic functions also register without unnecessary source inspection. Instance +execution and actual remote-decorator behavior retain their existing regressions. + +## Commands run + +Run from the repository root using an isolated virtual environment (pip was seeded for the server package-list test): + +```bash +uv pip install -e ./server-sdk -e './client-sdk[dev]' hatchling build twine +python -m pytest client-sdk/tests/test_declarative client-sdk/tests/test_packaging.py client-sdk/tests/test_version_lockstep.py -q +make lint typecheck format-check +make build dist-check +make test +# make stopped after server failures; run the client suite separately: +(cd client-sdk && python -m pytest tests/ -v --tb=short) +python -m pytest client-sdk/tests/test_release_validation.py client-sdk/tests/test_declarative/test_register.py -q +make hygiene +bash scripts/check-repo-health.sh +ruff check scripts/check_dists.py +ruff format --check scripts/check_dists.py +``` + +## Results + +| Scenario / test | Result | Notes | +|---|---|---| +| Focused declarative, actual wheel metadata and lockstep tests | PASS | 112 passed; includes dynamically defined function registration | +| Both package lint/type/format checks | PASS | 58 server and 114 client source files typechecked; 94 server and 260 client files formatted | +| Both wheels and sdists; twine; py.typed | PASS | Built from the candidate source; all four artifacts accepted | +| Client-only fresh wheel installation | PASS | Runtime distribution and namespace absent; client imports, decorator construction, explicit registration error and real HTTP pass | +| Local server extra in the same fresh environment | PASS | Offline resolution from the local server wheel; registration and pip check pass | +| Full server suite (`make test`, first phase) | FAIL | 877 passed, 3 failed; the client phase did not start because make stopped | +| Server failures rechecked after seeding pip | FAIL | Package-list test passes; unchanged WebSocket handshake and transfer-encoding tests still receive 501/502 rather than 400 | +| Full client suite run separately | FAIL | 2455 passed, 35 skipped, 8 failed; six existing raw HTTP/SSE cases and two outdated publication-workflow assertions | +| Release-workflow and registration follow-up | PASS | 74 passed after updating the expected two-phase installation checks; dynamic registration uses an isolated real registry | +| Hygiene | PASS | No local paths or credential-like strings found | +| Repository health | PASS | 106 passed, 1 warning, 0 failed after adding this evidence | + +## Cleanup + +The distribution verifier shuts down its owned HTTP server, joins its thread, +closes the HTTP client and removes its temporary virtual environment. No cloud +resources were created by these checks. + +## Problems found + +The first packaging assertion assumed Hatchling retained an extra self-reference; +actual wheel metadata expands `all`/`dev`. The corrected test checks their exact +server dependency. The first HTTP probe used `close()` instead of the public +`ServerClient.aclose()`; after correcting the probe, the complete isolated +installation check passed. These were test-harness errors, not product failures. +The uv-created development virtual environment initially lacked pip; the server +package-list regression passed after installing pip. No server product source +was changed. Two server tests still fail with 501/502 responses: +`test_upgrade_without_a_key_is_a_400_not_a_hang` and +`test_transfer_encoding_rejected_in_policy_mode`. + +The client managed-runtime failures concern private octet-stream rejection +(dispatcher/worker), incomplete request bodies, HTTP/1.1 SSE framing, and +post-header SSE failures (runtime/managed). They received timeouts or HTTP501. +Those runtime implementations and test assertions were not modified. Their +root cause has not been established by this change; no assertions were relaxed. +The two publication tests initially assumed one install/import verification +phase; their updated two-phase expectations pass, including existing failure +and retry bounds. + +The existing pytest-asyncio default fixture-loop warning and mypy unused-section +notice remain informational. + +## Not verified + +No package was published and no claim is made about an already published wheel +adopting these source changes. Full cloud `make e2e`, conditional provider features +and the Python 3.10/3.12 CI matrix have not been executed in this local check. + +## Current CLI documentation follow-up + +Current bilingual usage/reference guides and the SDK authoring skill were checked +against the coordinated CLI source. The command map now describes first-party +runtime and authoring responsibilities; template build/publish and named deploy +are distinct, SDK instance methods are preserved, and CLI local names must be +resolved to actual Sandbox IDs before Python connects. Archives/design history +remain unchanged. Changed Markdown introduced no missing relative link targets; +this is current-source guidance, not a claim of a new CLI/PyPI release. + +## Final documentation check + +- Changed Markdown local-link audit: 47 files checked, no introduced missing file links. URL anchors and remote pages were not fetched. +- Current CLI examples use named deploy/status/delete, sandbox operations and Provider run with JSON input. Historical archived documents remain historical. +- CHANGELOG updated; no version bump or package publication. diff --git a/.agents/notes/proposed/architecture/2026-11-10-client-runtime-boundaries.md b/.agents/notes/proposed/architecture/2026-11-10-client-runtime-boundaries.md new file mode 100644 index 00000000..7ceab06b --- /dev/null +++ b/.agents/notes/proposed/architecture/2026-11-10-client-runtime-boundaries.md @@ -0,0 +1,74 @@ +# Decision: Separate client installation from local server execution + +Status: proposed + +## Problem + +The Python client installed the local application runtime even when callers only +used standard cloud clients or application HTTP. The module-level `sandbox.run` +also created an implicit code-interpreter sandbox and called a name without +uploading the registered local function, obscuring the deployment boundary. + +## Decision + +Keep both distributions and their lockstep versions. Move the server dependency +to the client `server` extra and include it in `all` and `dev`. Preserve standard +clients, ProviderSandbox, ServerClient, shared client models, and the real remote +execution decorator. Remove only the factory's legacy run/run_async and private +execution helpers and unconsumed registered-function source capture. Local registration requires the server extra and fails with +installation guidance before storing local metadata if the runtime is missing. + +## API Design + +- `pip install agent-sandbox-client` installs the client alone in future releases. +- `agent-sandbox-client[server]` selects the exact matching local runtime. +- `@sandbox(...)`, `@sandbox.register`, `sandbox.server` and `list_registered` + remain distinct operations. Registration does not deploy functions. +- Remove `_SandboxFactory.run` and `run_async`. Use an explicitly selected + `ServerClient.commands.run` for deployed application commands. +- Instance `ProviderSandbox.run` / `invoke` remain unchanged. + +No release version is changed or published in this work. + +## Alternatives considered + +- Keep the hard dependency: preserves unnecessary coupling and conceals the + difference between calling a remote application and running its server. +- Remove the real decorator or merge standard/provider clients: independent + capabilities with observable contracts; outside the simplification scope. +- Keep a compatibility factory forwarding to an arbitrary template: still cannot + deploy the local registered function and preserves the ambiguity. +- Silently allow registration without a runtime: reports success for an unusable + command and becomes more common after making the dependency optional. + +## Dependencies + +Tracking: https://github.com/Alibaba-Agent-Sandbox/python-sdk/issues/157 + + +The runtime remains a separate namespace distribution with its current exact +version pin. No new library, framework, launcher or cloud resource is introduced. +Release and health checks must enforce optionality rather than the old mandatory +runtime invariant. + +## Test Strategy + +Run both package suites, lint, type checks, builds, repository health and hygiene. +Inspect actual wheel metadata for base/server/all/dev requirements. Install the +client wheel in an isolated environment outside source directories, assert the +runtime is absent, import all client surfaces and perform a real local HTTP +request. Resolve the server extra only from the locally built wheel and register +a real function. Keep cloud E2E results separate from local distribution checks. + +## Acceptance criteria + +- Base installation works without a local runtime and calls application HTTP. +- Optional server installation preserves namespace imports and registration. +- Missing-runtime registration is actionable and leaves no partial registration. +- Removed factory execution is absent; existing decorator/instance tests pass. +- Both languages document installation and the explicit invocation boundary. +- Required validation and its limits are recorded before merge. + +## Evidence + +- [Verification](../../../evidence/verify/2026-11-10-client-runtime-boundaries.md) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index e91f119c..ae3ac4db 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -228,6 +228,6 @@ requirements still apply. ## Publishing the Python pair -Publish both distributions from a matching `v` tag. The publication workflow runs the full reusable CI suite at that tag, builds and preserves one wheel/sdist pair per distribution, and verifies a combined clean installation. Manual runs default to dry-run. Before upload, existing PyPI files must match the candidate SHA-256; `skip-existing` is not sufficient verification. Upload server before client, then verify public digests and a fresh exact-version client installation with its automatic server dependency and `pip check`. Inspect the README embedded in both packages: it must not send users to an obsolete version. Preserve tags and artifacts; a content change uses a new version. +Publish both distributions from a matching `v` tag. The publication workflow runs the full reusable CI suite at that tag, builds and preserves one wheel/sdist pair per distribution, and verifies a combined clean installation. Manual runs default to dry-run. Before upload, existing PyPI files must match the candidate SHA-256; `skip-existing` is not sufficient verification. Upload server before client, then verify public digests and a fresh exact-version client-only installation followed by its `[server]` extra and `pip check`. Inspect the README embedded in both packages: it must not send users to an obsolete version. Preserve tags and artifacts; a content change uses a new version. -两个发行包从匹配的 `v` 标签发布,先执行完整 CI,再构建、保存并检查配套产物。手动运行默认 dry-run。上传前核对 PyPI 已有文件的 SHA-256,不能只依赖 `skip-existing`。先上传服务端,再上传客户端;随后检查公开摘要、新环境精确版本安装、自动服务端依赖和 `pip check`。检查双包内的 README,避免把用户导向旧版本。保留既有标签与产物,内容变更使用新版本号。 +两个发行包从匹配的 `v` 标签发布,先执行完整 CI,再构建、保存并检查配套产物。手动运行默认 dry-run。上传前核对 PyPI 已有文件的 SHA-256,不能只依赖 `skip-existing`。先上传服务端,再上传客户端;随后检查公开摘要、新环境精确版本客户端独立安装、随后安装 `[server]` extra 和 `pip check`。检查双包内的 README,避免把用户导向旧版本。保留既有标签与产物,内容变更使用新版本号。 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index c1f42b70..c3e73030 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -128,4 +128,7 @@ jobs: done "$RUNNER_TEMP/pypi-readback/bin/python" -m pip check cd "$RUNNER_TEMP" - "$RUNNER_TEMP/pypi-readback/bin/python" -c 'from agent_sandbox import Sandbox, AsyncSandbox, ProviderSandbox; from agent_sandbox.server import start; assert all(callable(x) for x in (Sandbox.create, AsyncSandbox.create, ProviderSandbox.create, start))' + "$RUNNER_TEMP/pypi-readback/bin/python" -c 'import importlib.util; from agent_sandbox import Sandbox, AsyncSandbox, ProviderSandbox; from agent_sandbox.api.server_client import ServerClient; assert all(callable(x) for x in (Sandbox.create, AsyncSandbox.create, ProviderSandbox.create, ServerClient)); assert importlib.util.find_spec("agent_sandbox.server") is None' + "$RUNNER_TEMP/pypi-readback/bin/python" -m pip install --no-cache-dir --index-url https://pypi.org/simple "agent-sandbox-client[server]==$version" + "$RUNNER_TEMP/pypi-readback/bin/python" -m pip check + "$RUNNER_TEMP/pypi-readback/bin/python" -c 'from agent_sandbox.server import start; assert callable(start)' diff --git a/CHANGELOG.md b/CHANGELOG.md index 5cbf8083..95e0b979 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,22 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/). ## [Unreleased] +### Changed + +- Align current English and Chinese CLI guides with template authoring, named deployment lifecycle and Provider operations; preserve the SDK’s independent clients and instance methods. + +- The client no longer installs the local server runtime by default. Use the + lockstep `[server]` extra for local command registration and server startup; + `[all]` and `[dev]` retain the runtime. Remote clients and decorators remain + available in the base package. Registration without the runtime now reports + the required extra instead of silently storing an unusable command. + +### Removed + +- Module-level `sandbox.run` / `sandbox.run_async`, which created an implicit + sandbox without deploying the registered function. Select an application and + use `ServerClient.commands.run`; instance `ProviderSandbox.run` is unchanged. + Release candidate: matching client/server **0.4.0a7**. / 候选版本:客户端与服务端均为 **0.4.0a7**。 ### Added diff --git a/README.md b/README.md index e3f7b3fb..80c15d4d 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ python -m pip install --index-url https://pypi.org/simple --upgrade --pre agent- python -m pip check ``` -`--pre` opts into preview releases. The package imports as `agent_sandbox` and installs its matching `agent-sandbox-server` dependency. See [installation](docs/en/guide/install-python.md) for a pinned version, optional extras and source setup. The PyPI name `agent-sandbox` belongs to a different project. +`--pre` opts into preview releases. The package imports as `agent_sandbox`. Standard clients, `ProviderSandbox` and the application HTTP `ServerClient` run on the client; `agent-sandbox-server` runs inside the application image. See [installation](docs/en/guide/install-python.md) for a pinned version, optional extras and source setup. The PyPI name `agent-sandbox` belongs to a different project. ## Quick start diff --git a/README.zh-CN.md b/README.zh-CN.md index f860cf0f..956956f9 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -17,7 +17,7 @@ python -m pip install --index-url https://pypi.org/simple --upgrade --pre agent- python -m pip check ``` -`--pre` 表示选择预览版本。导入名为 `agent_sandbox`,安装时自动带上同版本的 `agent-sandbox-server` 依赖。固定版本、可选依赖和源码安装见[安装指南](docs/zh/guide/install-python.md)。PyPI 上的 `agent-sandbox` 属于另一个项目。 +`--pre` 表示选择预览版本。导入名为 `agent_sandbox`。标准客户端、`ProviderSandbox` 与应用 HTTP 客户端 `ServerClient` 在客户端使用;`agent-sandbox-server` 在应用镜像内运行。固定版本、可选依赖和源码安装见[安装指南](docs/zh/guide/install-python.md)。PyPI 上的 `agent-sandbox` 属于另一个项目。 ## 快速开始 diff --git a/agent-sandbox-sdk/SKILL.md b/agent-sandbox-sdk/SKILL.md index 49992498..c839a342 100644 --- a/agent-sandbox-sdk/SKILL.md +++ b/agent-sandbox-sdk/SKILL.md @@ -221,7 +221,7 @@ EXPOSE 9000 CMD ["python3", "commands.py"] ``` -`abx template scaffold` writes this same `commands.py` shape. These Dockerfiles +`abx template init` writes this same `commands.py` shape. These Dockerfiles use the pinned public server package and do not require a local wheel. CLI build may inject an explicitly selected server wheel or resolve its own pinned archive; that preprocessing does not apply to a direct `docker build`. Generation, reserved ports, group @@ -234,12 +234,12 @@ Deploy and `abx` operation belong to the `agent-sandbox` skill. The commands this template needs: ```bash -abx template deploy ./my-template --acr-namespace "$ACR_NAMESPACE" --acr-repo my-template --alias my-template --yes +abx deploy ./my-template --name my-template --yes ``` `--yes` is required when stdin is not a terminal. Docker must be running for a Dockerfile build. `codex` and `openclaw` are reserved. Set `template.yaml` -`name` and `--alias` to `openai-codex` or `openclaw-agent`. Sending the +`name` to `openai-codex` or `openclaw-agent`. Sending the reserved name is rejected. Local check, from the template directory, with `agent-sandbox-server` installed: diff --git a/agent-sandbox-sdk/references/templates.md b/agent-sandbox-sdk/references/templates.md index f327b68c..a09b2b4a 100644 --- a/agent-sandbox-sdk/references/templates.md +++ b/agent-sandbox-sdk/references/templates.md @@ -4,7 +4,7 @@ The skill file is the procedure. This note is the extra detail. ## What deploy actually reads -`abx template deploy` uses the Dockerfile as the image. From `template.yaml` it +`abx deploy` uses the Dockerfile as the image. From `template.yaml` it reads `name`, `resources.cpu`, `resources.memory`, and `generation`. Omitted resources become 2 CPU and 2048 MB. It does not install `python_packages` or `system_packages`, and it does not apply a `server:` block. @@ -40,7 +40,7 @@ await sb.run("hello", name="Ada") ``` ```bash -abx run SANDBOX_ID hello --arg name=Ada +abx sandbox command run SANDBOX_ID hello --arg name=Ada abx sandbox command list SANDBOX_ID ``` @@ -85,5 +85,5 @@ Local execution exists for `codex` and `qwen-code`. `claude-code`, `cursor`, and ## Reserved template names `codex` and `openclaw` are reserved by the platform. The directory can keep -that name. `template.yaml` `name` and `abx template deploy --alias` must be +that name. `template.yaml` `name` and `abx template build --alias` must be `openai-codex` or `openclaw-agent`. diff --git a/client-sdk/README.md b/client-sdk/README.md index 311b61ab..21cf7e19 100644 --- a/client-sdk/README.md +++ b/client-sdk/README.md @@ -11,8 +11,10 @@ The standard root Sandbox/AsyncSandbox and code_interpreter APIs follow Alibaba- This package is the `agent_sandbox` import namespace *except* `agent_sandbox.server`, which is the container-side runtime shipped separately as -[`agent-sandbox-server`](https://github.com/Alibaba-Agent-Sandbox/python-sdk/blob/main/server-sdk/README.md) (a hard dependency pinned to the same version, -so installing the published client also installs the matching server). +[`agent-sandbox-server`](https://github.com/Alibaba-Agent-Sandbox/python-sdk/blob/main/server-sdk/README.md) (available through the optional `[server]` extra, pinned to the same version). +Standard clients, `ProviderSandbox`, `ServerClient` and `@sandbox(...)` do not require +a local server runtime. Local `@sandbox.register` and `sandbox.server` require the +server extra; `[all]` and `[dev]` include it. The command-line tool (`abx`) is a separate Node.js project that lives in the [`cli`](https://github.com/alibaba-agent-sandbox/agent-sandbox) repository. diff --git a/client-sdk/pyproject.toml b/client-sdk/pyproject.toml index b85740a2..e8ef6420 100644 --- a/client-sdk/pyproject.toml +++ b/client-sdk/pyproject.toml @@ -42,12 +42,11 @@ dependencies = [ "pyyaml>=6.0.3", "protobuf>=5", "tomli>=2.0; python_version < '3.11'", - # The client uses the matching local server runtime for its server/declarative helpers. - # Keep the required distribution version equal to ``_version.py``. - "agent-sandbox-server==0.4.0a7", ] [project.optional-dependencies] +# Local command registration/runtime only; keep the lockstep version pin. +server = ["agent-sandbox-server==0.4.0a7"] managed-agents = [ "psycopg[binary]>=3.2,<4", "alibabacloud-fc20230330>=4.8.3,<5", @@ -64,7 +63,7 @@ session = [ "filelock>=3.12", ] all = [ - "agent-sandbox-client[fast,session,mcp]", + "agent-sandbox-client[fast,session,mcp,server]", ] dev = [ "agent-sandbox-client[all]", diff --git a/client-sdk/src/agent_sandbox/declarative/decorator.py b/client-sdk/src/agent_sandbox/declarative/decorator.py index 46b4377c..300ba996 100644 --- a/client-sdk/src/agent_sandbox/declarative/decorator.py +++ b/client-sdk/src/agent_sandbox/declarative/decorator.py @@ -36,8 +36,6 @@ def demo(x: int, y: str) -> str: from uuid import uuid4 from agent_sandbox.utils.async_bridge import run_sync -from agent_sandbox.utils.coerce import coerce_kwargs as _coerce_kwargs -from agent_sandbox.utils.coerce import parse_scalar as _parse_scalar # noqa: F401 re-exported from .serializer import Serializer, SerializerType @@ -75,12 +73,6 @@ def _annotation_to_type_str(annotation: Any) -> str: ) -# ``_parse_scalar`` and ``_coerce_kwargs`` are imported from -# ``agent_sandbox.utils.coerce`` (canonical shared implementation). -# They are re-imported at the top of this file so existing callers -# (including tests) can still use them via this module. - - # --------------------------------------------------------------------------- # Registered command model # --------------------------------------------------------------------------- @@ -102,7 +94,6 @@ class _RegisteredCommand: """Metadata for a ``@sandbox.register``-ed function.""" name: str - source: str args: list[_CommandArg] = field(default_factory=list) @@ -118,7 +109,7 @@ def _import_server() -> Any: except ImportError as exc: raise ImportError( "agent_sandbox.server is not available: install the in-sandbox server with " - "'pip install agent-sandbox-server'." + "pip install 'agent-sandbox-client[server]'." ) from exc if not hasattr(server_mod, "start"): raise ImportError( @@ -171,8 +162,8 @@ def __call__(self, func: Callable[..., Any]) -> Callable[..., Any]: def demo(x: int, y: str) -> str: return f"{y}={x}" - Only scalar parameter types are supported: - ``str``, ``int``, ``float``, ``bool``. + Requires the local ``server`` extra. Supported parameter types: + ``str``, ``int``, ``float``, ``bool``, ``list[str]``. Args: func: The function to register (used as a bare decorator, @@ -181,8 +172,8 @@ def demo(x: int, y: str) -> str: Returns: The original function, unmodified. """ + _import_server() name = func.__name__ - source = _get_function_source(func) sig = inspect.signature(func) # Resolve string annotations (from __future__ annotations) to real types try: @@ -218,7 +209,6 @@ def demo(x: int, y: str) -> str: ) cmd = _RegisteredCommand( name=name, - source=source, args=args, ) self._factory._registry[name] = cmd @@ -247,8 +237,6 @@ def demo(x: int, y: str) -> str: args=server_args, description=getattr(cmd, "description", ""), ) - except ImportError: - pass # server module not installed except RuntimeError: pass # registry already frozen @@ -262,7 +250,7 @@ def demo(x: int, y: str) -> str: class _SandboxFactory: """Callable object that unifies ``@sandbox(...)`` decorator, - ``.register`` / ``.run``, and ``.server`` access. + ``.register`` and ``.server`` access. ``sandbox`` is a module-level singleton of this class. @@ -450,7 +438,7 @@ async def _execute( return decorator # ------------------------------------------------------------------ # - # Register + Run (@sandbox.register / sandbox.run) + # Server registration metadata # ------------------------------------------------------------------ # def list_registered(self) -> list[dict[str, Any]]: @@ -485,73 +473,6 @@ def list_registered(self) -> list[dict[str, Any]]: ) return result - def run(self, name: str, **kwargs: Any) -> Any: - """Execute a registered command synchronously. - - Legacy convenience call: creates ``code-interpreter-v1`` and calls an - already deployed command of the same name. It does not upload the local - registered function. For template services, use an explicit sandbox and - ``application_client().commands.run``. - - Args: - name: The registered command name. - **kwargs: Arguments for the command (coerced to declared - types). - - Returns: - The return value of the remote function. - - Raises: - ValueError: If *name* is not registered, a required arg - is missing, or an undeclared arg is passed. - RuntimeError: If the server responds with an error. - """ - cmd = self._get_registered(name) - coerced = _coerce_kwargs(cmd.args, kwargs) - - return run_sync(self._execute_registered(cmd, coerced)) - - async def run_async(self, name: str, **kwargs: Any) -> Any: - """Execute a registered command asynchronously. - - Same as :meth:`run` but ``await``-able. - """ - cmd = self._get_registered(name) - coerced = _coerce_kwargs(cmd.args, kwargs) - return await self._execute_registered(cmd, coerced) - - # -- internal helpers ------------------------------------------------ - - def _get_registered(self, name: str) -> _RegisteredCommand: - if name not in self._registry: - available = ", ".join(sorted(self._registry)) or "(none)" - raise ValueError(f"Unknown registered command {name!r}; available: {available}") - return self._registry[name] - - async def _execute_registered( - self, - cmd: _RegisteredCommand, - coerced_kwargs: dict[str, Any], - ) -> Any: - """Execute a registered command via the sandbox HTTP server. - - Creates a sandbox whose template runs a persistent - :mod:`agent_sandbox.server`, then sends - ``POST /commands/{name}`` with *coerced_kwargs* as the JSON body - via :meth:`~agent_sandbox.api.sandbox.Sandbox.run`, returning the command's raw JSON - value. - """ - from agent_sandbox.api.sandbox import Sandbox - - sb = await Sandbox.create( - template="code-interpreter-v1", - timeout=300, - ) - try: - return await sb.run(cmd.name, **coerced_kwargs) - finally: - await sb.kill() - # Module-level singleton ------------------------------------------------- # sandbox = _SandboxFactory() @@ -562,11 +483,6 @@ async def _execute_registered( # --------------------------------------------------------------------------- -# ``_coerce_kwargs`` is now the shared ``coerce_kwargs`` imported from -# ``agent_sandbox.utils.coerce`` at the top of this module. The alias -# ``_coerce_kwargs`` preserves internal call-sites unchanged. - - async def _detect_python_cmd(sb: Any) -> str: """Detect available python command: prefer ``python3``, fall back to ``python``.""" try: diff --git a/client-sdk/tests/test_declarative/test_register.py b/client-sdk/tests/test_declarative/test_register.py index 25fda835..f2109693 100644 --- a/client-sdk/tests/test_declarative/test_register.py +++ b/client-sdk/tests/test_declarative/test_register.py @@ -1,4 +1,4 @@ -"""Tests for @sandbox.register, sandbox.run(), type helpers, and P0 bug fixes.""" +"""Tests for @sandbox.register, type helpers, and P0 bug fixes.""" from __future__ import annotations @@ -10,16 +10,16 @@ from agent_sandbox.declarative.decorator import ( _annotation_to_type_str, - _coerce_kwargs, _CommandArg, _detect_python_cmd, - _parse_scalar, _RegisterProxy, _SandboxFactory, _ServerProxy, sandbox, ) from agent_sandbox.models.process import ProcessResult +from agent_sandbox.utils.coerce import coerce_kwargs as _coerce_kwargs +from agent_sandbox.utils.coerce import parse_scalar as _parse_scalar # --------------------------------------------------------------------------- # _annotation_to_type_str @@ -245,16 +245,25 @@ def test_register_unsupported_type_raises(self) -> None: def bad(data: list[int]) -> None: pass - def test_register_source_captured(self) -> None: + def test_register_dynamic_function_without_source( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: + from agent_sandbox.server import registry + + monkeypatch.setattr(registry, "_default_registry", registry.CommandRegistry()) factory = _SandboxFactory() + namespace: dict[str, object] = {} + exec("def dynamic(value: str) -> str:\n return value", namespace) + func = namespace["dynamic"] + assert callable(func) + registered = factory.register(func) + assert registered("value") == "value" - @factory.register - def add(a: int, b: int) -> int: - return a + b + from agent_sandbox.server.registry import default_registry - cmd = factory._registry["add"] - assert "def add" in cmd.source - assert "return a + b" in cmd.source + command = default_registry().get("dynamic") + assert command is not None + assert command.fn("server") == "server" # --------------------------------------------------------------------------- @@ -295,125 +304,6 @@ def fn(x: int, y: str = "hi") -> str: } -# --------------------------------------------------------------------------- -# sandbox.run() (registered command execution via HTTP) -# --------------------------------------------------------------------------- - - -class TestRunRegistered: - """sandbox.run('name', **kwargs) dispatches registered commands via HTTP.""" - - def test_unknown_command_raises(self) -> None: - factory = _SandboxFactory() - with pytest.raises(ValueError, match="Unknown registered command"): - factory.run("nonexistent") - - @patch("agent_sandbox.api.sandbox.Sandbox", new_callable=lambda: MagicMock) - def test_run_basic(self, mock_sandbox_cls: MagicMock) -> None: - """Run a registered command end-to-end (mocked sandbox HTTP).""" - mock_sb = AsyncMock() - mock_sb.run = AsyncMock(return_value="hello=1") - mock_sb.kill = AsyncMock() - mock_sandbox_cls.create = AsyncMock(return_value=mock_sb) - - factory = _SandboxFactory() - - @factory.register - def demo(x: int, y: str) -> str: - return f"{y}={x}" - - result = factory.run("demo", x=1, y="hello") - assert result == "hello=1" - - # Sandbox lifecycle - mock_sandbox_cls.create.assert_awaited_once() - mock_sb.run.assert_awaited_once_with("demo", x=1, y="hello") - mock_sb.kill.assert_awaited_once() - - @patch("agent_sandbox.api.sandbox.Sandbox", new_callable=lambda: MagicMock) - def test_run_type_coercion(self, mock_sandbox_cls: MagicMock) -> None: - """String arguments are coerced to declared types.""" - mock_sb = AsyncMock() - mock_sb.run = AsyncMock(return_value=10) - mock_sb.kill = AsyncMock() - mock_sandbox_cls.create = AsyncMock(return_value=mock_sb) - - factory = _SandboxFactory() - - @factory.register - def multiply(a: int, b: float) -> float: - return a * b - - result = factory.run("multiply", a="5", b="2.0") - assert result == 10 - - # Verify coerced types were passed via HTTP - mock_sb.run.assert_awaited_once_with("multiply", a=5, b=2.0) - - @patch("agent_sandbox.api.sandbox.Sandbox", new_callable=lambda: MagicMock) - def test_run_uses_run_command(self, mock_sandbox_cls: MagicMock) -> None: - """Execution goes through Sandbox.run (HTTP), not scripts.""" - mock_sb = AsyncMock() - mock_sb.run = AsyncMock(return_value=None) - mock_sb.kill = AsyncMock() - mock_sandbox_cls.create = AsyncMock(return_value=mock_sb) - - factory = _SandboxFactory() - - @factory.register - def noop() -> None: - pass - - factory.run("noop") - - # run was called with the command name - mock_sb.run.assert_awaited_once_with("noop") - # No file write occurred (old script-upload path gone) - assert not hasattr(mock_sb, "files") or not mock_sb.files.write.called - - @patch("agent_sandbox.api.sandbox.Sandbox", new_callable=lambda: MagicMock) - def test_run_creates_sandbox_with_template(self, mock_sandbox_cls: MagicMock) -> None: - """Sandbox is created with the code-interpreter-v1 template.""" - mock_sb = AsyncMock() - mock_sb.run = AsyncMock(return_value="ok") - mock_sb.kill = AsyncMock() - mock_sandbox_cls.create = AsyncMock(return_value=mock_sb) - - factory = _SandboxFactory() - - @factory.register - def echo() -> str: - return "ok" - - factory.run("echo") - - call_kw = mock_sandbox_cls.create.call_args - effective_tpl = call_kw.args[0] if call_kw.args else call_kw.kwargs.get("template") - assert effective_tpl == "code-interpreter-v1" - - @patch("agent_sandbox.api.sandbox.Sandbox", new_callable=lambda: MagicMock) - def test_run_command_error_raises(self, mock_sandbox_cls: MagicMock) -> None: - """RuntimeError from run propagates to the caller.""" - mock_sb = AsyncMock() - mock_sb.run = AsyncMock( - side_effect=RuntimeError("ValueError: some error"), - ) - mock_sb.kill = AsyncMock() - mock_sandbox_cls.create = AsyncMock(return_value=mock_sb) - - factory = _SandboxFactory() - - @factory.register - def fail() -> None: - pass - - with pytest.raises(RuntimeError): - factory.run("fail") - - # Sandbox should still be killed on error - mock_sb.kill.assert_awaited_once() - - # --------------------------------------------------------------------------- # P0 bug fix: UUID script path in @sandbox() decorator # --------------------------------------------------------------------------- @@ -513,9 +403,9 @@ def test_sandbox_has_register(self) -> None: assert callable(sandbox.register) assert isinstance(sandbox.register, _RegisterProxy) - def test_sandbox_has_run(self) -> None: - assert hasattr(sandbox, "run") - assert callable(sandbox.run) + def test_factory_has_no_implicit_remote_command_execution(self) -> None: + assert not hasattr(sandbox, "run") + assert not hasattr(sandbox, "run_async") def test_sandbox_has_server(self) -> None: assert hasattr(sandbox, "server") diff --git a/client-sdk/tests/test_declarative/test_server_missing.py b/client-sdk/tests/test_declarative/test_server_missing.py index b2864452..5cd3e3c6 100644 --- a/client-sdk/tests/test_declarative/test_server_missing.py +++ b/client-sdk/tests/test_declarative/test_server_missing.py @@ -21,15 +21,16 @@ def no_server(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setitem(sys.modules, "agent_sandbox.server", None) -def test_register_still_works_without_server(no_server: None) -> None: +def test_register_requires_server_without_partial_registration(no_server: None) -> None: factory = _SandboxFactory() - @factory.register def double(x: int) -> int: return x * 2 + with pytest.raises(ImportError, match=r"agent-sandbox-client\[server\]"): + factory.register(double) + assert factory.list_registered() == [] assert double(2) == 4 - assert {c["name"] for c in factory.list_registered()} == {"double"} _ACTIONS: list[Any] = [ @@ -44,7 +45,7 @@ def test_missing_server_error_names_the_distribution(no_server: None, action: An with pytest.raises(ImportError) as excinfo: action(factory) message = str(excinfo.value) - assert "pip install agent-sandbox-server" in message + assert "pip install 'agent-sandbox-client[server]'" in message def test_empty_namespace_after_inplace_upgrade_is_reported(monkeypatch: pytest.MonkeyPatch) -> None: diff --git a/client-sdk/tests/test_packaging.py b/client-sdk/tests/test_packaging.py index 4150dc84..67adf979 100644 --- a/client-sdk/tests/test_packaging.py +++ b/client-sdk/tests/test_packaging.py @@ -84,10 +84,9 @@ def test_wheel_metadata_has_no_cli_dependencies(built_dists: dict[str, Path]) -> for dep in ("click", "questionary", "e2b", "e2b-code-interpreter"): assert f"Requires-Dist: {dep}" not in metadata assert "Provides-Extra: cli" not in metadata - assert "Provides-Extra: server" not in metadata -def test_wheel_hard_depends_on_matching_server_and_keeps_functional_extras( +def test_wheel_offers_matching_server_only_as_extra( built_dists: dict[str, Path], ) -> None: import agent_sandbox @@ -96,8 +95,17 @@ def test_wheel_hard_depends_on_matching_server_and_keeps_functional_extras( metadata = next(zf.read(n).decode() for n in zf.namelist() if n.endswith("/METADATA")) requires = [line for line in metadata.splitlines() if line.startswith("Requires-Dist: ")] unconditional = [r for r in requires if "extra ==" not in r] - assert f"Requires-Dist: agent-sandbox-server=={agent_sandbox.__version__}" in unconditional - assert "Provides-Extra: server" not in metadata + assert not any("agent-sandbox-server" in r for r in unconditional) + assert ( + f"Requires-Dist: agent-sandbox-server=={agent_sandbox.__version__}; extra == 'server'" + in requires + ) + assert "Provides-Extra: server" in metadata + for extra in ("all", "dev"): + assert ( + f"Requires-Dist: agent-sandbox-server=={agent_sandbox.__version__}; extra == '{extra}'" + in requires + ) assert "Provides-Extra: declarative" not in metadata assert "Requires-Dist: pyyaml>=6.0.3" in unconditional mcp_deps = [r for r in requires if "extra == 'mcp'" in r] diff --git a/client-sdk/tests/test_release_validation.py b/client-sdk/tests/test_release_validation.py index abcd2760..a3c1fb0e 100644 --- a/client-sdk/tests/test_release_validation.py +++ b/client-sdk/tests/test_release_validation.py @@ -147,10 +147,12 @@ def _run_public_workflow(tmp_path, metadata=0, install=0, check=0, probe=0): @pytest.mark.parametrize("metadata_failures", [1, 7]) -def test_public_workflow_retries_propagation_then_checks_install_once(tmp_path, metadata_failures): +def test_public_workflow_retries_propagation_then_checks_both_installations( + tmp_path, metadata_failures +): result, state, sleeps = _run_public_workflow(tmp_path, metadata=metadata_failures, install=1) assert result.returncode == 0, result.stderr - assert state == {"metadata": metadata_failures + 1, "install": 2, "check": 1, "probe": 1} + assert state == {"metadata": metadata_failures + 1, "install": 3, "check": 2, "probe": 2} assert sleeps == metadata_failures + 1 assert "install failure" in result.stderr diff --git a/client-sdk/tests/test_version_lockstep.py b/client-sdk/tests/test_version_lockstep.py index 71666bc4..5c37d526 100644 --- a/client-sdk/tests/test_version_lockstep.py +++ b/client-sdk/tests/test_version_lockstep.py @@ -1,6 +1,6 @@ """The client and server distributions are released in lockstep. -The client depends on ``agent-sandbox-server==``, so the two +The client server extra depends on ``agent-sandbox-server==``, so the two ``_version.py`` files must never drift apart. """ diff --git a/docs/en/guide/authentication.md b/docs/en/guide/authentication.md index 69f3e4bd..4215b447 100644 --- a/docs/en/guide/authentication.md +++ b/docs/en/guide/authentication.md @@ -166,7 +166,7 @@ envd authentication requires the following HTTP headers (set automatically by th ## Related: the abx CLI -Setting credentials from the command line (`abx config set` / `list` / `get`, `abx create --env`) is documented in the CLI repository: [CLI Authentication](https://github.com/alibaba-agent-sandbox/agent-sandbox/blob/main/docs/en/guide/authentication.md). +Setting credentials from the command line (`abx config set` / `list` / `get`, `abx deploy --env`) is documented in the CLI repository: [CLI Authentication](https://github.com/alibaba-agent-sandbox/agent-sandbox/blob/main/docs/en/guide/authentication.md). ## STS temporary identities and private application access diff --git a/docs/en/guide/authoring-templates.md b/docs/en/guide/authoring-templates.md index f0a8c3ca..88354d1e 100644 --- a/docs/en/guide/authoring-templates.md +++ b/docs/en/guide/authoring-templates.md @@ -8,7 +8,7 @@ This is the **Server SDK**: `commands.py` starts with the image and serves HTTP This document explains how to create custom Agent Sandbox templates, including directory structure, field reference, whole-template startup, registered commands, and publishing. -A template is a directory of plain files, written by hand or generated. The `abx template scaffold` command generates one three ways (built-in scaffold, a one-sentence description, or adopting a project that already has source code); that workflow is documented in the CLI repository: [Authoring Templates (CLI)](https://github.com/alibaba-agent-sandbox/agent-sandbox/blob/main/docs/en/guide/authoring-templates.md). +A template is a directory of plain files, written by hand or generated. The `abx template init` command generates one three ways (built-in scaffold, a one-sentence description, or adopting a project that already has source code); that workflow is documented in the CLI repository: [Authoring Templates (CLI)](https://github.com/alibaba-agent-sandbox/agent-sandbox/blob/main/docs/en/guide/authoring-templates.md). --- @@ -120,7 +120,7 @@ if __name__ == "__main__": File upload and download are server endpoints (`/files/*`, `/upload`, `/download`) of the `file_ops` group, which is on by default. They are not registered from `commands.py`. Deploy does not apply a `server:` block. Turn an opt-in group on with `default_table().enable_group(CapabilityGroup.DEV_TOOLS)` (a `CapabilityGroup`, not a string) or `ABX_SERVER_ENABLED_GROUPS`. `dev_tools`, `browser`, and `agents` start disabled. -Registered commands are served by the in-sandbox HTTP server (`POST /commands/{name}`). Clients call them with `await sandbox.run("demo", x=1, y="hello")` or `abx run demo --arg x=1 --arg y=hello`, and list them with `sandbox.list_commands()` or `abx sandbox command list `. A bare shell command is a different call: `sandbox.commands.run(cmd)`. +Registered commands are served by the in-sandbox HTTP server (`POST /commands/{name}`). Clients call them with `await sandbox.run("demo", x=1, y="hello")` or `abx sandbox command run demo --arg x=1 --arg y=hello`, and list them with `sandbox.list_commands()` or `abx sandbox command list `. A bare shell command is a different call: `sandbox.commands.run(cmd)`. --- @@ -148,9 +148,9 @@ the sandbox creator owns its final deletion. ## Publishing and Deploying Templates -The Python SDK is the runtime client and does not build source templates. Normally use `abx --provider PROVIDER template launch SOURCE --yes`, then connect Python to the returned sandbox. Advanced image preparation uses `abx template build` and `abx template deploy`; before passing a prepared reference to `launch_template`, ensure its native startup does not also start the same service. See [startup ownership](template-workflows.md) and [Build & Deploy](build-and-deploy.md). +The Python SDK is the runtime client and does not build source templates. Normally use `abx --provider PROVIDER deploy SOURCE --name my-app --yes`, then connect Python to the returned sandbox. Advanced image preparation uses `abx template build` and `abx deploy`; before passing a prepared reference to `launch_template`, ensure its native startup does not also start the same service. See [startup ownership](template-workflows.md) and [Build & Deploy](build-and-deploy.md). -To publish a template to GitHub, install one locally or from a registry, adopt an existing project (`abx template scaffold --adopt`), or build and register one from the command line (`abx deploy`, `abx template deploy|create`), use the `abx` CLI; see [Related: the abx CLI](#related-the-abx-cli). +To publish a template to GitHub, install one locally or from a registry, adopt an existing project (`abx template init --adopt`), or build and register one from the command line (`abx deploy`, `abx template build`), use the `abx` CLI; see [Related: the abx CLI](#related-the-abx-cli). ## Next Steps @@ -162,4 +162,4 @@ To publish a template to GitHub, install one locally or from a registry, adopt a ## Related: the abx CLI -The command-line workflow for templates is documented in the CLI repository: [Authoring Templates (CLI)](https://github.com/alibaba-agent-sandbox/agent-sandbox/blob/main/docs/en/guide/authoring-templates.md) (`abx template scaffold`, `--adopt`, publishing, `template install`), [Deploy & Build](https://github.com/alibaba-agent-sandbox/agent-sandbox/blob/main/docs/en/guide/deploy-and-build.md) (`abx deploy`, `abx template deploy|create`) and [Using Templates](https://github.com/alibaba-agent-sandbox/agent-sandbox/blob/main/docs/en/guide/using-templates.md). +The command-line workflow for templates is documented in the CLI repository: [Authoring Templates (CLI)](https://github.com/alibaba-agent-sandbox/agent-sandbox/blob/main/docs/en/guide/authoring-templates.md) (`abx template init`, `--adopt`, publishing, `template init --from`), [Deploy & Build](https://github.com/alibaba-agent-sandbox/agent-sandbox/blob/main/docs/en/guide/deploy-and-build.md) (`abx deploy`, `abx template build`) and [Using Templates](https://github.com/alibaba-agent-sandbox/agent-sandbox/blob/main/docs/en/guide/using-templates.md). diff --git a/docs/en/guide/build-and-deploy.md b/docs/en/guide/build-and-deploy.md index f56d3d2a..02bbcc62 100644 --- a/docs/en/guide/build-and-deploy.md +++ b/docs/en/guide/build-and-deploy.md @@ -1,55 +1,17 @@ -# Build and Deploy +# Build and deploy -The Python SDK (`agent-sandbox-client`) is the **runtime** client. It creates, uses and kills sandboxes. It does not build images or register templates. Building and deploying a template is the job of the `abx` CLI. +The Python SDK is a runtime client. The CLI owns source acquisition, builds, deployment and source version publication. These are the current source commands; use `abx --help` for the installed release. -| Step | Command | Notes | -|------|---------|-------| -| Author | `abx template scaffold` | Writes `Dockerfile`, `template.yaml`, `commands.py` | -| Build | `abx template build` | Build the selected Dockerfile or explicit cloud steps | -| Publish | `abx template deploy` (or `abx deploy`) | Publish the selected build path; check the returned status before creating | -| Launch | `abx create ` or `Sandbox.create(template=...)` | Runtime; this is where the SDK starts | - -The full workflow, every option and the AI-assisted authoring steps are documented in the CLI's [Deploy and Build](https://github.com/alibaba-agent-sandbox/agent-sandbox/blob/main/docs/en/guide/deploy-and-build.md) guide. - -## Cloud steps and readiness - -Dockerfile deployment requires local Docker and the configured image registry. -`abx template deploy --build steps` uses explicit steps in `template.yaml` and -requires neither local Docker nor an ACR push. It does not translate a Dockerfile -or upload local `COPY` files automatically. For the catalog's `python-hello`, use -[the public steps preparation guide](https://github.com/Alibaba-Agent-Sandbox/awesome-templates/blob/main/docs/platform-steps.md#quick-start-python-hello-without-docker). - -In CLI 0.4.11, a direct steps deployment may return `Status: submitted` and a -`TemplateID`. Submission is not readiness: poll `abx --json template info ID` until -its state is `ready`, with a finite deadline; stop and clean up on failure. That -version returns `status` as a string containing a Python dictionary, so automation -must parse the outer JSON and use `ast.literal_eval` for that string, never `eval`. -Only then call `abx create ID --detach` or `ProviderSandbox.create(template=ID)`. -Retain each exact resource ID for cleanup (`abx kill SANDBOX_ID`, then -`abx template delete TEMPLATE_ID -y`) and verify that subsequent exact-ID reads -report not-found. A successful submit exit or an empty list alone is insufficient. - -## Using the result from Python - -```python -from agent_sandbox import ProviderSandbox as Sandbox - -async with await Sandbox.create(template="") as sandbox: - print(await sandbox.run("hello", name="Ada")) # a command registered in the template's server - proc = await sandbox.commands.run("python3 --version") +```bash +abx template init ./my-template --template python +abx deploy ./my-template --name my-app --yes +abx status my-app +abx sandbox command run my-app hello --arg name=Ada +abx delete my-app --yes ``` -Templates that ship an `agent-sandbox-server` are described in [Authoring Templates](authoring-templates.md) and [Template server HTTP API](template-server-api.md). - -## What changed - -The earlier SDK carried its own copy of the build pipeline (`Sandbox.deploy`, `Image`, `DockerBuilder`, `TemplateManager`, the `[alicloud]` extra, and the E7xxx build errors). They are removed: one pipeline in one tool. If your code called them, replace the call with the CLI commands above (a script can run `abx template deploy --yes`). - -## Next Steps - -- [Authoring Templates](authoring-templates.md) — Define templates via template.yaml -- [SDK Usage Guide](sdk-usage.md) — Runtime usage of the SDK +For reuse, prepare with `abx template build ./my-template --out ./.abx/build.json --yes`, then deploy with `abx deploy --build ./.abx/build.json --name my-app --yes`. Building creates no running instance. Deleting the application retains a separately prepared build. Clean an unused build with `abx template build --clean ./.abx/build.json --yes`; retained resources that require manual cleanup, such as images, are listed. -## Related: the abx CLI +For an existing native template, Python can call `await ProviderSandbox.create(template=template_id, provider=provider)`. Connect to the returned Sandbox ID when the CLI already started the application. `launch_template` is an advanced runtime entry: its caller must ensure that the image entrypoint or platform startup does not also start the manifest’s service. -The command-line template lifecycle is documented in the [`cli` repository](https://github.com/alibaba-agent-sandbox/agent-sandbox/blob/main/docs/en/guide/deploy-and-build.md). +[CLI authoring guide](https://github.com/Alibaba-Agent-Sandbox/agent-sandbox/blob/main/docs/en/guide/authoring-templates.md) · [SDK application connections](template-workflows.md) diff --git a/docs/en/guide/command-map.md b/docs/en/guide/command-map.md index de766f40..48f6d814 100644 --- a/docs/en/guide/command-map.md +++ b/docs/en/guide/command-map.md @@ -1,121 +1,20 @@ -# Command and SDK map - -> Historical native-interface comparison: these tables predate the current standard CLI/client migration and do not specify the current command surface. For working installation and current APIs, use [Python installation](install-python.md), [E2B migration](migrate-from-e2b.md), [native SDK usage](sdk-usage.md), and the [current CLI README](https://github.com/Alibaba-Agent-Sandbox/agent-sandbox#readme). External product versions below are historical references. - -Each row is the closest entry point for the same job in the three products. It is not parameter-for-parameter compatibility, and it does not shrink Agent Sandbox to a subset of either product. - -`abx` is the Agent Sandbox command line. The Python distribution is `agent-sandbox-client`. The default provider is Alibaba Cloud Express. After the `opensandbox` provider is installed, the `abx` command names stay the same and the calls go to an OpenSandbox server. That is a different command from running `osb`. - -Public surfaces used for this map: - -| Product | CLI | SDK | -|---|---|---| -| Agent Sandbox | `abx` (`@agent-sandbox/cli`) | `agent-sandbox-client`, `from agent_sandbox import ProviderSandbox` | -| E2B | `e2b` (`@e2b/cli`, docs for 2.16.1) | `e2b`, `from e2b import Sandbox` | -| OpenSandbox | `osb` (`opensandbox-cli`, [CLI](https://open-sandbox.ai/cli/)) | `opensandbox`, `from opensandbox import Sandbox` ([Python SDK](https://open-sandbox.ai/sdks/python)) | - -Standard synchronous and asynchronous clients are exported by `agent_sandbox`. Differences and the move are in [Migrating from E2B](migrate-from-e2b.md). The HTTP field map for the OpenSandbox provider is the design note [provider-opensandbox-mapping](../design/provider-opensandbox-mapping.md). - -A cell with "—" means that side has no matching public command or method. - -## Install and credentials - -| Job | abx | Agent Sandbox SDK | e2b | E2B SDK | osb | OpenSandbox SDK | -|---|---|---|---|---|---|---| -| Install | [CLI binary installation](https://github.com/Alibaba-Agent-Sandbox/agent-sandbox/blob/main/docs/en/guide/binary-installation.md) | [Paired source install](install-python.md) | `npm install -g @e2b/cli` | `pip install e2b` | `pip install opensandbox-cli` | `pip install opensandbox` | -| Store a credential | `abx config set sandbox_api_key` | environment variable `ABX_SANDBOX_API_KEY` | `e2b auth login` | environment variable `E2B_API_KEY` | `osb config set connection.api_key` | `ConnectionConfig(api_key=…)` or `OPEN_SANDBOX_API_KEY` | -| Show the effective config | `abx config list` | — | `e2b auth info` | — | `osb config show` | — | - -Alibaba Cloud access keys keep the official names `ALICLOUD_ACCESS_KEY_ID` and `ALICLOUD_ACCESS_KEY_SECRET`. The OpenSandbox address is `osb config set connection.domain`, and the environment variable is `OPEN_SANDBOX_DOMAIN`. - -## Sandbox lifecycle - -| Job | abx | Agent Sandbox SDK | e2b | E2B SDK | osb | OpenSandbox SDK | -|---|---|---|---|---|---|---| -| Create | `abx create ` | `Sandbox.create(template=…)` | `e2b sandbox create [template]` | `Sandbox.create(template)` | `osb sandbox create --image ` | `Sandbox.create("")` | -| Attach to an existing sandbox | `abx connect ` | `Sandbox.connect(id)` | `e2b sandbox connect ` | `Sandbox.connect(id)` | — | `Sandbox.connect(sandbox_id)` | -| List | `abx list` | `Sandbox.list()` | `e2b sandbox list` | `Sandbox.list()` | `osb sandbox list` | a manager lists sandboxes | -| Inspect one | `abx info ` | `sandbox.refresh_info()` | `e2b sandbox info ` | read the object after create or connect | `osb sandbox get ` | read the object after connect | -| Destroy | `abx kill ` | `sandbox.kill()`, or `Sandbox.kill_by_id(id)` | `e2b sandbox kill ` | `sandbox.kill()`, or `Sandbox.kill(id)` | `osb sandbox kill ` | `sandbox.kill()`; `destroy()` terminates the remote sandbox and then closes local resources | -| Extend lifetime | `abx create --timeout ` (set at create) | `sandbox.set_timeout(seconds)` | `e2b sandbox create --timeout` | `sandbox.set_timeout(seconds)` | `osb sandbox renew --timeout 30m` | `sandbox.renew(timedelta)` | -| Pause | — | `sandbox.pause()` | `e2b sandbox pause ` | `sandbox.pause()` | `osb sandbox pause ` | `sandbox.pause()` | -| Resume | — | `sandbox.resume()` | `e2b sandbox resume ` | `Sandbox.connect` attaches to a paused sandbox | `osb sandbox resume ` | `Sandbox.resume(sandbox_id=…)` | -| Check that it is up | — | `sandbox.is_running()` | `e2b sandbox list --state running` | `sandbox.is_running()` | `osb sandbox health ` | `is_healthy()` | -| Address for a port | — | `sandbox.network.get_host(port)`, `get_url(port)` | — | `sandbox.get_host(port)` | `osb sandbox endpoint --port 8080` | `sandbox.get_endpoint(port)` | - -The three create calls do not select the same kind of object. `abx create --template` and `Sandbox.create(template=…)` take a template name. `e2b sandbox create` takes an E2B template, and the build result is a snapshot. `osb sandbox create --image` and `Sandbox.create("python:3.12")` take an image URI. OpenSandbox can also take `--template` or `--snapshot-id`, and those three flags are mutually exclusive. - -`abx connect` is a line-at-a-time REPL, not a PTY. E2B's `e2b sandbox connect` attaches a terminal to the sandbox. OpenSandbox's persistent shell is `osb command session`. - -Pause depends on the runtime. On Docker, OpenSandbox pause is a cgroup freeze, and resume is `Sandbox.resume`. E2B pause can continue after a timeout. Agent Sandbox forwards `pause` and `resume` to the current provider. - -## Run something inside - -| Job | abx | Agent Sandbox SDK | e2b | E2B SDK | osb | OpenSandbox SDK | -|---|---|---|---|---|---|---| -| Run one command | `abx exec ` | `sandbox.commands.run(cmd)` | `e2b sandbox exec ` | `sandbox.commands.run(cmd)` | `osb command run -- `; the short form is `osb exec` | `sandbox.commands.run(cmd)` | -| Background command | `abx exec --background -- ` | `sandbox.commands.start(…, background=True)` | `e2b sandbox exec -b` | the background argument of `commands.run` | `osb command run --background` | `commands.run`, then `get_command_status` | -| Read background output | — | `commands.stream` | — | the command stream | `osb command logs ` | `commands.get_background_command_logs` | -| Interrupt | `abx sandbox process signal` | `commands.send_signal(pid, 15 or 9)` | — | stop that process | `osb command interrupt ` | `commands.interrupt(execution.id)` | -| List processes | `abx sandbox process list` | `commands.list()` | — | — | — | — | -| Persistent shell | each line of `abx connect` is a new process | — | `e2b sandbox connect` | PTY | `osb command session create`, then `session run` | `commands.create_session`, `run_in_session`, `delete_session` | -| Interactive terminal | `abx connect ` is a line REPL. The CLI has no PTY command | `sandbox.get_terminal()` | `e2b sandbox connect` | `sandbox.pty` | — | execd's PTY WebSocket | -| Run a snippet | — | `sandbox.run_code(code)` | — | `sandbox.run_code(code)` | — | `/code` on a code-interpreter image; the package is `opensandbox-code-interpreter` | -| Registered command | `abx run --arg k=v` | `sandbox.run(name, **kwargs)` | — | — | — | — | -| List registered commands | `abx command list ` | `sandbox.list_commands()` | — | — | — | — | - -Agent Sandbox sends only signal 15 (SIGTERM) and signal 9 (SIGKILL). OpenSandbox `interrupt` stops one execd execution, not an arbitrary numeric pid. - -## Files - -| Job | abx | Agent Sandbox SDK | e2b | E2B SDK | osb | OpenSandbox SDK | -|---|---|---|---|---|---|---| -| Read | `abx download` writes a local file | `files.read`, `read_bytes` | — | `files.read` | `osb file cat`, `osb file download` | `files.read_file` | -| Write | `abx upload` | `files.write` | — | `files.write` | `osb file write`, `osb file upload` | `files.write_files` | -| List a directory | `abx sandbox files list` | `files.list` | — | `files.list` | — | `files.list_directory` | -| Metadata | `abx sandbox files stat` | `files.get_info` | — | the info object from the files API | `osb file info` | entries from list or search | -| Make a directory | `abx sandbox files mkdir` | `files.make_dir` | — | `files.make_dir` | — | upload creates parent directories | -| Delete | `abx sandbox files rm` | `files.remove` | — | `files.remove` | — | `files.delete_files` | -| Move | `abx sandbox files mv` | `files.move`, `rename` | — | `files.rename` | — | — | -| Search | `abx sandbox files search` | — | — | — | `osb file search` | `files.search` | -| Exists | — | `files.exists` | — | `files.exists` | — | a failed read or stat means it is absent | -| Watch | — | `files.watch` | — | `files.watch_dir` | — | — | -| Signed file URL | — | `upload_url` / `download_url` raise `FileOperationError`. `files.upload_url` still requires `X-Access-Token` | — | `upload_url`, `download_url` | — | `get_endpoint` resolves a port, not a signed URL for one file | - -E2B file operations live on the SDK. The CLI reaches files through `e2b sandbox exec`. OpenSandbox `file download` replaces the local destination only after the whole download succeeds. - -## Templates - -| Job | abx | Agent Sandbox SDK | e2b | E2B SDK | osb | OpenSandbox SDK | -|---|---|---|---|---|---|---| -| Scaffold a local project | `abx template scaffold` | — | `e2b template init` | the `Template()` builder | — | — | -| Build and publish | `abx template deploy`; the steps are `build` and `push` | — (the CLI builds and deploys) | `e2b template create ` (`template build` is deprecated) | `Template.build` | `osb template create --image … --publish …` | the SDK template API | -| List | `abx template list` | — | `e2b template list` | the template API | `osb template list` | the same API | -| Inspect | `abx template info` | — | — | — | `osb template get ` | the same API | -| Delete | `abx template delete` | — | `e2b template delete` | — | `osb template delete` | the same API | -| Search a catalog | `abx template search`; install with `abx template install` | — | — | — | — | — | -| Snapshot | — | a provider capability | the template build is the snapshot | `Template.build` | `osb snapshot create ` | the snapshot API | -| Publish or unpublish | — | — | `e2b template publish`, `unpublish` | — | — | — | - -"Template" is not the same object in the three products. An Agent Sandbox template is `template.yaml` plus an image. The default provider hands that to Alibaba Cloud Express. An E2B template build produces a snapshot that can start directly. `osb template` builds a golden image on a Kubernetes runtime; the Docker runtime does not serve that API. Start OpenSandbox from a snapshot with `osb sandbox create --snapshot-id`. - -## Capabilities that belong to one side - -| Capability | Where | The other two | -|---|---|---| -| Swappable sandbox providers | `abx provider`, and the SDK provider plugins | E2B and OpenSandbox each own their control plane | -| Modal-style functions | `@sandbox`, see [Declarative usage](declarative-usage.md) | no decorator of that name | -| Registered commands | `abx run`, `sandbox.run()` | no equivalent layer | -| Agents API | in-sandbox HTTP. Codex and Qwen Code are supported today. See [Agents API](agents-api.md) | no `/v1/agents/sessions` | -| MCP | `abx mcp install/start/status/deploy` | E2B has no command group of that name. OpenSandbox has `osb skills`, which installs its own skill packs | -| Egress policy | provider policy | E2B uses its own network limits. OpenSandbox uses `osb egress get/patch` | -| Credential store | inject environment variables at create | OpenSandbox also has `osb credential-vault`, which needs `--credential-proxy` at create | -| Diagnostic logs | `abx sandbox system metrics` | `e2b sandbox logs`, `e2b sandbox metrics`. OpenSandbox uses `osb diagnostics logs/events`; the older entry is `osb devops` | -| Per-endpoint route policy | `agent-sandbox-server` policy, see [Fine-grained capabilities](fine-grained-capabilities.md) | that is the HTTP server inside the sandbox, not the E2B or OpenSandbox control plane | - -## Related - -- [Migrating from E2B](migrate-from-e2b.md) -- [Comparison](comparison.md) -- [CLI reference](https://github.com/alibaba-agent-sandbox/agent-sandbox/blob/main/docs/en/reference/cli-reference.md) -- [Providers](providers.md) +# CLI and SDK responsibilities + +This guide describes the current CLI source. Standard Python `Sandbox` / `AsyncSandbox`, the cross-provider `ProviderSandbox`, and the application HTTP client are distinct entry points. Check the installed release’s `--help` before using these commands. + +| Task | CLI | ProviderSandbox / Python | +| --- | --- | --- | +| Acquire and author source | `abx template list / info / init` | `—` | +| Prepare a reusable build | `abx template build SOURCE --yes` | `—` | +| Publish a source version | `abx template publish SOURCE --tag VERSION --yes` | `—` | +| Deploy source | `abx deploy SOURCE --name APP --yes` | `—` | +| Create from a native template | `abx deploy --template-id TEMPLATE --name APP --yes` | `await ProviderSandbox.create(template=..., provider=...)` | +| Inspect and delete | `abx list; abx status APP; abx delete APP --yes` | `await sandbox.refresh_info(); await sandbox.kill()` | +| Run a process | `abx sandbox exec APP -- COMMAND` | `await sandbox.commands.run(command)` | +| Invoke a registered function | `abx sandbox command run APP FUNCTION --arg key=value` | `await sandbox.run(name, **kwargs)` | +| List registered functions | `abx sandbox command list APP` | `await sandbox.list_commands()` | +| Provider-native operations | `abx provider command list PROVIDER; abx provider run OPERATION` | `agent_sandbox.providers.commands` | + +A local CLI deployment name is not a remote Sandbox ID. Inspect `abx status APP`, then connect to its actual ID with `ProviderSandbox.connect(id, provider=...)` and the same provider. `close()` releases a client; the creator owns remote deletion. Managed applications expose their own HTTP API and are not a single Sandbox handle. + +[Standard client migration](migrate-from-e2b.md) · [Application connections and authentication](template-workflows.md) · [Template authoring](authoring-templates.md) diff --git a/docs/en/guide/comparison.md b/docs/en/guide/comparison.md index 36892f94..d693d908 100644 --- a/docs/en/guide/comparison.md +++ b/docs/en/guide/comparison.md @@ -51,8 +51,8 @@ from account availability and live acceptance; see [providers](providers.md). ## Template applications -Use `abx template launch ` to prepare and start the complete template. -Advanced `template build` / `template deploy` handle image preparation separately. +Use `abx deploy --name my-app` to prepare and start the complete template. +Advanced `template build` / `deploy --build` handle image preparation separately. After launch, bind an application client with `await sandbox.application_client(9000, token=application_token)` and call `client.commands.run("hello", name="Python")`. This requires a running template diff --git a/docs/en/guide/conditional-acceptance.md b/docs/en/guide/conditional-acceptance.md index 3b8069a1..7c013508 100644 --- a/docs/en/guide/conditional-acceptance.md +++ b/docs/en/guide/conditional-acceptance.md @@ -15,8 +15,8 @@ Express supports pause/resume and Snapshot after the corresponding account/regio | Pause | `sandbox.pause()` | `abx sandbox pause "$SANDBOX_ID"` | | Resume the same sandbox | `Sandbox.connect(sandbox_id)` | `abx sandbox resume "$SANDBOX_ID"` | | Create a snapshot | `sandbox.create_snapshot(name="checkpoint", request_timeout=300)` | `abx sandbox snapshot create "$SANDBOX_ID" --name checkpoint` | -| List snapshots | `Sandbox.list_snapshots(sandbox_id=sandbox_id)`, consume the paginator | `abx sandbox snapshot list "$SANDBOX_ID" --format json` | -| Create from a snapshot | `Sandbox.create(template=snapshot.snapshot_id)` | `abx create "$SNAPSHOT_ID" --detach` | +| List snapshots | `Sandbox.list_snapshots(sandbox_id=sandbox_id)`, consume the paginator | `abx --json sandbox snapshot list "$SANDBOX_ID"` | +| Create from a snapshot | `Sandbox.create(template=snapshot.snapshot_id)` | `abx deploy --template-id "$SNAPSHOT_ID" --name restored --yes` | | Delete a snapshot | `Sandbox.delete_snapshot(snapshot.snapshot_id)` | `abx sandbox snapshot delete "$SNAPSHOT_ID"` | Python examples use `from agent_sandbox import Sandbox`; asynchronous callers use `AsyncSandbox` and await the appropriate operations. These standard clients are distinct from the native Provider/Server extension at `agent_sandbox.api.sandbox.Sandbox`. A native provider feature gate is not a statement that the Express standard API is unavailable. Python spells the method `create_snapshot`; TypeScript uses `createSnapshot`, with argument shapes determined by the installed SDK version. @@ -54,23 +54,27 @@ needs no local Docker build or private registry credentials. ## Provision a uniquely named fixture Choose the same region/Team as the runtime key. Set REGION and TEAM_ID from local -configuration; NAME must be unique and at most36 characters. From a checkout with -the standard CLI entry use this existing project extension: +configuration; NAME must be unique and at most36 characters. Use the provider management operation: ```bash REGION=cn-hangzhou -NAME="conditional-$(date +%s)" -abx template from-image \ - "fc-e2b-registry.${REGION}.cr.aliyuncs.com/runtime-v2/base:v0.0.53" \ - --name "$NAME" --team-id "$TEAM_ID" --region "$REGION" \ - --generation 2 --cpu 2 --memory 2048 --disk-size 15360 --no-envd-inject -abx template info "$TEMPLATE_ID" +cat > template-create.json <<'JSON' +{ + "name": "conditional-UNIQUE", + "team_id": "YOUR_TEAM_ID", + "image": "fc-e2b-registry.cn-hangzhou.cr.aliyuncs.com/runtime-v2/base:v0.0.53", + "generation": 2, + "cpu": 2, + "memory_size": 2048, + "disk_size": 15360, + "envd_inject": false +} +JSON +abx --provider alicloud-express --region "$REGION" provider run template.create --input-file template-create.json --yes +abx --provider alicloud-express --region "$REGION" provider run template.get --input-file template-get.json ``` -The create result supplies TEMPLATE_ID. Poll info until build state is ready; -creation is asynchronous. Accepted native CLI builds use `template create` for -this image-registration operation; inspect installed help before choosing it. -Do not pass --force or update existing templates for this test. +Replace the unique name, Team and image region before running. Put the returned `template_id` and the same `team_id` in `template-get.json`; poll until the build is ready. Submission is asynchronous. The create operation rejects existing names. These commands follow the current source CLI; inspect installed help when using an older release. ## Run and verify @@ -92,9 +96,11 @@ absence rather than expecting a second delete to return false. ## Cleanup and evidence +Before deleting the template, write this run’s `template_id` to `template-delete.json` (a JSON object such as `{ "template_id": "owned-template-id" }`), then run the Provider operation below. + Delete restored/source sandboxes before their occupied snapshot. Delete only resources created by this run. Verify exact resource absence after cleanup, then -remove the owned fixture with `abx template delete "$TEMPLATE_ID" --yes`. +remove the owned fixture with `abx --provider alicloud-express provider run template.delete --input-file template-delete.json --yes`. Record source/artifact SHA, actual passes/failures/skips, admission errors and owned residual count; omit keys, account identifiers and private endpoint details. Do not upload credentials or dispatch cloud/model acceptance in Actions. diff --git a/docs/en/guide/environment-variables.md b/docs/en/guide/environment-variables.md index 114fa0b4..babe0029 100644 --- a/docs/en/guide/environment-variables.md +++ b/docs/en/guide/environment-variables.md @@ -45,7 +45,7 @@ async def main(): asyncio.run(main()) ``` -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). +The CLI passes `abx deploy SOURCE --name APP --env KEY=value` to the created runtime. Declare required runtime environment in the template and keep secrets out of source. Python can use `ProviderSandbox.create(envs=...)` directly. When attaching to an instance you do not own, release the local connection with `await sandbox.close()` instead of deleting the instance. diff --git a/docs/en/guide/express-quickstart.md b/docs/en/guide/express-quickstart.md index 8f2d4e51..83ba20ba 100644 --- a/docs/en/guide/express-quickstart.md +++ b/docs/en/guide/express-quickstart.md @@ -10,7 +10,7 @@ source builds require the documented Docker/registry prerequisites. ```bash export ABX_SERVER_TOKEN='replace_me' -abx --provider alicloud-express template launch 'alibaba-agent-sandbox/awesome-templates//python-hello' +abx --provider alicloud-express deploy 'alibaba-agent-sandbox/awesome-templates//python-hello' --name my-app ``` The launcher prepares the provider reference, creates the sandbox, runs the diff --git a/docs/en/guide/install-python.md b/docs/en/guide/install-python.md index 4b34822a..d881e9d7 100644 --- a/docs/en/guide/install-python.md +++ b/docs/en/guide/install-python.md @@ -21,7 +21,7 @@ Check the installed pair: python -c "from importlib.metadata import version; print(version('agent-sandbox-client'), version('agent-sandbox-server'))" ``` -Install the [abx CLI](https://github.com/Alibaba-Agent-Sandbox/agent-sandbox) separately. For template launch in this guide, use CLI 0.5.0 or newer and configure the selected provider before creating a sandbox. +Install the [abx CLI](https://github.com/Alibaba-Agent-Sandbox/agent-sandbox) separately. Named deployment examples in the current guides require the matching current CLI source. Older releases may expose different commands; check their help and configure the selected provider before deployment. Project configuration uses `ABX_SANDBOX_API_KEY`, `ABX_SANDBOX_BASE_URL` and `ABX_SANDBOX_DOMAIN` for all project entries, including standard Sandbox / @@ -44,6 +44,29 @@ python -m pip install --index-url https://pypi.org/simple 'agent-sandbox-server= The server runs the application inside an image. Installing it does not create a sandbox. See the [server guide](server-sdk.md) to register commands and start the service. +## Client and runtime boundaries in current source + +The current checkout makes the local runtime optional. Install only the client +for standard `Sandbox` / `AsyncSandbox`, `ProviderSandbox`, `ServerClient`, template +YAML and the remote `@sandbox(...)` decorator: + +```bash +python -m pip install ./client-sdk +``` + +For local `@sandbox.register` or `sandbox.server`, install the matching runtime: + +```bash +python -m pip install ./server-sdk './client-sdk[server]' +``` + +`[all]` and `[dev]` include `[server]`. The pinned public release above predates +this dependency change; source installation does not publish a new release. +Registration belongs in the application server program and does not upload a +local function. The module-level `sandbox.run` / `sandbox.run_async` have been +removed: call an explicitly selected application's `ServerClient.commands.run` +instead. `ProviderSandbox.run` and the remote decorator remain available. + ## Development from a checkout From the repository root, install both local distributions together: diff --git a/docs/en/guide/provider-commands.md b/docs/en/guide/provider-commands.md index b2a784c8..bd6697d1 100644 --- a/docs/en/guide/provider-commands.md +++ b/docs/en/guide/provider-commands.md @@ -183,17 +183,17 @@ these registered commands do not silently change the legacy native pause/resume ```sh abx --json provider command list alicloud-express abx --json provider command info alicloud-express snapshot.create -abx --json provider command run alicloud-express sandbox.pause \ +abx --json --provider alicloud-express provider run sandbox.pause \ --input '{"sandbox_id":""}' --yes -abx --json provider command run alicloud-express sandbox.resume \ +abx --json --provider alicloud-express provider run sandbox.resume \ --input '{"sandbox_id":"","timeout":600}' --yes -abx --json provider command run alicloud-express snapshot.create \ +abx --json --provider alicloud-express provider run snapshot.create \ --input '{"sandbox_id":"","name":"checkpoint","request_timeout":300}' --yes -abx --json provider command run alicloud-express snapshot.list \ +abx --json --provider alicloud-express provider run snapshot.list \ --input '{"sandbox_id":"","limit":100}' -abx --json provider command run alicloud-express snapshot.restore \ +abx --json --provider alicloud-express provider run snapshot.restore \ --input '{"snapshot_id":"","timeout":300}' --yes -abx --json provider command run alicloud-express snapshot.delete \ +abx --json --provider alicloud-express provider run snapshot.delete \ --input '{"snapshot_id":""}' --yes ``` diff --git a/docs/en/guide/providers.md b/docs/en/guide/providers.md index a0311f57..c5824ba0 100644 --- a/docs/en/guide/providers.md +++ b/docs/en/guide/providers.md @@ -129,11 +129,11 @@ Highest priority first: | Where | Example | |---|---| | SDK argument | `await ProviderSandbox.create(provider="my-cloud")` | -| CLI flag | `abx create --provider my-cloud` | +| CLI flag | `abx --provider my-cloud deploy --template-id base --name my-sandbox` | | Process environment | `export ABX_PROVIDER=my-cloud` | | `./.env` | `ABX_PROVIDER=my-cloud` | | `~/.abx/.env` | `ABX_PROVIDER=my-cloud` | -| `~/.abx/config.toml` | `[core]` `provider = "my-cloud"` (written by `abx provider default my-cloud`) | +| `~/.abx/config.toml` | `[core]` `provider = "my-cloud"` (written by `abx config set provider my-cloud`) | | Built-in | `alicloud-express` | ```python @@ -159,9 +159,9 @@ Default provider management: ```bash abx provider list -abx provider default my-cloud # persist in ~/.abx/config.toml -abx provider default # show the current default -abx provider default alicloud-express +abx config set provider my-cloud # persist in ~/.abx/config.toml +abx config set provider # show the current default +abx config set provider alicloud-express ``` --- @@ -268,7 +268,7 @@ Installing a plugin does not implement every interface. Inspect `abx provider in | Read and write files | `files.read/write` | `upload`, `download` | | Call a template application | `await application_client(port, token=...)`, then `client.commands.run` | Named-command entry, see the [server API](template-server-api.md) | | Resolve an application endpoint | `await network.resolve(port)` returns the URL and gateway headers | No dedicated URL command | -| Prepare a source template | Prepare with the CLI, then connect to its sandbox or use a prepared reference | `template launch`, see the [workflow guide](template-workflows.md) | +| Prepare a source template | Prepare with the CLI, then connect to its sandbox or use a prepared reference | `abx deploy SOURCE --name APP`, see the [workflow guide](template-workflows.md) | Process execution, named commands, interpreters and PTY are different interfaces; one working interface does not implement the others. Preserve gateway headers and provide the application token explicitly when calling an application. See the [SDK reference](../reference/api-reference.md) for the complete methods. diff --git a/docs/en/guide/sdk-skill.md b/docs/en/guide/sdk-skill.md index ad25c662..924da97e 100644 --- a/docs/en/guide/sdk-skill.md +++ b/docs/en/guide/sdk-skill.md @@ -8,7 +8,7 @@ The guide covers: - client code: `Sandbox.create`, `commands.run`, `run`, `files`, and the built-in provider `alicloud-express` - a template: `template.yaml`, `Dockerfile`, `commands.py` with `@command` and `start()` -- deploy: `abx template deploy` (the Python package does not build images) +- deploy: `abx deploy` (the Python package does not build images) - test: `make test` and a local `commands.py` health check - publish: one version for both packages, released from GitHub diff --git a/docs/en/guide/sdk-usage.md b/docs/en/guide/sdk-usage.md index 8153b10f..9ae14084 100644 --- a/docs/en/guide/sdk-usage.md +++ b/docs/en/guide/sdk-usage.md @@ -24,7 +24,7 @@ The template-author example below retains its explicit deployment prerequisite. ## Template applications -Launch the complete template with `abx template launch `, then use +Launch the complete template with `abx deploy --name my-app`, then use `application_client` to call its registered commands with the application token. Source image preparation and platform configuration belong to the CLI. See [Express quickstart](express-quickstart.md) and [template launch](template-workflows.md). diff --git a/docs/en/guide/template-server-api.md b/docs/en/guide/template-server-api.md index 92f1e528..75e4cbc7 100644 --- a/docs/en/guide/template-server-api.md +++ b/docs/en/guide/template-server-api.md @@ -37,7 +37,7 @@ asyncio.run(main()) ``` `close()` releases this client connection; it leaves the existing sandbox running. -The creator remains responsible for `kill()` or `abx kill ID`. +The creator remains responsible for `kill()` or `abx delete ID`. This resolves the endpoint for that sandbox and port, preserving the full proxy prefix and signed query. Gateway headers and the application `ABX_SERVER_TOKEN` are separate; no control-plane API key is copied and no anonymous authentication retry occurs. Redirects (including another sandbox on the same origin), URL userinfo and authority overrides are rejected. Operation-signed endpoints require the provider's per-operation resolver, never generic prefix joining. diff --git a/docs/en/guide/template-workflows.md b/docs/en/guide/template-workflows.md index 0b436605..dfb90297 100644 --- a/docs/en/guide/template-workflows.md +++ b/docs/en/guide/template-workflows.md @@ -1,6 +1,6 @@ # Launch and use a template -Use `abx --provider PROVIDER template launch SOURCE --yes` to prepare and start a source template, then connect with Python using the returned sandbox ID and the same provider. If the platform already provides a template ID, Python can create it directly. +Use `abx --provider PROVIDER deploy SOURCE --name my-app --yes` to prepare and start a source template, then connect with Python using the returned sandbox ID and the same provider. If the platform already provides a template ID, Python can create it directly. ## Python application consumption @@ -21,7 +21,7 @@ finally: The client also sends the explicit application token in the server's existing `X-Access-Token` header when that header is unused. This keeps authentication working through proxies that strip `Authorization`, including OpenSandbox 1.1.1. Existing gateway headers, including defaults on an injected HTTP client, are never replaced. Bearer authentication remains available for services that require it. -For advanced preparation, `launch_template` creates a new sandbox from a prepared provider reference and runs the manifest's `start_cmd`. The reference must not also start the same service through its native startup command or image entrypoint. An ordinary `abx template deploy` can retain native startup behavior; its returned reference is not automatically idle. If the CLI has already started the application, connect to its sandbox as above. +For advanced preparation, `launch_template` creates a new sandbox from a prepared provider reference and runs the manifest's `start_cmd`. The reference must not also start the same service through its native startup command or image entrypoint. CLI build artifacts retain their declared startup behavior; do not treat a prepared reference as an instruction to start the service twice. If the CLI has already started the application, connect to its sandbox as above. To let Python own startup and cleanup after checking that preparation boundary: diff --git a/docs/en/guide/troubleshooting.md b/docs/en/guide/troubleshooting.md index 9efce864..8c60d3d5 100644 --- a/docs/en/guide/troubleshooting.md +++ b/docs/en/guide/troubleshooting.md @@ -93,11 +93,11 @@ export ALICLOUD_ACCESS_KEY_SECRET="your-ak-secret" 1. Check the template name for typos 2. Confirm the template is installed: ```bash - abx template install owner/repo + abx template init ./project --from owner/repo ``` 3. Test with the default template: ```bash - abx create base + abx deploy --template-id base --name my-sandbox ``` ### E2009 — SandboxNotFoundError @@ -150,10 +150,10 @@ See [explicit template workflows](template-workflows.md) and [Template server HT abx list --status running # Destroy unneeded sandboxes -abx kill sbx-xxxx +abx delete sbx-xxxx # Batch destroy -abx kill --all --yes +abx delete --yes ``` --- @@ -174,14 +174,14 @@ abx kill --all --yes result = await sandbox.commands.run("long-command", timeout=300) # Increase timeout in CLI -abx exec sbx-xxxx "long-command" --timeout 300 +abx sandbox exec sbx-xxxx "long-command" --timeout 300 ``` --- ## Build and Deploy Issues -Build, push and deploy errors (the `E7xxx` codes, a missing LLM key for `abx create` / `abx template scaffold`) are raised by the `abx` CLI, not by this SDK. See the CLI troubleshooting guide and [Build and deploy](build-and-deploy.md). +Build, push and deploy errors (the `E7xxx` codes, a missing LLM key for `abx template init --prompt` / `abx template init`) are raised by the `abx` CLI, not by this SDK. See the CLI troubleshooting guide and [Build and deploy](build-and-deploy.md). --- @@ -239,4 +239,4 @@ python -m pip show agent-sandbox-client ## Related: the abx CLI -CLI-specific problems (`abx` not found, `abx deploy` with no Dockerfile, long deploy steps, invalid shortcut targets, `abx template scaffold --adopt` confirmations) are covered in the CLI repository: [Troubleshooting (CLI)](https://github.com/alibaba-agent-sandbox/agent-sandbox/blob/main/docs/en/guide/troubleshooting.md). +CLI-specific problems (`abx` not found, `abx deploy` with no Dockerfile, long deploy steps, invalid shortcut targets, `abx template init --adopt` confirmations) are covered in the CLI repository: [Troubleshooting (CLI)](https://github.com/alibaba-agent-sandbox/agent-sandbox/blob/main/docs/en/guide/troubleshooting.md). diff --git a/docs/en/reference/configuration.md b/docs/en/reference/configuration.md index 2e7f8fee..18e99a76 100644 --- a/docs/en/reference/configuration.md +++ b/docs/en/reference/configuration.md @@ -47,8 +47,8 @@ Each key has one name. Product settings use `ABX_`. A vendor that already p The same chain is what these calls read: - `Sandbox.create` and the other SDK clients (`load_config`) -- natural-language `abx create` (the coding agent) -- `abx template install` / `abx template search` (GitHub token) +- natural-language `abx template init --prompt` (the coding agent) +- `abx template init --from SOURCE` / `abx template list` (GitHub token) - `abx mcp install` (the sandbox API key copied into the editor config) `abx mcp install` writes `ABX_SANDBOX_API_KEY` into the Cursor and Claude config. `ABX_SANDBOX_BASE_URL` and `ALIBABA_CLOUD_REGION_ID` are copied into the same file only when the process environment sets them. `abx mcp start` loads `~/.abx` again, so a region saved with `abx config set region` still applies when the editor starts the server. @@ -134,7 +134,7 @@ Config file path: `~/.abx/config.toml` ```toml [core] -provider = "alicloud-express" # default provider (abx provider default NAME) +provider = "alicloud-express" # default provider (abx config set provider NAME) [transport] sandbox_base_url = "https://api.cn-hangzhou.e2b.fc.aliyuncs.com" diff --git a/docs/en/reference/error-codes.md b/docs/en/reference/error-codes.md index 4ece8cda..97a9d350 100644 --- a/docs/en/reference/error-codes.md +++ b/docs/en/reference/error-codes.md @@ -65,8 +65,8 @@ except SandboxError as e: | E3002 | `ProcessError` | Non-zero exit code | — | Command returned a non-zero exit code (carries `exit_code`, `stdout`, `stderr` attributes) | | E3003 | `CodeExecutionError` | Code execution failed | Check code syntax and runtime dependencies | Code Interpreter failed to execute code | | E3004 | `CapabilityNotSupportedError` | Selected call or endpoint unavailable | Inspect the exact operation and consumer; retired template flags cannot bypass it | Includes disabled server endpoints; unsupported provider operations use E8004 | -| E3005 | `CommandNotFoundError` | Named command not found | Check the command name with `sandbox.list_commands()`, and make sure the SandboxServer is running in the sandbox (`sandbox.start_server()`) | `Sandbox.run(name)` / `abx run ` names a command the in-sandbox SandboxServer does not have (HTTP 404). Carries the `command_name` attribute | -| E3006 | `EnvdRpcError` | envd rejected an RPC call (HTTP >= 400) | Check the command and arguments, or run with `abx -v` for the raw error response | Sandbox envd returned HTTP >= 400 (e.g., a `process.Process/Start` 500 for an unknown executable). Carries `status_code`, `rpc_path`, `envd_error` (parsed JSON body) and `body_text`; the message never includes the full sandbox URL. `abx connect` maps it to a single friendly notice (e.g., `Command not found: `) | +| E3005 | `CommandNotFoundError` | Named command not found | Check the command name with `sandbox.list_commands()`, and make sure the SandboxServer is running in the sandbox (`sandbox.start_server()`) | `Sandbox.run(name)` / `abx sandbox command run ` names a command the in-sandbox SandboxServer does not have (HTTP 404). Carries the `command_name` attribute | +| E3006 | `EnvdRpcError` | envd rejected an RPC call (HTTP >= 400) | Check the command and arguments, or run with `abx -v` for the raw error response | Sandbox envd returned HTTP >= 400 (e.g., a `process.Process/Start` 500 for an unknown executable). Carries `status_code`, `rpc_path`, `envd_error` (parsed JSON body) and `body_text`; the message never includes the full sandbox URL. `abx sandbox exec` maps it to a single friendly notice (e.g., `Command not found: `) | ### E3004 Details @@ -96,7 +96,7 @@ Interpreter HTTP 404, missing runtime and not-ready responses never fall back to | Error Code | Exception Class | Meaning | Suggestion | Trigger Scenario | |------------|----------------|---------|------------|------------------| | E5000 | `NetworkError` | Network error (base class) | — | General network connection errors | -| E5000 | `GitHubRateLimitError` | GitHub anonymous API rate limit hit while fetching templates | Run `abx config set github_token` (masked input; the CLI offers this interactively and retries once), or inject `GITHUB_TOKEN` in CI | `abx template install` / `abx install` / `abx template search` without a token configured | +| E5000 | `GitHubRateLimitError` | GitHub anonymous API rate limit hit while fetching templates | Run `abx config set github_token` (masked input; the CLI offers this interactively and retries once), or inject `GITHUB_TOKEN` in CI | `abx template init --from SOURCE` / `abx template list` without a token configured | | E5001 | `ConnectionError_` | Connection failed | Check network connectivity and firewall rules | Unable to connect to the sandbox service | > **Note**: The `ConnectionError_` class name has a trailing underscore to avoid shadowing Python's built-in `ConnectionError`. diff --git a/docs/zh/guide/authentication.md b/docs/zh/guide/authentication.md index ab3c1c63..4d0a11f1 100644 --- a/docs/zh/guide/authentication.md +++ b/docs/zh/guide/authentication.md @@ -165,7 +165,7 @@ envd 认证需要以下 HTTP 头(已由 SDK 自动设置): ## 相关:abx CLI -通过命令行设置凭证(`abx config set` / `list` / `get`、`abx create --env`)的说明位于 CLI 仓库:[CLI 认证配置](https://github.com/alibaba-agent-sandbox/agent-sandbox/blob/main/docs/zh/guide/authentication.md)。 +通过命令行设置凭证(`abx config set` / `list` / `get`、`abx deploy --env`)的说明位于 CLI 仓库:[CLI 认证配置](https://github.com/alibaba-agent-sandbox/agent-sandbox/blob/main/docs/zh/guide/authentication.md)。 ## STS 临时身份与私有应用访问 diff --git a/docs/zh/guide/authoring-templates.md b/docs/zh/guide/authoring-templates.md index 17f365b4..03d6fa15 100644 --- a/docs/zh/guide/authoring-templates.md +++ b/docs/zh/guide/authoring-templates.md @@ -8,7 +8,7 @@ 本文档介绍如何创建自定义 Agent Sandbox 模板,包括目录结构、字段参考、整体启动、注册命令和发布流程。 -模板是一个由纯文件组成的目录,可手写也可生成。`abx template scaffold` 命令有三种生成方式(内置脚手架、一句话描述、适配已有源码的项目);该工作流记录在 CLI 仓库:[模板编写(CLI)](https://github.com/alibaba-agent-sandbox/agent-sandbox/blob/main/docs/zh/guide/authoring-templates.md)。 +模板是一个由纯文件组成的目录,可手写也可生成。`abx template init` 命令有三种生成方式(内置脚手架、一句话描述、适配已有源码的项目);该工作流记录在 CLI 仓库:[模板编写(CLI)](https://github.com/alibaba-agent-sandbox/agent-sandbox/blob/main/docs/zh/guide/authoring-templates.md)。 --- @@ -118,7 +118,7 @@ if __name__ == "__main__": 文件上传和下载是 server 的端点(`/files/*`、`/upload`、`/download`),属于默认开启的 `file_ops` 组,不在 `commands.py` 里注册。部署不会应用 `server:` 块。可选组用 `default_table().enable_group(CapabilityGroup.DEV_TOOLS)`(参数是 `CapabilityGroup`,不是字符串)或 `ABX_SERVER_ENABLED_GROUPS` 打开。`dev_tools`、`browser`、`agents` 默认关闭。 -注册的命令由沙箱内 HTTP server 提供(`POST /commands/{name}`)。客户端用 `await sandbox.run("demo", x=1, y="hello")` 或 `abx run demo --arg x=1 --arg y=hello` 调用,用 `sandbox.list_commands()` 或 `abx sandbox command list ` 列出。裸 shell 命令是另一个调用:`sandbox.commands.run(cmd)`。 +注册的命令由沙箱内 HTTP server 提供(`POST /commands/{name}`)。客户端用 `await sandbox.run("demo", x=1, y="hello")` 或 `abx sandbox command run demo --arg x=1 --arg y=hello` 调用,用 `sandbox.list_commands()` 或 `abx sandbox command list ` 列出。裸 shell 命令是另一个调用:`sandbox.commands.run(cmd)`。 --- @@ -143,9 +143,9 @@ if __name__ == "__main__": ## 发布与部署模板 -Python SDK 是运行时客户端,不构建源码模板。通常使用 `abx --provider PROVIDER template launch SOURCE --yes`,再用 Python 连接返回的沙箱。高级镜像准备使用 `abx template build` 和 `abx template deploy`;将准备好的引用传给 `launch_template` 前,确认原生启动路径不会同时启动同一个服务。见[启动方说明](template-workflows.md)和[构建与部署](build-and-deploy.md)。 +Python SDK 是运行时客户端,不构建源码模板。通常使用 `abx --provider PROVIDER deploy SOURCE --name my-app --yes`,再用 Python 连接返回的沙箱。高级镜像准备使用 `abx template build` 和 `abx deploy`;将准备好的引用传给 `launch_template` 前,确认原生启动路径不会同时启动同一个服务。见[启动方说明](template-workflows.md)和[构建与部署](build-and-deploy.md)。 -将模板发布到 GitHub、从本地或 registry 安装模板、适配已有项目(`abx template scaffold --adopt`),或通过命令行构建并注册模板(`abx deploy`、`abx template deploy|create`),请使用 `abx` CLI;见[相关:abx CLI](#相关abx-cli)。 +将模板发布到 GitHub、从本地或 registry 安装模板、适配已有项目(`abx template init --adopt`),或通过命令行构建并注册模板(`abx deploy`、`abx template build`),请使用 `abx` CLI;见[相关:abx CLI](#相关abx-cli)。 ## 下一步 @@ -157,4 +157,4 @@ Python SDK 是运行时客户端,不构建源码模板。通常使用 `abx --p ## 相关:abx CLI -模板的命令行工作流记录在 CLI 仓库:[模板编写(CLI)](https://github.com/alibaba-agent-sandbox/agent-sandbox/blob/main/docs/zh/guide/authoring-templates.md)(`abx template scaffold`、`--adopt`、发布、`template install`)、[部署与构建](https://github.com/alibaba-agent-sandbox/agent-sandbox/blob/main/docs/zh/guide/deploy-and-build.md)(`abx deploy`、`abx template deploy|create`)和[模板使用](https://github.com/alibaba-agent-sandbox/agent-sandbox/blob/main/docs/zh/guide/using-templates.md)。 +模板的命令行工作流记录在 CLI 仓库:[模板编写(CLI)](https://github.com/alibaba-agent-sandbox/agent-sandbox/blob/main/docs/zh/guide/authoring-templates.md)(`abx template init`、`--adopt`、发布、`template init --from`)、[部署与构建](https://github.com/alibaba-agent-sandbox/agent-sandbox/blob/main/docs/zh/guide/deploy-and-build.md)(`abx deploy`、`abx template build`)和[模板使用](https://github.com/alibaba-agent-sandbox/agent-sandbox/blob/main/docs/zh/guide/using-templates.md)。 diff --git a/docs/zh/guide/build-and-deploy.md b/docs/zh/guide/build-and-deploy.md index 8b020962..ccef68f0 100644 --- a/docs/zh/guide/build-and-deploy.md +++ b/docs/zh/guide/build-and-deploy.md @@ -1,52 +1,17 @@ # 构建与部署 -Python SDK(`agent-sandbox-client`)是**运行时**客户端:创建、使用、销毁沙箱。它不构建镜像,也不注册模板。模板的构建和部署由 `abx` CLI 负责。 - -| 步骤 | 命令 | 说明 | -|------|------|------| -| 编写 | `abx template scaffold` | 生成 `Dockerfile`、`template.yaml`、`commands.py` | -| 构建 | `abx template build` | 按所选 Dockerfile 或显式云端 steps 构建 | -| 发布 | `abx template deploy`(或 `abx deploy`) | 发布所选构建路径;创建前确认返回的实际状态 | -| 启动 | `abx create ` 或 `Sandbox.create(template=...)` | 运行时;SDK 从这里开始 | - -完整流程、全部选项和 AI 辅助编写步骤见 CLI 的[部署与构建](https://github.com/alibaba-agent-sandbox/agent-sandbox/blob/main/docs/zh/guide/deploy-and-build.md)指南。 - -## 云端 steps 与就绪状态 - -Dockerfile 部署需要本地 Docker 和已配置的镜像仓库。 -`abx template deploy --build steps` 使用 `template.yaml` 中的显式步骤,不需要本地 -Docker 或 ACR 上传,但不会自动转换 Dockerfile,也不会自动上传本地 `COPY` 文件。 -目录中的 `python-hello` 可按[公开 steps 准备指南](https://github.com/Alibaba-Agent-Sandbox/awesome-templates/blob/main/docs/platform-steps.zh-CN.md#无-docker-运行-python-hello)操作。 - -CLI 0.4.11 的直接 steps 部署可能返回 `Status: submitted` 和 `TemplateID`。 -受理不等于就绪:设置有限等待期限,轮询 `abx --json template info ID`,直到状态为 -`ready`;构建失败则停止并清理。该版本的 `status` 是包含 Python 字典的字符串,自动化时 -先解析外层 JSON,再用 `ast.literal_eval` 解析此字符串,不使用 `eval`。 -就绪后才调用 `abx create ID --detach` 或 `ProviderSandbox.create(template=ID)`。 -保留每个精确资源 ID,先 `abx kill SANDBOX_ID`,再 `abx template delete TEMPLATE_ID -y`, -并通过精确 ID 查询确认资源不存在。提交成功或列表为空本身不足以证明就绪或清理完成。 - -## 在 Python 里使用结果 - -```python -from agent_sandbox import ProviderSandbox as Sandbox - -async with await Sandbox.create(template="") as sandbox: - print(await sandbox.run("hello", name="Ada")) # 注册在模板 server 里的命令 - proc = await sandbox.commands.run("python3 --version") +Python SDK 负责运行时调用。源码获取、构建、部署和源码版本发布由 CLI 负责。以下为当前源码入口,安装版用法以 `abx --help` 为准。 + +```bash +abx template init ./my-template --template python +abx deploy ./my-template --name my-app --yes +abx status my-app +abx sandbox command run my-app hello --arg name=Ada +abx delete my-app --yes ``` -带 `agent-sandbox-server` 的模板见[编写模板](authoring-templates.md)和[模板服务器 HTTP API](template-server-api.md)。 - -## 有什么变化 - -早期的 SDK 自带一套构建流水线(`Sandbox.deploy`、`Image`、`DockerBuilder`、`TemplateManager`、`[alicloud]` extra 和 E7xxx 构建错误码)。它们已删除:一条流水线,一个工具。如果你的代码调用过它们,换成上面的 CLI 命令(脚本里可以运行 `abx template deploy --yes`)。 - -## 下一步 - -- [编写模板](authoring-templates.md) — 通过 template.yaml 定义模板 -- [SDK 使用指南](sdk-usage.md) — SDK 的运行时用法 +重复部署时,可先用 `abx template build ./my-template --out ./.abx/build.json --yes` 准备构建,再用 `abx deploy --build ./.abx/build.json --name my-app --yes` 复用。构建不创建运行实例。删除应用后,独立构建仍保留;用 `abx template build --clean ./.abx/build.json --yes` 清理不再被应用使用的构建资源。镜像等需手动清理的资源会明确列出。 -## 相关:abx CLI +使用已有平台模板时,Python 可直接 `await ProviderSandbox.create(template=template_id, provider=provider)`。CLI 创建的应用则用其返回的沙箱 ID 连接,避免再创建一个实例。`launch_template` 是高级运行时入口:调用者必须确保镜像入口或平台启动命令不会重复启动清单中的服务。 -命令行的模板生命周期记录在 [`cli` 仓库](https://github.com/alibaba-agent-sandbox/agent-sandbox/blob/main/docs/zh/guide/deploy-and-build.md)。 +[CLI 模板编写](https://github.com/Alibaba-Agent-Sandbox/agent-sandbox/blob/main/docs/zh/guide/authoring-templates.md) · [SDK 应用连接](template-workflows.md) diff --git a/docs/zh/guide/command-map.md b/docs/zh/guide/command-map.md index e6c03661..be293f81 100644 --- a/docs/zh/guide/command-map.md +++ b/docs/zh/guide/command-map.md @@ -1,121 +1,20 @@ # 命令与 SDK 对照 -> 历史原生接口对照:下表早于当前标准 CLI/客户端迁移,不定义当前命令面。请以[Python 安装](install-python.md)、[E2B 迁移](migrate-from-e2b.md)、[原生 SDK 使用](sdk-usage.md)和[当前 CLI README](https://github.com/Alibaba-Agent-Sandbox/agent-sandbox#readme)为准;下列其他产品版本也仅为历史参考。 - -同一行是同一件事在三家产品里的最近入口。它不是参数级兼容,也不是把 Agent Sandbox 收成其中一家的子集。 - -`abx` 是 Agent Sandbox 的命令行。Python SDK 是 `agent-sandbox-client`。默认提供方是阿里云极速版。装上 `opensandbox` 提供方之后,`abx` 的命令名不变,请求改发到 OpenSandbox 服务。那和直接运行 `osb` 不是同一条命令。 - -对照用的公开表面: - -| 产品 | 命令行 | SDK | -|---|---|---| -| Agent Sandbox | `abx`(`@agent-sandbox/cli`) | `agent-sandbox-client`,`from agent_sandbox import ProviderSandbox` | -| E2B | `e2b`(`@e2b/cli`,文档版本 2.16.1) | `e2b`,`from e2b import Sandbox` | -| OpenSandbox | `osb`(`opensandbox-cli`,[CLI 文档](https://open-sandbox.ai/cli/)) | `opensandbox`,`from opensandbox import Sandbox`([Python SDK](https://open-sandbox.ai/sdks/python)) | - -标准同步与异步客户端由 `agent_sandbox` 导出。差异和迁移步骤在[从 E2B 迁移](migrate-from-e2b.md)。OpenSandbox 的 HTTP 字段对照在设计稿 [provider-opensandbox-mapping](../../en/design/provider-opensandbox-mapping.md)。 - -单元格里的「—」表示这一侧没有对应的公开命令或方法。 - -## 安装与凭证 - -| 做什么 | abx | Agent Sandbox SDK | e2b | E2B SDK | osb | OpenSandbox SDK | -|---|---|---|---|---|---|---| -| 安装 | [CLI binary installation](https://github.com/Alibaba-Agent-Sandbox/agent-sandbox/blob/main/docs/en/guide/binary-installation.md) | [Paired source install](install-python.md) | `npm install -g @e2b/cli` | `pip install e2b` | `pip install opensandbox-cli` | `pip install opensandbox` | -| 写入凭证 | `abx config set sandbox_api_key` | 环境变量 `ABX_SANDBOX_API_KEY` | `e2b auth login` | 环境变量 `E2B_API_KEY` | `osb config set connection.api_key` | `ConnectionConfig(api_key=…)` 或 `OPEN_SANDBOX_API_KEY` | -| 查看生效配置 | `abx config list` | — | `e2b auth info` | — | `osb config show` | — | - -阿里云 AccessKey 保持官方名字 `ALICLOUD_ACCESS_KEY_ID` 和 `ALICLOUD_ACCESS_KEY_SECRET`。OpenSandbox 的连接地址是 `osb config set connection.domain`,对应环境变量 `OPEN_SANDBOX_DOMAIN`。 - -## 沙箱生命周期 - -| 做什么 | abx | Agent Sandbox SDK | e2b | E2B SDK | osb | OpenSandbox SDK | -|---|---|---|---|---|---|---| -| 创建 | `abx create <名称>` | `Sandbox.create(template=…)` | `e2b sandbox create [template]` | `Sandbox.create(template)` | `osb sandbox create --image <镜像>` | `Sandbox.create("<镜像>")` | -| 接上已有沙箱 | `abx connect ` | `Sandbox.connect(id)` | `e2b sandbox connect ` | `Sandbox.connect(id)` | — | `Sandbox.connect(sandbox_id)` | -| 列出 | `abx list` | `Sandbox.list()` | `e2b sandbox list` | `Sandbox.list()` | `osb sandbox list` | 管理器列出沙箱 | -| 查看一个 | `abx info ` | `sandbox.refresh_info()` | `e2b sandbox info ` | 创建或连接后读对象 | `osb sandbox get ` | 连接后读对象 | -| 销毁 | `abx kill ` | `sandbox.kill()`,按 id 用 `Sandbox.kill_by_id(id)` | `e2b sandbox kill ` | `sandbox.kill()`,按 id 用 `Sandbox.kill(id)` | `osb sandbox kill ` | `sandbox.kill()`;`destroy()` 会先终止再关掉本地资源 | -| 延长寿命 | `abx create --timeout <秒>`(创建时设定) | `sandbox.set_timeout(秒)` | `e2b sandbox create --timeout` | `sandbox.set_timeout(秒)` | `osb sandbox renew --timeout 30m` | `sandbox.renew(timedelta)` | -| 暂停 | — | `sandbox.pause()` | `e2b sandbox pause ` | `sandbox.pause()` | `osb sandbox pause ` | `sandbox.pause()` | -| 恢复 | — | `sandbox.resume()` | `e2b sandbox resume ` | `Sandbox.connect` 接上暂停的沙箱 | `osb sandbox resume ` | `Sandbox.resume(sandbox_id=…)` | -| 是否在跑 | — | `sandbox.is_running()` | `e2b sandbox list --state running` | `sandbox.is_running()` | `osb sandbox health ` | `is_healthy()` | -| 端口地址 | — | `sandbox.network.get_host(port)`、`get_url(port)` | — | `sandbox.get_host(port)` | `osb sandbox endpoint --port 8080` | `sandbox.get_endpoint(port)` | - -创建时三家选的不是同一种东西。`abx create --template` 和 `Sandbox.create(template=…)` 用模板名。`e2b sandbox create` 用 E2B 模板(构建结果是快照)。`osb sandbox create --image` 和 `Sandbox.create("python:3.12")` 用镜像地址;OpenSandbox 也可以 `--template` 或 `--snapshot-id`,这三项互斥。 - -`abx connect` 是一行一个命令的 REPL,不是 PTY。E2B 的 `e2b sandbox connect` 把终端接到沙箱上。OpenSandbox 的持久 shell 在 `osb command session`。 - -暂停的含义随运行时变化。OpenSandbox 在 Docker 上是 cgroup 冻结,恢复用 `Sandbox.resume`。E2B 的暂停可以在超时后续跑。Agent Sandbox 把 `pause` / `resume` 交给当前提供方。 - -## 在沙箱里执行 - -| 做什么 | abx | Agent Sandbox SDK | e2b | E2B SDK | osb | OpenSandbox SDK | -|---|---|---|---|---|---|---| -| 跑一条命令 | `abx exec <命令>` | `sandbox.commands.run(cmd)` | `e2b sandbox exec <命令>` | `sandbox.commands.run(cmd)` | `osb command run -- <命令>`,短写是 `osb exec` | `sandbox.commands.run(cmd)` | -| 后台命令 | `abx exec --background -- ` | `sandbox.commands.start(…, background=True)` | `e2b sandbox exec -b` | `commands.run` 的后台参数 | `osb command run --background` | `commands.run` 后用 `get_command_status` | -| 看后台输出 | — | `commands.stream` | — | 命令流 | `osb command logs ` | `commands.get_background_command_logs` | -| 中断 | `abx sandbox process signal` | `commands.send_signal(pid, 15 或 9)` | — | 结束对应进程 | `osb command interrupt ` | `commands.interrupt(execution.id)` | -| 列进程 | `abx sandbox process list` | `commands.list()` | — | — | — | — | -| 持久 shell | `abx connect` 的每一行都是新进程 | — | `e2b sandbox connect` | PTY | `osb command session create`,然后 `session run` | `commands.create_session`,`run_in_session`,`delete_session` | -| 交互终端 | `abx connect ` 是按行 REPL。CLI 没有 PTY 命令 | `sandbox.get_terminal()` | `e2b sandbox connect` | `sandbox.pty` | — | execd 的 PTY WebSocket | -| 跑一段代码 | — | `sandbox.run_code(code)` | — | `sandbox.run_code(code)` | — | 代码解释器镜像上的 `/code`,SDK 包是 `opensandbox-code-interpreter` | -| 注册命令 | `abx run <命令名> --arg k=v` | `sandbox.run(name, **kwargs)` | — | — | — | — | -| 列出注册命令 | `abx command list ` | `sandbox.list_commands()` | — | — | — | — | - -Agent Sandbox 只发送信号 15(SIGTERM)和 9(SIGKILL)。OpenSandbox 的 `interrupt` 停的是一次 execd 执行,不是任意数字 pid。 - -## 文件 - -| 做什么 | abx | Agent Sandbox SDK | e2b | E2B SDK | osb | OpenSandbox SDK | -|---|---|---|---|---|---|---| -| 读 | `abx download` 落到本机 | `files.read`、`read_bytes` | — | `files.read` | `osb file cat`、`osb file download` | `files.read_file` | -| 写 | `abx upload` | `files.write` | — | `files.write` | `osb file write`、`osb file upload` | `files.write_files` | -| 列目录 | `abx sandbox files list` | `files.list` | — | `files.list` | — | `files.list_directory` | -| 元数据 | `abx sandbox files stat` | `files.get_info` | — | `files.get_info` 一类的信息对象 | `osb file info` | 列出或搜索结果里的条目 | -| 建目录 | `abx sandbox files mkdir` | `files.make_dir` | — | `files.make_dir` | — | 上传时父目录会建出来 | -| 删除 | `abx sandbox files rm` | `files.remove` | — | `files.remove` | — | `files.delete_files` | -| 移动 | `abx sandbox files mv` | `files.move`、`rename` | — | `files.rename` | — | — | -| 搜索 | `abx sandbox files search` | — | — | — | `osb file search` | `files.search` | -| 是否存在 | — | `files.exists` | — | `files.exists` | — | 读或 stat 失败即不存在 | -| 监听变化 | — | `files.watch` | — | `files.watch_dir` | — | — | -| 签名文件 URL | — | `upload_url` / `download_url` 抛出 `FileOperationError`。`files.upload_url` 仍要 `X-Access-Token` | — | `upload_url`、`download_url` | — | `get_endpoint` 解析的是端口,不是单个文件的签名 URL | - -E2B 的文件操作在 SDK 里。命令行用 `e2b sandbox exec` 间接碰文件。OpenSandbox 的 `file download` 在整次下载成功之后才替换本机目标文件。 - -## 模板 - -| 做什么 | abx | Agent Sandbox SDK | e2b | E2B SDK | osb | OpenSandbox SDK | -|---|---|---|---|---|---|---| -| 生成本地工程 | `abx template scaffold` | — | `e2b template init` | `Template()` 构建器 | — | — | -| 构建并发布 | `abx template deploy`,分步是 `build` 和 `push` | —(由 CLI 构建和部署) | `e2b template create <名称>`(`template build` 已弃用) | `Template.build` | `osb template create --image … --publish …` | SDK 的模板管理 | -| 列出 | `abx template list` | — | `e2b template list` | 模板 API | `osb template list` | 同上 | -| 查看 | `abx template info` | — | — | — | `osb template get ` | 同上 | -| 删除 | `abx template delete` | — | `e2b template delete` | — | `osb template delete` | 同上 | -| 搜索目录 | `abx template search`,安装是 `abx template install` | — | — | — | — | — | -| 快照 | — | 提供方能力 | 模板构建本身就是快照 | `Template.build` | `osb snapshot create ` | 快照 API | -| 公开或撤回 | — | — | `e2b template publish`、`unpublish` | — | — | — | - -三家的「模板」不是同一个对象。Agent Sandbox 的模板是 `template.yaml` 加镜像,默认提供方把它交给阿里云极速版。E2B 的模板构建结果是可直接启动的快照。OpenSandbox 的 `osb template` 是 Kubernetes 运行时上的黄金镜像;Docker 运行时不提供这组接口。OpenSandbox 从快照启动用 `osb sandbox create --snapshot-id`。 - -## 只有一侧提供的能力 - -| 能力 | 在哪里 | 另外两家 | -|---|---|---| -| 可切换的沙箱提供方 | `abx provider`,SDK 的 provider 插件 | E2B 和 OpenSandbox 各管自己的控制面 | -| Modal 风格的函数 | `@sandbox`,见[声明式用法](declarative-usage.md) | 没有同名装饰器 | -| 注册命令 | `abx run`、`sandbox.run()` | 没有这一层 | -| Agents API | 沙箱内 HTTP,目前支持 Codex 和 Qwen Code,见 [Agents API](agents-api.md) | 没有这组 `/v1/agents/sessions` | -| MCP | `abx mcp install/start/status/deploy` | E2B 无同名命令组。OpenSandbox 有 `osb skills`,那是它自己的技能包 | -| 出口策略 | 提供方策略 | E2B 用自己的网络限制。OpenSandbox 是 `osb egress get/patch` | -| 凭证保管 | 创建时注入环境变量 | OpenSandbox 另有 `osb credential-vault`,要在创建时打开 `--credential-proxy` | -| 诊断日志 | `abx sandbox system metrics` | `e2b sandbox logs`、`e2b sandbox metrics`。OpenSandbox 是 `osb diagnostics logs/events`,旧入口是 `osb devops` | -| 按端点开关路由 | 沙箱内 `agent-sandbox-server` 的策略,见[细粒度能力](fine-grained-capabilities.md) | 那是沙箱里的 HTTP 服务器,不是 E2B 或 OpenSandbox 的控制面 | - -## 相关文档 - -- [从 E2B 迁移](migrate-from-e2b.md) -- [与其他沙箱工具的对比](comparison.md) -- [CLI 参考](https://github.com/alibaba-agent-sandbox/agent-sandbox/blob/main/docs/zh/reference/cli-reference.md) -- [提供方](providers.md) +本文对应当前源码的 CLI 入口。Python 标准 `Sandbox` / `AsyncSandbox`、跨提供方 `ProviderSandbox` 和应用 HTTP 客户端是不同入口,方法不能混用。已安装发行版的命令以其 `--help` 为准。 + +| 任务 | CLI | ProviderSandbox / Python | +| --- | --- | --- | +| 获取和编写模板 | `abx template list / info / init` | `—` | +| 构建可复用产物 | `abx template build SOURCE --yes` | `—` | +| 发布源码版本 | `abx template publish SOURCE --tag VERSION --yes` | `—` | +| 部署源码应用 | `abx deploy SOURCE --name APP --yes` | `—` | +| 从原生模板创建 | `abx deploy --template-id TEMPLATE --name APP --yes` | `await ProviderSandbox.create(template=..., provider=...)` | +| 查询和删除 | `abx list; abx status APP; abx delete APP --yes` | `await sandbox.refresh_info(); await sandbox.kill()` | +| 执行命令 | `abx sandbox exec APP -- COMMAND` | `await sandbox.commands.run(command)` | +| 调用注册函数 | `abx sandbox command run APP FUNCTION --arg key=value` | `await sandbox.run(name, **kwargs)` | +| 列出注册函数 | `abx sandbox command list APP` | `await sandbox.list_commands()` | +| 原生 Provider 操作 | `abx provider command list PROVIDER; abx provider run OPERATION` | `agent_sandbox.providers.commands` | + +CLI 的本机部署名称不等于云端沙箱 ID。先用 `abx status APP` 获取真实 ID,再通过 `ProviderSandbox.connect(id, provider=...)` 连接相同提供方。`close()` 只关闭客户端;创建者负责删除远端资源。托管应用通过自身 HTTP API 使用,不能当作单个 Sandbox 调用。 + +[标准客户端迁移](migrate-from-e2b.md) · [应用连接与认证](template-workflows.md) · [模板编写](authoring-templates.md) diff --git a/docs/zh/guide/comparison.md b/docs/zh/guide/comparison.md index fb249e66..a3656a77 100644 --- a/docs/zh/guide/comparison.md +++ b/docs/zh/guide/comparison.md @@ -48,8 +48,8 @@ async with await ProviderSandbox.create(template="base") as sandbox: ## 模板应用 -使用 `abx template launch ` 准备并启动完整模板。高级 `template build` / -`template deploy` 可单独准备镜像。启动后通过 +使用 `abx deploy --name my-app` 准备并启动完整模板。高级 `template build` / +`template build` 可单独准备镜像。启动后通过 `await sandbox.application_client(9000, token=application_token)` 绑定应用客户端, 再调用 `client.commands.run("hello", name="Python")`。前提是运行中的模板注册了 hello, 普通 `base` 不包含该服务。见 [Express 快速开始](express-quickstart.md)和 diff --git a/docs/zh/guide/conditional-acceptance.md b/docs/zh/guide/conditional-acceptance.md index 3b67028f..a3fc4f27 100644 --- a/docs/zh/guide/conditional-acceptance.md +++ b/docs/zh/guide/conditional-acceptance.md @@ -15,8 +15,8 @@ E2B_API_URL、E2B_DOMAIN。不要把极速版密钥发给其他提供商。 | 暂停 | `sandbox.pause()` | `abx sandbox pause "$SANDBOX_ID"` | | 恢复同一个沙箱 | `Sandbox.connect(sandbox_id)` | `abx sandbox resume "$SANDBOX_ID"` | | 创建快照 | `sandbox.create_snapshot(name="checkpoint", request_timeout=300)` | `abx sandbox snapshot create "$SANDBOX_ID" --name checkpoint` | -| 快照列表 | `Sandbox.list_snapshots(sandbox_id=sandbox_id)`,通过分页器读取 | `abx sandbox snapshot list "$SANDBOX_ID" --format json` | -| 从快照新建沙箱 | `Sandbox.create(template=snapshot.snapshot_id)` | `abx create "$SNAPSHOT_ID" --detach` | +| 快照列表 | `Sandbox.list_snapshots(sandbox_id=sandbox_id)`,通过分页器读取 | `abx --json sandbox snapshot list "$SANDBOX_ID"` | +| 从快照新建沙箱 | `Sandbox.create(template=snapshot.snapshot_id)` | `abx deploy --template-id "$SNAPSHOT_ID" --name restored --yes` | | 删除快照 | `Sandbox.delete_snapshot(snapshot.snapshot_id)` | `abx sandbox snapshot delete "$SNAPSHOT_ID"` | 上表 Python 使用 `from agent_sandbox import Sandbox`;异步使用 `AsyncSandbox` 并 await 相应操作。它们是标准 SDK 入口,不能与 `agent_sandbox.api.sandbox.Sandbox` 的原生 Provider/Server 扩展入口混为一谈,也不能把原生提供方的特性门禁当成极速版标准 API 的服务能力结论。Python 命名为 `create_snapshot`,TypeScript 命名为 `createSnapshot`;参数形式以实际 SDK 版本为准。 @@ -54,21 +54,27 @@ finally: ## 创建独立模板 选择与运行时密钥相同的地域和 Team,TEAM_ID 从本地配置获取。 -名称必须唯一且不超过36字符。在具有标准入口的 CLI 检出中执行现有项目扩展: +名称必须唯一且不超过36字符。使用 Provider 管理操作: ```bash REGION=cn-hangzhou -NAME="conditional-$(date +%s)" -abx template from-image \ - "fc-e2b-registry.${REGION}.cr.aliyuncs.com/runtime-v2/base:v0.0.53" \ - --name "$NAME" --team-id "$TEAM_ID" --region "$REGION" \ - --generation 2 --cpu 2 --memory 2048 --disk-size 15360 --no-envd-inject -abx template info "$TEMPLATE_ID" +cat > template-create.json <<'JSON' +{ + "name": "conditional-UNIQUE", + "team_id": "YOUR_TEAM_ID", + "image": "fc-e2b-registry.cn-hangzhou.cr.aliyuncs.com/runtime-v2/base:v0.0.53", + "generation": 2, + "cpu": 2, + "memory_size": 2048, + "disk_size": 15360, + "envd_inject": false +} +JSON +abx --provider alicloud-express --region "$REGION" provider run template.create --input-file template-create.json --yes +abx --provider alicloud-express --region "$REGION" provider run template.get --input-file template-get.json ``` -创建结果提供 TEMPLATE_ID。构建是异步操作,轮询 info 直到 ready。 -已有原生 CLI 构建以 `template create` 注册镜像模板,须先检查安装版本的帮助。 -不传 --force,不修改已有模板。 +执行前替换唯一名称、Team 和镜像地域。将返回的 `template_id` 与相同的 `team_id` 写入 `template-get.json`,轮询直到构建就绪。提交是异步操作,注册时拒绝已有名称。这些命令对应当前源码 CLI;使用较早发行版时先查看已安装的帮助。 ## 执行与校验 @@ -87,8 +93,10 @@ SDK 测试校验仅保存在 Python 进程内存中的随机标记、文件、 ## 清理与证据 +删除模板前,将本轮模板的 `template_id` 写入 `template-delete.json`(JSON 对象,例如 `{ "template_id": "本轮模板ID" }`),再执行下面的 Provider 操作。 + 先删除恢复和源沙箱,再删除被占用的快照。只清理本轮创建的资源,独立确认不存在后, -用 `abx template delete "$TEMPLATE_ID" --yes` 删除本轮模板。 +用 `abx --provider alicloud-express provider run template.delete --input-file template-delete.json --yes` 删除本轮模板。 记录源代码/二进制 SHA、真实通过/失败/跳过和遗留资源数,不记录密钥、账号标识和私人地址。 不向 Actions 上传凭据,不新增或分发云端/模型验收工作流。 diff --git a/docs/zh/guide/environment-variables.md b/docs/zh/guide/environment-variables.md index 0403d153..3208a1c3 100644 --- a/docs/zh/guide/environment-variables.md +++ b/docs/zh/guide/environment-variables.md @@ -45,7 +45,7 @@ async def main(): asyncio.run(main()) ``` -默认 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)。 +CLI 的 `abx deploy SOURCE --name APP --env KEY=value` 将变量传给创建的运行实例。模板应声明需要的运行环境,敏感值不写入源码。Python 可直接使用 `ProviderSandbox.create(envs=...)`。 连接不属于自己的实例时,用 `await sandbox.close()` 释放本地连接,不要删除该实例。 diff --git a/docs/zh/guide/express-quickstart.md b/docs/zh/guide/express-quickstart.md index 80666d90..509de602 100644 --- a/docs/zh/guide/express-quickstart.md +++ b/docs/zh/guide/express-quickstart.md @@ -9,7 +9,7 @@ ```bash export ABX_SERVER_TOKEN='replace_me' -abx --provider alicloud-express template launch 'alibaba-agent-sandbox/awesome-templates//python-hello' +abx --provider alicloud-express deploy 'alibaba-agent-sandbox/awesome-templates//python-hello' --name my-app ``` 启动器准备提供方引用、创建沙箱、执行模板启动命令并检查声明的服务,不需要选择 diff --git a/docs/zh/guide/install-python.md b/docs/zh/guide/install-python.md index 8c10905b..5b7febc7 100644 --- a/docs/zh/guide/install-python.md +++ b/docs/zh/guide/install-python.md @@ -21,7 +21,7 @@ python -m pip check python -c "from importlib.metadata import version; print(version('agent-sandbox-client'), version('agent-sandbox-server'))" ``` -[abx CLI](https://github.com/Alibaba-Agent-Sandbox/agent-sandbox) 需要独立安装。本指南中的模板启动使用 CLI 0.5.0 或更高版本;创建沙箱前先配置所选平台。 +[abx CLI](https://github.com/Alibaba-Agent-Sandbox/agent-sandbox) 需要独立安装。本指南中的 CLI 命令对应当前 CLI 源码;较早发行版请以已安装的帮助为准。创建沙箱前先配置所选平台。 本项目配置统一使用 `ABX_SANDBOX_API_KEY`、`ABX_SANDBOX_BASE_URL` 和 `ABX_SANDBOX_DOMAIN`,标准 Sandbox / AsyncSandbox 与 ProviderSandbox 均支持。 @@ -43,6 +43,27 @@ python -m pip install --index-url https://pypi.org/simple 'agent-sandbox-server= 服务端负责在镜像内运行应用,安装它不会创建沙箱。注册命令与启动服务见[服务端指南](server-sdk.md)。 +## 当前源码的客户端与运行时边界 + +当前检出已将本机服务端运行时改为可选依赖。标准 `Sandbox` / `AsyncSandbox`、 +`ProviderSandbox`、`ServerClient`、模板 YAML 与远程执行装饰器 `@sandbox(...)` +只需客户端: + +```bash +python -m pip install ./client-sdk +``` + +本机使用 `@sandbox.register` 或 `sandbox.server` 时,安装匹配的运行时: + +```bash +python -m pip install ./server-sdk './client-sdk[server]' +``` + +`[all]` 和 `[dev]` 包含 `[server]`。上文固定的公开发行版早于这项依赖调整; +源码安装不代表已有新发行版。注册代码应放在应用服务程序中,不会上传本机函数。 +模块级 `sandbox.run` / `sandbox.run_async` 已删除,请通过明确选定的应用的 +`ServerClient.commands.run` 调用命名命令。`ProviderSandbox.run` 与远程执行装饰器仍保留。 + ## 从检出源码进行开发 在仓库根目录同时安装两个本地发行包: diff --git a/docs/zh/guide/provider-commands.md b/docs/zh/guide/provider-commands.md index d04ee994..659f4bfc 100644 --- a/docs/zh/guide/provider-commands.md +++ b/docs/zh/guide/provider-commands.md @@ -165,17 +165,17 @@ HTTP403/409 不能归为 Provider 未声明能力。原生 ProviderSandbox 特 ```sh abx --json provider command list alicloud-express abx --json provider command info alicloud-express snapshot.create -abx --json provider command run alicloud-express sandbox.pause \ +abx --json --provider alicloud-express provider run sandbox.pause \ --input '{"sandbox_id":""}' --yes -abx --json provider command run alicloud-express sandbox.resume \ +abx --json --provider alicloud-express provider run sandbox.resume \ --input '{"sandbox_id":"","timeout":600}' --yes -abx --json provider command run alicloud-express snapshot.create \ +abx --json --provider alicloud-express provider run snapshot.create \ --input '{"sandbox_id":"","name":"checkpoint","request_timeout":300}' --yes -abx --json provider command run alicloud-express snapshot.list \ +abx --json --provider alicloud-express provider run snapshot.list \ --input '{"sandbox_id":"","limit":100}' -abx --json provider command run alicloud-express snapshot.restore \ +abx --json --provider alicloud-express provider run snapshot.restore \ --input '{"snapshot_id":"","timeout":300}' --yes -abx --json provider command run alicloud-express snapshot.delete \ +abx --json --provider alicloud-express provider run snapshot.delete \ --input '{"snapshot_id":""}' --yes ``` diff --git a/docs/zh/guide/providers.md b/docs/zh/guide/providers.md index 82e379f4..600e5cb5 100644 --- a/docs/zh/guide/providers.md +++ b/docs/zh/guide/providers.md @@ -121,11 +121,11 @@ Node.js SDK 当前是沙箱内 HTTP ServerClient,不提供 provider 生命周 | 位置 | 示例 | |---|---| | SDK 参数 | `await ProviderSandbox.create(provider="my-cloud")` | -| CLI 选项 | `abx create --provider my-cloud` | +| CLI 选项 | `abx --provider my-cloud deploy --template-id base --name my-sandbox` | | 进程环境变量 | `export ABX_PROVIDER=my-cloud` | | `./.env` | `ABX_PROVIDER=my-cloud` | | `~/.abx/.env` | `ABX_PROVIDER=my-cloud` | -| `~/.abx/config.toml` | `[core]` `provider = "my-cloud"`(由 `abx provider default my-cloud` 写入) | +| `~/.abx/config.toml` | `[core]` `provider = "my-cloud"`(由 `abx config set provider my-cloud` 写入) | | 内置 | `alicloud-express` | ```python @@ -151,9 +151,9 @@ asyncio.run(main()) ```bash abx provider list -abx provider default my-cloud # 持久化到 ~/.abx/config.toml -abx provider default # 显示当前默认提供方 -abx provider default alicloud-express +abx config set provider my-cloud # 持久化到 ~/.abx/config.toml +abx config set provider # 显示当前默认提供方 +abx config set provider alicloud-express ``` --- @@ -258,7 +258,7 @@ python -m agent_sandbox.providers test my-cloud # 契约测试套件(需 | 读写文件 | `files.read/write` | `upload`、`download` | | 调用模板应用 | `await application_client(port, token=...)` 后调用 `client.commands.run` | 模板的命名命令入口,见[服务 API](template-server-api.md) | | 获取应用端点 | `await network.resolve(port)` 返回 URL 和网关头 | 无独立 URL 命令 | -| 准备源码模板 | 使用 CLI 准备,再连接返回的沙箱或使用已准备好的引用 | `template launch`,见[工作流指南](template-workflows.md) | +| 准备源码模板 | 使用 CLI 准备,再连接返回的沙箱或使用已准备好的引用 | `abx deploy SOURCE --name APP`,见[工作流指南](template-workflows.md) | 进程执行、命名命令、解释器和 PTY 是不同接口;一种接口通过不代表其他接口已实现。网关头与应用令牌用途不同,调用应用时保留网关头并显式提供应用令牌。完整方法见 [SDK 参考](../reference/api-reference.md)。 diff --git a/docs/zh/guide/sdk-skill.md b/docs/zh/guide/sdk-skill.md index ea743862..353f546b 100644 --- a/docs/zh/guide/sdk-skill.md +++ b/docs/zh/guide/sdk-skill.md @@ -8,7 +8,7 @@ - 客户端:`Sandbox.create`、`commands.run`、`run`、`files`,以及内置提供方 `alicloud-express` - 模板:`template.yaml`、`Dockerfile`、用 `@command` 和 `start()` 写的 `commands.py` -- 部署:`abx template deploy`(Python 包不构建镜像) +- 部署:`abx deploy`(Python 包不构建镜像) - 测试:`make test`,以及在本地启动 `commands.py` 做健康检查 - 发布:两个包同一个版本号,由 GitHub Release 发布 diff --git a/docs/zh/guide/sdk-usage.md b/docs/zh/guide/sdk-usage.md index fd7b4c2a..e88310f3 100644 --- a/docs/zh/guide/sdk-usage.md +++ b/docs/zh/guide/sdk-usage.md @@ -25,7 +25,7 @@ from agent_sandbox import ProviderSandbox as Sandbox ## 模板应用 -通过 `abx template launch ` 启动完整模板,再使用 `application_client` +通过 `abx deploy --name my-app` 启动完整模板,再使用 `application_client` 携带应用令牌调用注册命令。源码镜像准备和平台配置由 CLI 负责。见 [Express 快速开始](express-quickstart.md)和[模板启动](template-workflows.md)。 diff --git a/docs/zh/guide/template-server-api.md b/docs/zh/guide/template-server-api.md index 5eae1c16..30e09dc4 100644 --- a/docs/zh/guide/template-server-api.md +++ b/docs/zh/guide/template-server-api.md @@ -35,7 +35,7 @@ async def main() -> None: asyncio.run(main()) ``` -`close()` 释放本次客户端连接,不销毁这个现有沙箱;创建者仍负责调用 `kill()` 或 `abx kill ID`。 +`close()` 释放本次客户端连接,不销毁这个现有沙箱;创建者仍负责调用 `kill()` 或 `abx delete ID`。 此调用解析当前沙箱和端口的应用端点,保留完整代理前缀及签名 query。平台网关请求头和应用 `ABX_SERVER_TOKEN` 分离,不复制控制面 API key,不做匿名认证重试。重定向(包括同域另一沙箱)、URL 用户信息和寻址覆盖被拒绝;逐操作签名入口必须由 Provider 解析操作请求,不能按 URL 前缀拼接。 diff --git a/docs/zh/guide/template-workflows.md b/docs/zh/guide/template-workflows.md index 2c2d00b6..30a29b69 100644 --- a/docs/zh/guide/template-workflows.md +++ b/docs/zh/guide/template-workflows.md @@ -1,6 +1,6 @@ # 启动和使用模板 -源码模板使用 `abx --provider PROVIDER template launch SOURCE --yes` 准备并启动,再用 Python 通过返回的沙箱 ID 和同一个 Provider 连接。平台已经提供模板 ID 时,可以直接用 Python 创建。 +源码模板使用 `abx --provider PROVIDER deploy SOURCE --name my-app --yes` 准备并启动,再用 Python 通过返回的沙箱 ID 和同一个 Provider 连接。平台已经提供模板 ID 时,可以直接用 Python 创建。 ## Python 应用调用 @@ -21,7 +21,7 @@ finally: 如果 `X-Access-Token` 请求头未被占用,客户端还会通过服务端已有的这个请求头发送同一个显式应用令牌,使认证能通过 OpenSandbox 1.1.1 等会过滤 `Authorization` 的代理。已有网关请求头(包括注入 HTTP 客户端的默认请求头)不会被覆盖;需要 Bearer 的服务仍可使用原认证方式。 -高级准备场景中,`launch_template` 根据已准备好的 Provider 引用创建新沙箱,并执行清单的 `start_cmd`。该引用的原生启动命令或镜像入口不能同时启动同一个服务。普通 `abx template deploy` 可能保留原生启动行为,返回的引用不自动代表应用尚未启动。如果 CLI 已启动应用,按上文连接它返回的沙箱即可。 +高级准备场景中,`launch_template` 根据已准备好的 Provider 引用创建新沙箱,并执行清单的 `start_cmd`。该引用的原生启动命令或镜像入口不能同时启动同一个服务。构建产物保留其启动声明,不能把准备好的引用理解为可以重复启动同一服务。如果 CLI 已启动应用,按上文连接它返回的沙箱即可。 确认准备阶段没有重复启动后,让 Python 负责启动和清理: diff --git a/docs/zh/guide/troubleshooting.md b/docs/zh/guide/troubleshooting.md index 1b490c43..7b252513 100644 --- a/docs/zh/guide/troubleshooting.md +++ b/docs/zh/guide/troubleshooting.md @@ -93,11 +93,11 @@ export ALICLOUD_ACCESS_KEY_SECRET="your-ak-secret" 1. 检查模板名称拼写 2. 确认模板已安装: ```bash - abx template install owner/repo + abx template init ./project --from owner/repo ``` 3. 使用默认模板测试: ```bash - abx create base + abx deploy --template-id base --name my-sandbox ``` ### E2009 — SandboxNotFoundError @@ -150,10 +150,10 @@ export ALICLOUD_ACCESS_KEY_SECRET="your-ak-secret" abx list --status running # 销毁不需要的沙箱 -abx kill sbx-xxxx +abx delete sbx-xxxx # 批量销毁 -abx kill --all --yes +abx delete --yes ``` --- @@ -174,14 +174,14 @@ abx kill --all --yes result = await sandbox.commands.run("long-command", timeout=300) # CLI 中增加超时 -abx exec sbx-xxxx "long-command" --timeout 300 +abx sandbox exec sbx-xxxx "long-command" --timeout 300 ``` --- ## 构建与部署相关 -构建、推送、部署的错误(`E7xxx` 错误码,以及 `abx create` / `abx template scaffold` 缺少 LLM Key)由 `abx` CLI 抛出,不属于这个 SDK。见 CLI 的排障指南和[构建与部署](build-and-deploy.md)。 +构建、推送、部署的错误(`E7xxx` 错误码,以及 `abx template init --prompt` / `abx template init` 缺少 LLM Key)由 `abx` CLI 抛出,不属于这个 SDK。见 CLI 的排障指南和[构建与部署](build-and-deploy.md)。 --- @@ -239,4 +239,4 @@ python -m pip show agent-sandbox-client ## 相关:abx CLI -CLI 特有的问题(找不到 `abx`、`abx deploy` 没有 Dockerfile、部署步骤长时间无输出、无效的快捷方式目标、`abx template scaffold --adopt` 确认提示)见 CLI 仓库:[故障排查(CLI)](https://github.com/alibaba-agent-sandbox/agent-sandbox/blob/main/docs/zh/guide/troubleshooting.md)。 +CLI 特有的问题(找不到 `abx`、`abx deploy` 没有 Dockerfile、部署步骤长时间无输出、无效的快捷方式目标、`abx template init --adopt` 确认提示)见 CLI 仓库:[故障排查(CLI)](https://github.com/alibaba-agent-sandbox/agent-sandbox/blob/main/docs/zh/guide/troubleshooting.md)。 diff --git a/docs/zh/reference/configuration.md b/docs/zh/reference/configuration.md index e51c3043..c8fc7e44 100644 --- a/docs/zh/reference/configuration.md +++ b/docs/zh/reference/configuration.md @@ -47,8 +47,8 @@ 下面这些调用读的是同一条链: - `Sandbox.create` 以及其他 SDK 客户端(`load_config`) -- 自然语言 `abx create`(编码代理) -- `abx template install` / `abx template search`(GitHub token) +- 自然语言 `abx template init --prompt`(编码代理) +- `abx template init --from SOURCE` / `abx template list`(GitHub token) - `abx mcp install`(写入编辑器配置的沙箱 API Key) `abx mcp install` 把 `ABX_SANDBOX_API_KEY` 写入 Cursor 和 Claude 的配置。`ABX_SANDBOX_BASE_URL` 和 `ALIBABA_CLOUD_REGION_ID` 只有在进程环境变量里设置了才会写进同一份文件。`abx mcp start` 会再次读取 `~/.abx`,所以用 `abx config set region` 保存的区域在编辑器启动 server 时仍然生效。 @@ -134,7 +134,7 @@ ```toml [core] -provider = "alicloud-express" # 默认提供方(abx provider default NAME) +provider = "alicloud-express" # 默认提供方(abx config set provider NAME) [transport] sandbox_base_url = "https://api.cn-hangzhou.e2b.fc.aliyuncs.com" diff --git a/docs/zh/reference/error-codes.md b/docs/zh/reference/error-codes.md index 6a307e4a..042e89ff 100644 --- a/docs/zh/reference/error-codes.md +++ b/docs/zh/reference/error-codes.md @@ -65,8 +65,8 @@ except SandboxError as e: | E3002 | `ProcessError` | 进程非零退出码 | — | 命令返回非零退出码(携带 `exit_code`、`stdout`、`stderr` 属性) | | E3003 | `CodeExecutionError` | 代码执行失败 | 检查代码语法和运行时依赖 | Code Interpreter 执行代码失败 | | E3004 | `CapabilityNotSupportedError` | 所选调用或端点不可用 | 检查具体操作及所选入口,不能添加旧模板能力标记绕过 | 服务端端点禁用等;Provider 精确操作不支持使用 E8004 | -| E3005 | `CommandNotFoundError` | 命名命令未找到 | 用 `sandbox.list_commands()` 检查命令名,并确认沙箱里的 SandboxServer 在运行(`sandbox.start_server()`) | `Sandbox.run(name)` / `abx run <名称>` 指定的命令在沙箱内 SandboxServer 中不存在(HTTP 404)。携带 `command_name` 属性 | -| E3006 | `EnvdRpcError` | envd 拒绝了 RPC 调用(HTTP >= 400) | 检查命令与参数,或用 `abx -v` 查看原始错误响应 | 沙箱 envd 返回 HTTP >= 400(例如未知可执行文件触发 `process.Process/Start` 500)。携带 `status_code`、`rpc_path`、`envd_error`(解析后的 JSON 体)与 `body_text`;消息不包含完整沙箱 URL。`abx connect` 会将其映射为单条友好提示(如 `Command not found: `) | +| E3005 | `CommandNotFoundError` | 命名命令未找到 | 用 `sandbox.list_commands()` 检查命令名,并确认沙箱里的 SandboxServer 在运行(`sandbox.start_server()`) | `Sandbox.run(name)` / `abx sandbox command run <名称>` 指定的命令在沙箱内 SandboxServer 中不存在(HTTP 404)。携带 `command_name` 属性 | +| E3006 | `EnvdRpcError` | envd 拒绝了 RPC 调用(HTTP >= 400) | 检查命令与参数,或用 `abx -v` 查看原始错误响应 | 沙箱 envd 返回 HTTP >= 400(例如未知可执行文件触发 `process.Process/Start` 500)。携带 `status_code`、`rpc_path`、`envd_error`(解析后的 JSON 体)与 `body_text`;消息不包含完整沙箱 URL。`abx sandbox exec` 会将其映射为单条友好提示(如 `Command not found: `) | ### E3004 详细说明 @@ -96,7 +96,7 @@ except SandboxError as e: | 错误码 | 异常类 | 含义 | 建议 | 触发场景 | |--------|--------|------|------|----------| | E5000 | `NetworkError` | 网络错误(基类) | — | 网络连接相关通用错误 | -| E5000 | `GitHubRateLimitError` | GitHub 匿名 API 限流(拉取模板时触发) | 执行 `abx config set github_token`(星号脱敏输入;交互终端下 CLI 会主动引导并只重试一次),CI 中注入 `GITHUB_TOKEN` | 未配置 token 时执行 `abx template install` / `abx install` / `abx template search` | +| E5000 | `GitHubRateLimitError` | GitHub 匿名 API 限流(拉取模板时触发) | 执行 `abx config set github_token`(星号脱敏输入;交互终端下 CLI 会主动引导并只重试一次),CI 中注入 `GITHUB_TOKEN` | 未配置 token 时执行 `abx template init --from SOURCE` / `abx template list` | | E5001 | `ConnectionError_` | 连接失败 | 检查网络连通性和防火墙规则 | 无法连接到沙箱服务 | > **注意**:`ConnectionError_` 类名带下划线后缀,以避免遮蔽 Python 内置 `ConnectionError`。 diff --git a/scripts/check-repo-health.sh b/scripts/check-repo-health.sh index cf0364dd..b7f76c0a 100755 --- a/scripts/check-repo-health.sh +++ b/scripts/check-repo-health.sh @@ -173,10 +173,11 @@ else: client = server = None if client and server: deps = [d.replace(" ", "") for d in client.get("dependencies", [])] - if any(d.startswith("agent-sandbox-server") for d in deps): - result("PASS", "agent-sandbox-client hard-depends on agent-sandbox-server (keeps `from agent_sandbox.server import` working after `pip install agent-sandbox-client`)") + extras = client.get("optional-dependencies", {}) + if not any(d.startswith("agent-sandbox-server") for d in deps) and extras.get("server") == [f"agent-sandbox-server=={cv}"]: + result("PASS", "client-only base with lockstep optional server runtime") else: - result("FAIL", "agent-sandbox-client does not hard-depend on agent-sandbox-server: `pip install agent-sandbox-client` no longer provides agent_sandbox.server (0.2.0 behaviour)") + result("FAIL", "server must be absent from base dependencies and pinned in the server extra") for name, proj in (("client-sdk", client), ("server-sdk", server)): urls = " ".join(proj.get("urls", {}).values()) if "agent-sandbox/python-sdk" in urls and "alibaba-agent-sandbox/agent-sandbox" not in urls: diff --git a/scripts/check_dists.py b/scripts/check_dists.py index aaafee9f..8177a773 100644 --- a/scripts/check_dists.py +++ b/scripts/check_dists.py @@ -9,8 +9,8 @@ * ``py-typed`` — each wheel ships its PEP 561 marker (``agent_sandbox/py.typed`` for the client, ``agent_sandbox/server/py.typed`` for the server). -* ``combined-install`` — both wheels install into one fresh virtualenv and the shared - ``agent_sandbox`` namespace works (``import agent_sandbox`` and ``import agent_sandbox.server``). +* ``combined-install`` — install the client alone, exercise HTTP without the runtime, + then resolve the local server extra and verify command registration. Usage: ``python scripts/check_dists.py [py-typed|combined-install|all]`` """ @@ -32,20 +32,89 @@ "server-sdk": "agent_sandbox/server/py.typed", } -IMPORT_CHECK = """ -import agent_sandbox -import agent_sandbox.server +CLIENT_CHECK = """ +import asyncio +import importlib.metadata +import importlib.util +import threading +from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer + import yaml +from agent_sandbox import Sandbox, AsyncSandbox, ProviderSandbox, sandbox from agent_sandbox.api import launch_template -from agent_sandbox import Sandbox, AsyncSandbox, ProviderSandbox +from agent_sandbox.api.server_client import ServerClient +from agent_sandbox.code_interpreter import Sandbox as InterpreterSandbox +from agent_sandbox.code_interpreter import AsyncSandbox as AsyncInterpreterSandbox + +classes = (Sandbox, AsyncSandbox, ProviderSandbox, InterpreterSandbox, AsyncInterpreterSandbox) +assert all(callable(cls.create) for cls in classes) +assert callable(launch_template) and callable(sandbox) +assert not hasattr(sandbox, "run") and not hasattr(sandbox, "run_async") +assert yaml.safe_load("spec_version: 5") == {"spec_version": 5} +assert importlib.util.find_spec("agent_sandbox.server") is None +try: + importlib.metadata.distribution("agent-sandbox-server") +except importlib.metadata.PackageNotFoundError: + pass +else: + raise AssertionError("base install pulled in the server runtime") + +def identity(value: str) -> str: + return value + +assert callable(sandbox(template="code-interpreter-v1")(identity)) + +try: + sandbox.register(identity) +except ImportError as exc: + assert "agent-sandbox-client[server]" in str(exc) +else: + raise AssertionError("registration without the runtime silently succeeded") +assert sandbox.list_registered() == [] + +class Handler(BaseHTTPRequestHandler): + def do_GET(self): + assert self.path == "/health" + body = b'{"status":"ok"}' + self.send_response(200) + self.send_header("Content-Type", "application/json") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + def log_message(self, *args): + pass + +server = ThreadingHTTPServer(("127.0.0.1", 0), Handler) +thread = threading.Thread(target=server.serve_forever) +thread.start() +async def check_http(): + client = ServerClient(f"http://127.0.0.1:{server.server_port}") + try: + assert await client.health() + finally: + await client.aclose() +try: + asyncio.run(check_http()) +finally: + server.shutdown() + server.server_close() + thread.join() +print("client-only imports, explicit runtime error, and real HTTP: OK") +""" + +SERVER_CHECK = """ +from agent_sandbox import sandbox from agent_sandbox.server import default_registry, start -assert all(callable(cls.create) for cls in (Sandbox, AsyncSandbox, ProviderSandbox)) -assert callable(launch_template) -assert yaml.safe_load("spec_version: 4") == {"spec_version": 4} -print("template YAML parser: available in the base install") -print("agent_sandbox :", agent_sandbox.__file__) -print("agent_sandbox.server:", agent_sandbox.server.__file__) +@sandbox.register +def identity(value: str) -> str: + return value + +assert callable(start) +assert identity("registered") == "registered" +assert any(command["name"] == "identity" for command in sandbox.list_registered()) +assert default_registry().get("identity") is not None +print("server extra namespace and command registration: OK") """ @@ -79,12 +148,28 @@ def check_combined_install() -> None: bindir = "Scripts" if sys.platform == "win32" else "bin" python = venv / bindir / ("python.exe" if sys.platform == "win32" else "python") subprocess.run( - [str(python), "-m", "pip", "install", "--quiet", str(server), str(client)], + [str(python), "-m", "pip", "install", "--quiet", str(client)], check=True, ) # Run outside the repo so the installed wheels, not the source trees, are imported. - subprocess.run([str(python), "-c", IMPORT_CHECK], check=True, cwd=tmp) - print("client + server wheels install into one venv and import: OK") + subprocess.run([str(python), "-c", CLIENT_CHECK], check=True, cwd=tmp) + subprocess.run( + [ + str(python), + "-m", + "pip", + "install", + "--quiet", + "--no-index", + "--find-links", + str(server.parent), + f"{client}[server]", + ], + check=True, + ) + subprocess.run([str(python), "-c", SERVER_CHECK], check=True, cwd=tmp) + subprocess.run([str(python), "-m", "pip", "check"], check=True, cwd=tmp) + print("client-only and optional server wheel installation: OK") def main(argv: list[str]) -> int: diff --git a/server-sdk/RELEASING.md b/server-sdk/RELEASING.md index 76a72753..a93163c4 100644 --- a/server-sdk/RELEASING.md +++ b/server-sdk/RELEASING.md @@ -1,6 +1,6 @@ # Releasing the Python package pair -Publish `agent-sandbox-server` first, then `agent-sandbox-client`, from the same source tag. The client pins the matching server dependency. The unrelated PyPI project `agent-sandbox` is never a publication target. +Publish `agent-sandbox-server` first, then `agent-sandbox-client`, from the same source tag. The client `[server]` extra pins the matching runtime dependency. The unrelated PyPI project `agent-sandbox` is never a publication target. ## Candidate and versions @@ -71,7 +71,7 @@ alpha,发布流程、产物摘要及全新公开安装验证均已通过;这 四处版本(两个 `_version.py`、client 的 server 精确依赖、MCP `SERVER_VERSION`)一起更新。先通过同一标签的完整 CI、产物检查和真实组件验收。将 token 保存在 GitHub repository 或 `pypi` environment 的 `PYPI_API_TOKEN` secret 中,由上传步骤直接引用;不要复制到源码或日志。此流程发布到正式 PyPI 的预发布版本,不是 TestPyPI,也不声明 OIDC 证明。 -手动执行默认 `dry_run=true`,必须选择实际已审查的 `v` 版本标签,选择 main 会失败。验证后创建该标签的 GitHub prerelease,或显式设置 `dry_run=false`。工作流保存本次产物,先检查已有公开文件摘要,再按顺序上传;最后仅安装固定版本 client,验证 server 自动依赖、导入与 `pip check`。 +手动执行默认 `dry_run=true`,必须选择实际已审查的 `v` 版本标签,选择 main 会失败。验证后创建该标签的 GitHub prerelease,或显式设置 `dry_run=false`。工作流保存本次产物,先检查已有公开文件摘要,再按顺序上传;最后先安装固定版本 client 验证独立导入,再安装 `[server]` extra 验证配套运行时导入与 `pip check`。 部分上传失败时保留并使用同一批候选文件。重新构建后如摘要不同,应停止调查,不得依赖 skip-existing 掩盖差异。全部公开包下载与安装验证通过后,才更新对外的发布状态。 From 17f41a16bf72babbe68c5d9ae8b8b860a7dba138 Mon Sep 17 00:00:00 2001 From: anycodes <21079031+anycodes@users.noreply.github.com> Date: Tue, 10 Nov 2026 14:53:03 +0000 Subject: [PATCH 2/2] docs: index recent changes and delivery evidence --- .agents/README.md | 5 ++ .../verify/2026-11-10-recent-change-index.md | 71 ++++++++++++++++ .../process/2026-11-10-recent-change-index.md | 81 +++++++++++++++++++ 3 files changed, 157 insertions(+) create mode 100644 .agents/evidence/verify/2026-11-10-recent-change-index.md create mode 100644 .agents/notes/proposed/process/2026-11-10-recent-change-index.md diff --git a/.agents/README.md b/.agents/README.md index 75b87ae4..629301ae 100644 --- a/.agents/README.md +++ b/.agents/README.md @@ -1,5 +1,10 @@ # `.agents/` — engineering record of the python-sdk repository +## Recent changes — 2026-11-10 + +- [Owner change index](notes/proposed/process/2026-11-10-recent-change-index.md) · [records verification](evidence/verify/2026-11-10-recent-change-index.md). +- This is a dated source/delivery index; Draft implementation, prior releases and new validation remain separate. + - [Client/runtime boundaries](notes/proposed/architecture/2026-11-10-client-runtime-boundaries.md) · [verification](evidence/verify/2026-11-10-client-runtime-boundaries.md). - [Managed Agents API](notes/proposed/architecture/2026-11-05-managed-agents-api.md) · [initial verification](evidence/verify/2026-11-05-managed-agents-api.md) · [release-candidate verification](evidence/verify/2026-11-06-managed-agents-release-candidate.md) · [CI artifacts](evidence/verify/2026-11-06-managed-agents-ci-artifacts.md). diff --git a/.agents/evidence/verify/2026-11-10-recent-change-index.md b/.agents/evidence/verify/2026-11-10-recent-change-index.md new file mode 100644 index 00000000..a85d5f4b --- /dev/null +++ b/.agents/evidence/verify/2026-11-10-recent-change-index.md @@ -0,0 +1,71 @@ +# Verification: recent python-sdk change records + +- Date: 2026-11-10 +- Result: PASS (records-only checks; implementation gates remain separate) +- Scope: `.agents/README.md`, the recent-change process note and this evidence. +- Source revision inspected: `2abbdb02500c12dd8841ab32211372c3ef883c12`. +- Mainline revision inspected: `eb7c980a825d804483474923938277700e048571`. +- Remote query timestamp: `2026-11-10T14:48:35.682193+00:00`. +- Environment: Linux, Git, Python 3; source and remote metadata inspection only. + +## Commands and observations + +- `git fetch origin --prune`: exit 0 in the owning source repository. +- `git status --short --branch`: the selected worktree was clean before this edit. +- `git log origin/main --first-parent --since=2026-11-05 -8`: inspected source history. +- `git rev-parse HEAD` and `git rev-parse origin/main`: revisions listed above. +- GitHub REST GET repository PR and Actions runs: remote metadata queried separately + from any claim of executing tests locally. Authenticated REST supplied a consistent metadata snapshot. + +## Prior source CI snapshot + +[python-sdk PR #160](https://github.com/Alibaba-Agent-Sandbox/python-sdk/pull/160) was open, Draft and unmerged. + +| Workflow | State | Result | +| --- | --- | --- | +| [CI](https://github.com/Alibaba-Agent-Sandbox/python-sdk/actions/runs/42806475630) | completed | success | +| [Docs links](https://github.com/Alibaba-Agent-Sandbox/python-sdk/actions/runs/42806476211) | completed | success | + +These results belong to the source revision above, before this records commit. +New commits require their own CI; a queued workflow is not a pass. + +## Local records validation + +- Scoped inline Markdown check below: exit 0; staged relative links resolved. +- One-off ADR/scope/privacy validation: PASS; seven ordered required sections, + proposed lifecycle, 3 intended files, tracked index membership, + no machine paths or credential-pattern matches. All 166 + relative links in the scoped documents resolved, including the existing README index. +- `git diff --cached --check -- .agents`: exit 0. +- Reviewed the staged `.agents` diff; no runtime/configuration/public article changes. +- `bash scripts/check-repo-health.sh`: exit 0, 106 pass, 0 fail; one existing warning retained. +- `make hygiene`: exit 0. +- `bash .githooks/check-secrets.sh`: exit 0. + +Reproducible scoped link check, run from this repository after staging: + +```bash +python3 - <<'PY' +from pathlib import Path +import re, subprocess +files = subprocess.check_output( + ["git", "diff", "--cached", "--name-only"], text=True).splitlines() +for name in files: + assert name.startswith(".agents/") and name.endswith(".md") + path = Path(name) + text = path.read_text() + for target in re.findall(r"\[[^\]]*\]\(([^\s)]+)\)", text): + if re.match(r"^[a-zA-Z][a-zA-Z0-9+.-]*:", target) or target.startswith("#"): + continue + assert (path.parent / target.split("#", 1)[0]).exists(), (name, target) +print("Scoped staged Markdown links: PASS") +PY +``` + + +## Not verified and cleanup + +No runtime, Docker, cloud, model, performance or new package-publication check was +run for this records-only change. Earlier acceptance retains its original scope. +No cloud resources were created, changed or deleted. Required implementation gates +remain with their existing PRs; this task does not declare those PRs complete. diff --git a/.agents/notes/proposed/process/2026-11-10-recent-change-index.md b/.agents/notes/proposed/process/2026-11-10-recent-change-index.md new file mode 100644 index 00000000..bab33cb6 --- /dev/null +++ b/.agents/notes/proposed/process/2026-11-10-recent-change-index.md @@ -0,0 +1,81 @@ +# Decision: Index recent changes to python client, server and runtime boundaries +Status: proposed + +## Problem + +Recent implementation, user-experience changes and delivery status are spread across +owner records, pull requests and workspace reports. The user requested that recent +changes be retained under each owning repository's `.agents/` and pushed. + +## Decision + +Keep this dated index in python-sdk; link existing canonical decisions and immutable +verification instead of copying their reports or rewriting historical acceptance. +This index adds no product behavior and does not change any older ADR's lifecycle. + +### Owner changes and canonical records + +The candidate makes the exactly matching local server an optional [server] extra, preserves independent standard/async/Provider/HTTP client entry points, and removes the misleading decorator-factory execution shortcuts. Real server registration and deployed remote command invocation remain distinct. Managed Agents and Express SDK work already have their own earlier records. + +- [Client/server installation and invocation boundaries](../../../notes/proposed/architecture/2026-11-10-client-runtime-boundaries.md). +- [Isolated package and local HTTP verification](../../../evidence/verify/2026-11-10-client-runtime-boundaries.md). +- [Managed Agents implementation](../../../notes/proposed/architecture/2026-11-05-managed-agents-api.md). +- [Managed release-candidate evidence](../../../evidence/verify/2026-11-06-managed-agents-release-candidate.md). +- [Express SDK completion](../../../notes/implemented/feature/2026-11-05-express-sdk-completion.md). + +### Mainline and candidate boundary + +- Mainline observed: `eb7c980a825d804483474923938277700e048571`. +- Candidate/source observed before this records commit: `2abbdb02500c12dd8841ab32211372c3ef883c12`. +- [python-sdk PR #160](https://github.com/Alibaba-Agent-Sandbox/python-sdk/pull/160), OPEN and Draft at the recorded query; not merged. +- The unified source CLI is different from published CLI 0.5.10. This task does + not issue npm/PyPI releases, deploy sites or certify changed templates. + +Recent mainline changes, inspected from the refreshed Git history: + +| Date | Commit | Change | +| --- | --- | --- | +| 2026-11-10 | [eb7c980a825d](https://github.com/Alibaba-Agent-Sandbox/python-sdk/commit/eb7c980a825d804483474923938277700e048571) | docs: replace community QR image and join link (#159) | +| 2026-11-10 | [75c7b8b01268](https://github.com/Alibaba-Agent-Sandbox/python-sdk/commit/75c7b8b01268637ea1bb52b60f096436c0782988) | docs: announce preview release schedule and developer community (#158) | +| 2026-11-06 | [fa15efd7e679](https://github.com/Alibaba-Agent-Sandbox/python-sdk/commit/fa15efd7e679b1697e140363a395d998cde7f66a) | Merge pull request #156 from Alibaba-Agent-Sandbox/feat/managed-agents-api-20261105 | + +### Remaining delivery limits + +The candidate PR remains Draft for coordinated delivery and its complete-suite/E2E acceptance. Recorded isolated wheel installation and localhost HTTP success do not establish full real-backend acceptance. The native OpenSandbox thin-adapter design discussed on November 5 remains a proposal; this index does not introduce an adapter or extra. + +## API Design + +N/A — this decision does not involve API changes. Existing runtime, credential, +source identity and resource ownership contracts remain with their original owners. + +## Alternatives considered + +- A workspace-only summary cannot travel with an independent checkout. +- Copying all cross-repository reports duplicates authority and may expose private logs. +- Marking all proposed records implemented would confuse source work with accepted delivery. +- Combining unrelated local changes or permission-only differences in this commit + would conceal the actual records scope. + +## Dependencies + +Existing owner records and GitHub PR/commit state. Coordinated implementation is +tracked in [CLI Issue #244](https://github.com/Alibaba-Agent-Sandbox/agent-sandbox/issues/244). +Each repository retains its own branch, record, verification and push. + +## Test Strategy + +Check the new record structure, relative links, pinned Git source revisions and +scoped diff; run the owner's applicable documentation/health checks. Query PR/CI +state separately from local checks. Do not run cloud tests solely to validate prose. + +## Acceptance criteria + +- Recent owner work is reachable from `.agents/README.md` with dated source references. +- Merged, Draft, failed, unrun and planned work remain distinguishable. +- Existing historical evidence and unrelated work remain intact. +- Documentation checks and remaining gates are recorded; scoped commits are pushed + to task branches and remote branch hashes are independently compared. + +## Evidence + +- [Records verification](../../../evidence/verify/2026-11-10-recent-change-index.md).