Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
405b8f9
Define explicit provider create settlement evidence
SaladDay Sep 24, 2026
edb8623
Carry managed allocation identity through daemon authentication
SaladDay Sep 24, 2026
bfa9a8a
Wire deployment provider administration and package cloud SDK support
SaladDay Sep 24, 2026
341c578
Add deployment-wide hosted provider setup and guarded switching
SaladDay Sep 24, 2026
542059f
Document cloud selection and retained provider state
SaladDay Sep 24, 2026
b8b37f9
Implement initial official SDK E2B provider boundary
SaladDay Sep 24, 2026
8a8424a
Package and test managed E2B helper lifecycle proofs
SaladDay Sep 24, 2026
337729f
Preserve definite absence proofs and verify private helper state
SaladDay Sep 24, 2026
24f1793
Implement hosted provider configuration and lifecycle transitions
SaladDay Sep 24, 2026
cebf19d
Include E2B initialization checks and archived source provenance
SaladDay Sep 24, 2026
906639e
Allow safe migration rollback before provider switching
SaladDay Sep 24, 2026
1027a1d
Authenticate enrollment credentials before deployment validation
SaladDay Sep 24, 2026
173a274
Use SDK state enums and bound each discovery page
SaladDay Sep 24, 2026
d4ed417
Clarify provider generation ownership and SDK checks
SaladDay Sep 24, 2026
c22a61e
Generate deployment provider administration contract
SaladDay Sep 24, 2026
7aa6634
Keep SDK-dependent startup tests in the E2B SDK gate
SaladDay Sep 24, 2026
15eebb6
Include native SDK tracing imports and verify frozen transport
SaladDay Sep 24, 2026
81dd6d4
Return actual deployment resources after configuration mutations
SaladDay Sep 24, 2026
859d71a
Keep administration available when hosted provider dependencies fail
SaladDay Sep 24, 2026
431a470
Report unavailable provider dependencies without disclosing credentials
SaladDay Sep 24, 2026
41f873f
Pass provider load context to structured logger
SaladDay Sep 24, 2026
7ff6712
Document E2B account key cleanup requirement
SaladDay Sep 24, 2026
7c5cfd7
Recover durable E2B create rejections without cloud access
SaladDay Sep 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 44 additions & 20 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -613,8 +613,7 @@ batches. Resource reads need only tenant authorization, not a live Runtime.
See the [Template coverage and unresolved semantics](contracts/agents-api/environment-templates.md).

SandboxProvider has five operations: Create, GetInfo, Renew, Kill and RunCommand.
Use maintained provider SDKs and thin adapters. Hosted deployments select Docker or
the optional microsandbox profile on the assigned node.
Use maintained provider SDKs and thin adapters. Hosted deployments select one deployment-wide Provider: E2B cloud, or Docker/microsandbox on administrator-owned nodes.
Provider initialization creates the sandbox and starts its daemon/harness;
RunCommand is for initialization only. Daily execution and Files use Runtime and
native or bounded local capabilities. Docker's lack of a native renewable lease
Expand All @@ -631,13 +630,27 @@ executor process or additional execution architecture is required. Qualify
principal/tenant ownership, credentials and connection lifecycle using the pinned
client and actual execution; document our transport boundary explicitly.

The Core-managed E2B Provider, including its custom HTTP/Connect and envd protocol
implementation, is retired. User-side E2B tooling uses the official SDK and the
shared Runtime, not an additional execution architecture. The
[E2B guide](services/agents-api/deploy/e2b/README.md) owns packaging and user-managed
allocation, renewal and cleanup. Historical Core-managed E2B acceptance retains
only its original scope; it does not qualify the new enrollment path. Current
public Template acceptance uses Docker.
Core-managed E2B is a separate hosted deployment choice, using the official pinned
Python SDK through a packaged private helper. Do not restore the retired custom
HTTP/Connect or envd implementation. The adapter implements the same five operations;
cloud allocations use direct placement with no synthetic node, while Runtime execution
and file access keep the shared daemon contract. Its immutable Runtime template build
is deployment configuration, not a public Environment Template. Keep the account API
key encrypted in the database, write-only through admin input and absent from helper
arguments, logs, metadata and receipts. SDK connection materials and attempted-create
receipts belong in the private durable provider state directory; never replace missing
state to make cleanup appear successful. Create runs once. Unknown control-plane
outcomes remain blockers even if a listing is empty. Explicit matching-reference
CreateSettled evidence proves that the original initialization cannot mutate further;
confirmed absent compute may then be released. Ordinary 404 responses do not prove it.
The helper's pinned SDK, dependencies and licenses ship with Core; users do not install
Python packages after selecting E2B in Web. Application-managed self_hosted tooling
remains independent and uses the same Runtime. Qualify each changed path using actual
provider and model execution before claiming acceptance. Run `make check-e2b-provider`
with `PARSAR_E2B_SDK_PYTHON` pointing to the pinned SDK environment; the packaged
helper build runs the provider tests as well. The SDK gate also covers the
application-managed launch tests. `make check` covers shared initialization and
managed initialization using only the Python standard library.

The independent Docker Provider consumes an immutable Runtime image and retains
one caller-owned allocation reference through partial creation and cleanup. Persist
Expand Down Expand Up @@ -667,14 +680,25 @@ of non-secret matched distribution artifacts for its node installation command;
never serve private installation files or arbitrary paths. Node installation
reuses the existing node process and Provider configuration, verifies downloaded
files, retains private identity and uses a user service. It performs no SSH
installation, Session creation or model call. PostgreSQL owns this immutable selection under
the existing execution lease and deployment lock. Exact retries are idempotent;
a changed selection conflicts. No provider migration or hot reload is implied.
installation, Session creation or model call. PostgreSQL owns a generation-tagged
selection under the existing execution lease and deployment lock. E2B setup needs an
account key and a qualified immutable Runtime template, not node enrollment. Changing
selection requires maintenance, the current generation and verified zero retained or
pending resources. Retire old nodes and enrollment credentials in the same transaction;
keep history. Do not automatically delete resources or retry uncertain writes.

Web-managed startup claims the stable installation identity and a new owner epoch
even before provider selection. The existing runtime manager stays present and
loads one immutable configuration when selection becomes available, before any
node lifecycle is created. Admission refuses uninitialized hosted work without
loads an immutable configuration for the selected generation. A clean switch pauses
new manager operations, drains old lifecycle calls and loops, commits the new selection
and activates it through the existing loader. Keep the Worker and runtime manager as
single owners. Failed replacement activation stays in maintenance and can be retried; resume only
when the committed generation is active. A locally unavailable provider dependency
keeps hosted admission closed while the existing scan waits for repair; it must not
take the administrator recovery API offline, including on restart. Database and
ownership failures still stop the execution owner. Mutation responses read actual
resource counts in their transaction; omitted accounting must never imply cleanup.
Observation and bootstrap share this selection. Admission refuses uninitialized hosted work without
creating Session state. File-managed and Web-managed configuration are mutually
exclusive. Node registration, observation and daemon bootstrap reuse existing
contracts. Derive Runtime bootstrap and daemon WebSocket addresses from the saved
Expand All @@ -683,23 +707,23 @@ configuration API a startup snapshot; use the live deployment endpoint in setup.


A Core deployment may run without a sandbox provider. When enabled, exactly one
sandbox provider is selected at setup: Docker or microsandbox. Keep both adapters but reject multiple provider entries,
sandbox provider is selected at setup: E2B, Docker or microsandbox. Keep both adapters but reject multiple provider entries,
legacy default-provider maps and engine-based placement. Harness selection is
independent. The configuration has one installation UUID and one provider kind.
A local node has one explicit backend object; a remote-only Core has none. No mixed-provider or engine-based provider route is supported.

The execution database pins the selected installation and backend namespace.
Under the existing execution lease, startup validates that identity before
reconciling work. Changing an installation or backend requires a previous startup
of the old configuration in maintenance, with new configuration still in
maintenance. Maintenance prevents fresh hosted Sessions and fresh allocations,
reconciling work. File-managed backend changes require starting the old and new configurations in
maintenance. Web-managed changes use the generation-checked maintenance and deployment
endpoints; the Core origin remains unchanged within this operation. Maintenance prevents fresh hosted Sessions and fresh allocations,
while retaining known receipts, existing Session use and explicit cleanup.
Unreleased allocation receipts include live/stopped compute, snapshots, uncertain
operations and pending cleanup; all must be released before a switch. Pending
hosted Environments that have not yet received an allocation also block a switch.
A failed check reports why and performs no resource deletion or provider change.
After a successful switch, restart the same configuration with maintenance off to
admit new sandboxes. Retain immutable historical allocation ownership; never
After a successful Web switch, explicitly resume through the maintenance endpoint.
File-managed deployments restart the same configuration with maintenance off. Retain immutable historical allocation ownership; never
migrate an existing Session to another provider or recreate a released allocation.
Fresh adoption of a deployment with unverified retained allocations fails closed.

Expand Down
10 changes: 10 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ build-agents-api-release:
check-agents-api: build-agents-api
go test ./services/agents-api/... ./packages/agents-client/... -count=1
PYTHONDONTWRITEBYTECODE=1 python3 services/agents-api/deploy/runtime/initialize_receipt_test.py
PYTHONDONTWRITEBYTECODE=1 python3 services/agents-api/deploy/e2b/managed_init_test.py

docker-build-agents-api:
./scripts/build-agents-api-image.sh
Expand Down Expand Up @@ -126,3 +127,12 @@ check-distribution:

build-core-distribution:
./scripts/build-core-distribution.sh

.PHONY: build-e2b-provider check-e2b-provider
build-e2b-provider:
./scripts/build-e2b-provider.sh

# The pinned SDK environment is also tested when building the shipped helper.
check-e2b-provider:
PYTHONDONTWRITEBYTECODE=1 $${PARSAR_E2B_SDK_PYTHON:-python3} -m unittest discover -s services/agents-api/deploy/e2b -p '*_test.py'
PYTHONDONTWRITEBYTECODE=1 $${PARSAR_E2B_SDK_PYTHON:-python3} -m unittest discover -s services/agents-api/tools/e2b-provider -p '*_test.py'
2 changes: 1 addition & 1 deletion apps/web/e2e/fixture-sandbox.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export function handleSandboxFixture(request, response, url, sendJson, sendError
calls.push({ path, method: request.method, authorization: request.headers.authorization ?? null });
// This fixture represents authenticated console routes, not direct Core administration.
if (request.headers.authorization) { sendError(response, 400, "Browser admin credentials are not accepted.", "unexpected_authorization"); return true; }
const deployment = () => ({ installation_id: "fixture-installation", provider, core_url: coreUrl, maintenance: false, owner_epoch: 1 });
const deployment = () => ({ installation_id: "fixture-installation", provider, core_url: coreUrl, maintenance: false, owner_epoch: 1, generation: provider ? 1 : 0, mode: provider ? "nodes" : "", resources: { allocations: nodes.some((entry) => entry.id === "node-local") ? 1 : 0, pending: 0 } });
if (path.endsWith("/deployment") && request.method === "POST") {
let body = "";
request.on("data", (chunk) => { body += chunk; });
Expand Down
11 changes: 7 additions & 4 deletions apps/web/e2e/sandbox-manager.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,8 @@ for (const value of ["node_unavailable", "resource_missing"]) {
}

test("Chinese actions, diagnostics, and enrollment are translated and language persists", async ({ page, request }) => {
await page.getByLabel("Language / 语言").selectOption("zh");
await page.getByRole("button", { name: "Language and appearance" }).click();
await page.getByRole("menuitemradio", { name: "简体中文" }).click();
await request.post(`${fixture}/__fixture/sandbox-diagnostic?value=resource_missing`);
await page.getByRole("button", { name: "托管沙箱管理", exact: true }).click();
await page.locator(".sandbox-topology-node").first().click();
Expand All @@ -109,9 +110,10 @@ test("Chinese actions, diagnostics, and enrollment are translated and language p
await expect(page.getByLabel("一次性注册命令")).toHaveValue(/fixture-once-token/);
await expect(page.getByRole("dialog")).toContainText("等待节点连接");
await page.keyboard.press("Escape");
await page.getByLabel("Language / 语言").selectOption("en");
await page.getByRole("button", { name: "语言和外观" }).click();
await page.getByRole("menuitemradio", { name: "English" }).click();
await page.reload();
await expect(page.getByLabel("Language / 语言")).toHaveValue("en");
await expect(page.getByRole("button", { name: "Language and appearance" })).toContainText("EN");
await expect(page.getByLabel("One-time enrollment command")).toHaveCount(0);
});

Expand Down Expand Up @@ -290,7 +292,8 @@ for (const theme of ["light", "dark"]) {
test(`Chinese topology and node details render on desktop in ${theme}`, async ({ page }, testInfo) => {
await page.setViewportSize({ width: 1440, height: 1000 });
await page.evaluate((theme) => document.documentElement.dataset.theme = theme, theme);
await page.getByLabel("Language / 语言").selectOption("zh");
await page.getByRole("button", { name: "Language and appearance" }).click();
await page.getByRole("menuitemradio", { name: "简体中文" }).click();
await page.getByRole("button", { name: "托管沙箱管理", exact: true }).click();
await expect(page.getByRole("region", { name: "沙箱节点", exact: true })).toContainText("可用");
await page.screenshot({ path: testInfo.outputPath(`topology-zh-${theme}.png`), animations: "disabled" });
Expand Down
Loading
Loading