eve-aca-backend
v0.1.0
Published
Azure Container Apps Sandboxes backend for eve
Maintainers
Readme
eve-aca-backend
A TypeScript eve sandbox backend for Azure Container Apps Sandboxes (Microsoft.App/SandboxGroups). It uses the ACA data-plane REST API directly—no Python process, aca CLI subprocess, Docker daemon, or unofficial SDK at runtime.
[!IMPORTANT] ACA Sandboxes and ACA Dynamic Sessions are different products. This package targets hardware-isolated, persistent ACA Sandbox microVMs with stop/resume, disk commits, files, exec, and egress policy.
Status and compatibility
This package and ACA Sandboxes are preview software.
- Node.js: 24+
- eve peer range:
>=0.29.4 <0.31.0 - ACA data-plane API:
2026-02-01-preview - OAuth scope:
https://dynamicsessions.io/.default - Default disk: public
ubuntu
The API version is centralized and overrideable because preview payloads can drift. Contract tests cover eve 0.29.4 and the current 0.30.x line; opt-in live tests are still the release gate for a new ACA API version.
Install
npm install eve-aca-backend @azure/identity@azure/identity is already a runtime dependency, but listing it explicitly is useful if application code constructs its own credential.
Azure prerequisite
Create an ACA sandbox group in the same region you intend to use. Assign the managed identity of the host Azure Container App the Container Apps SandboxGroup Data Owner role on that group.
The optional infra/main.bicep creates a minimal group, gives it a system-assigned outbound identity, and optionally assigns the official Data Owner role (c24cf47c-5077-412d-a19c-45202126392c) to a supplied host principal. See infra/README.md.
Two identities have separate roles:
- The host Container App identity authenticates this Node.js package to the ACA sandbox data plane.
- The sandbox group identity can obtain downstream Azure tokens for proxy-injected credentials. Grant it only the downstream roles it needs.
The backend does not create ARM resources or role assignments at runtime.
Basic eve configuration
import { defineSandbox } from "eve/sandbox";
import { aca } from "eve-aca-backend";
export default defineSandbox({
backend: () =>
aca({
subscriptionId: process.env.AZURE_SUBSCRIPTION_ID!,
resourceGroup: process.env.ACA_RESOURCE_GROUP!,
sandboxGroup: process.env.ACA_SANDBOX_GROUP!,
region: process.env.ACA_REGION!,
disk: "ubuntu",
resources: { cpu: "1000m", memory: "2048Mi" },
lifecycle: { autoSuspendSeconds: 300 },
networkPolicy: "deny-all",
}),
});The factory form is recommended so configuration is evaluated lazily. In Azure Container Apps, DefaultAzureCredential automatically uses the workload's managed identity. To select a user-assigned host identity, set managedIdentityClientId. For tests or a specialized chain, pass any structural TokenCredential as credential.
Keep the custom backend external when eve compiles authored modules. Add it beside other external runtime packages in the root agent.ts:
export default defineAgent({
// model and other settings...
build: {
externalDependencies: ["eve-aca-backend"],
},
});Without this, the authored-module bundler may attempt to inline Azure Identity and fail on its Node-specific module wrappers.
Core options
| Option | Purpose |
| ------------------------------------------------- | ----------------------------------------------------------- |
| subscriptionId, resourceGroup, sandboxGroup | Existing sandbox group identity |
| region | Builds https://management.<region>.azuredevcompute.io |
| endpoint | Explicit HTTPS endpoint; mutually exclusive with region |
| credential | Custom structural Azure token credential |
| managedIdentityClientId | User-assigned identity client ID for the default credential |
| disk / diskId | Public disk name or private disk ID |
| resources | ACA CPU, memory, and optional disk quantities |
| lifecycle | Auto-suspend and opt-in auto-delete policy |
| environment | Values intentionally exposed inside every sandbox |
| networkPolicy | Supported subset of eve's portable policy |
| egressPolicy | ACA-native policy, including secret/identity references |
| apiVersion, timeout/poll options | Preview compatibility and operation bounds |
networkPolicy and egressPolicy are mutually exclusive. Auto-delete is opt-in because deleting a sandbox destroys the durable workspace represented by eve reconnect metadata.
Templates and persistence
When an eve sandbox has bootstrap code, workspace seeds, or skill seeds, eve runs prewarm() before the built service begins accepting traffic. With an external custom backend in a self-hosted Nitro build, this occurs during eve start:
- Find a ready private disk with the deterministic, namespaced template fingerprint.
- Otherwise create a temporary sandbox from the configured base disk.
- verify Bash and create
/workspace; - write workspace and resolved
$HOME/.agents/skillsseeds; - run authored bootstrap;
- commit the filesystem as a private ACA disk and wait for
Ready; - delete the temporary sandbox after the commit is known complete.
A live eve session is created from that disk with fresh resources, environment, lifecycle, labels, and egress settings. This matches eve's filesystem-template semantics better than restoring a full-memory snapshot.
captureState() persists only:
{
"backendName": "aca",
"sessionKey": "...",
"metadata": { "version": 1, "sandboxId": "..." }
}shutdown() stops and polls the sandbox rather than deleting it. A later server process reattaches by ACA ID, validates both backend and hashed session ownership labels before resume, resumes it, and reapplies lifecycle and egress policy before returning the handle. Environment is create-only in the current ACA API; an existing sandbox retains its original environment after reattach.
ACA requires a sandbox to be running before its egress policy can be updated. Reattach therefore resumes and then immediately replaces the policy. Normally the sandbox was stopped with the same policy already active. If a deployment intentionally changes an existing session from a permissive to a stricter policy, replace that sandbox/session rather than assuming policy installation can precede resume.
Sandbox operations
The backend implements the complete public eve SandboxSession contract:
- blocking
run()through buffered ACA shell execution; - detached
spawn()with byte streams, sharedwait(), abort, process-group kill, and spool cleanup; - streaming, binary, and encoded text file reads/writes;
- 1-based inclusive text line ranges;
- missing file reads returning
null; - forced and recursive deletion;
- relative paths rooted at
/workspace; - runtime network-policy updates;
- transparent resume after ACA auto-suspend, with a short concurrency-safe running-state cache.
ACA's official exec endpoint is buffered and does not return a durable process handle. spawn() therefore starts a detached process group and polls private spool files under /tmp/.eve-aca-processes. The selected disk must provide Bash, setsid, nohup, awk, sync, and Linux /proc (the default ubuntu disk does). This preserves eve semantics but is less efficient for very large, continuously growing output than a native streaming process API. Keep the live spawn tests as a deployment gate.
Credentials: two explicit modes
1. Conventional sandbox environment
aca({
// ...Azure group options
environment: {
VENDOR_API_KEY: process.env.VENDOR_API_KEY!,
},
});These values are deliberately visible to commands in the sandbox and to code controlled by the model. Use this only when that is the intended trust boundary. Values are sent at sandbox creation, never written into eve reconnect metadata, and redacted from backend errors/logs.
2. ACA egress proxy injection
An eve literal transform keeps the value out of the sandbox process:
aca({
// ...Azure group options
networkPolicy: {
allow: {
"api.example.com": [
{
match: { method: ["POST"], path: { exact: "/v1/query" } },
transform: [{ headers: { authorization: `Bearer ${process.env.API_TOKEN!}` } }],
},
],
},
},
});For better secret lifecycle, store a group secret out of band and use an ACA-native reference:
aca({
// ...Azure group options
egressPolicy: {
defaultAction: "Deny",
trafficInspection: "Full",
rules: [
{
match: { host: "api.example.com", path: "/v1/query", methods: ["POST"] },
action: {
type: "Transform",
headers: [
{
operation: "Set",
name: "Authorization",
valueRef: {
secretRef: {
secretId: "vendor",
secretKey: "token",
format: "Bearer {}",
},
},
},
],
},
},
],
},
});managedIdentityRef is also supported for system- and user-assigned sandbox group identities. Its resource is the downstream token resource/audience; a user-assigned ref additionally requires identityResourceId.
Group secrets are not automatically environment variables. Proxy references keep their values out of the sandbox.
Network-policy compatibility
The translation is intentionally fail-closed.
Supported:
"allow-all"and"deny-all";- host allow lists and wildcard host patterns accepted by ACA;
- the
"*": []catch-all; - exact path and HTTP method matching;
- literal header transforms;
- ACA-native Allow, Deny, and Transform rules;
- ACA group-secret and managed-identity header references.
Rejected before an HTTP request:
- CIDR/subnet rules;
forwardURLproxying;- request header/query matchers;
startsWithand regex path matchers;- ACA rewrite rules through this package's narrowed policy type.
Rejecting an unprovable mapping is safer than silently broadening egress. deny-all uses ACA Full traffic inspection.
Errors, retries, and aborts
AcaSandboxError exposes a safe operation, HTTP status, Azure request ID, and service code. Authorization headers, environment values, and transform values are never included. REST retries are operation-aware:
- reads and idempotent writes/deletes may retry throttling and transient failures;
- collection create and commit are not blindly replayed after ambiguous failures;
- all polling and session primitives accept/propagate abort signals where the eve contract provides one.
If commit outcome is ambiguous, the temporary sandbox is preserved for operator reconciliation instead of being deleted and potentially losing the only source filesystem. Concurrent prewarm calls in one backend process share a single operation. Separate build processes can still race because the preview API exposes no documented conditional commit/idempotency key; deterministic labels make the resulting disks discoverable, but operators should prune duplicates after overlapping builds.
Development
npm install
npm run checkLive tests require a disposable pre-provisioned group and Data Owner access:
export AZURE_SUBSCRIPTION_ID=...
export ACA_RESOURCE_GROUP=...
export ACA_SANDBOX_GROUP=...
export ACA_REGION=...
npm run test:liveThe live suite creates and removes sandboxes and private template disks. Use a non-production group.
The runnable example is under examples/eve-agent.
Preview maintenance
Before changing ACA_API_VERSION:
- Download the newest official
azure-containerapps-sandboxPython wheel without installing it. - Diff its constants, operation modules, model serializers, state polling, and route payloads against
src/client/andsrc/types.ts. - Update mock contract fixtures.
- Run all live lifecycle, files, spawn, commit, reattach, managed-identity, and egress tests.
- Record changes and any migration note in
CHANGELOG.md.
See CONTRIBUTING.md for the release checklist.
