airflow.providers.common.ai.sandbox.base¶
Vendor-neutral contract for running agent commands and file operations in an isolated sandbox.
Attributes¶
Who the sandbox was provisioned for: the value of |
|
The agent task currently holding the sandbox, set on attach and cleared on release. |
|
Unix time, in whole seconds, at which the backend will end the sandbox. |
|
The network policy the sandbox was provisioned with, as |
|
The working directory the sandbox was provisioned with, when the creator chose one. |
Exceptions¶
A sandbox operation failed in a way the agent may be able to work around. |
|
The sandbox is unusable and retrying the same call cannot succeed. |
|
A file is larger than the caller's read budget, so it was not transferred. |
Classes¶
What a single sandbox should be provisioned with. |
|
What |
|
Outcome of one command executed inside a sandbox. |
|
Contract for running commands and file operations in an isolated sandbox. |
|
A backend whose sandboxes outlive the process that created them and can be found again. |
Functions¶
|
Whether |
|
Return the owner token naming the Dag run a task is part of: |
|
Serialize a spec's network policy for a sandbox tag, so an attaching toolset can read it back. |
|
Read a |
Module Contents¶
- airflow.providers.common.ai.sandbox.base.OWNER_TAG = 'airflow_owner'[source]¶
Who the sandbox was provisioned for: the value of
SandboxSpec.owner.
- airflow.providers.common.ai.sandbox.base.HOLDER_TAG = 'airflow_holder'[source]¶
The agent task currently holding the sandbox, set on attach and cleared on release.
- airflow.providers.common.ai.sandbox.base.EXPIRES_AT_TAG = 'airflow_expires_at'[source]¶
Unix time, in whole seconds, at which the backend will end the sandbox.
- airflow.providers.common.ai.sandbox.base.NETWORK_TAG = 'airflow_network'[source]¶
The network policy the sandbox was provisioned with, as
encode_network_policy()writes it.
- airflow.providers.common.ai.sandbox.base.WORKDIR_TAG = 'airflow_workdir'[source]¶
The working directory the sandbox was provisioned with, when the creator chose one.
- airflow.providers.common.ai.sandbox.base.is_sandbox_handle(value)[source]¶
Whether
valuecan name a sandbox at all.A handle travels between tasks by XCom and template, so the shapes a missing one takes are known:
Nonefrom a missing XCom under native rendering, the string"None"from the default Jinja environment, and the empty string.
- exception airflow.providers.common.ai.sandbox.base.SandboxError[source]¶
Bases:
ExceptionA sandbox operation failed in a way the agent may be able to work around.
The toolset turns this into a
ModelRetryso the model can adjust and try again within the run (a bad path, a command the image cannot run). Raised fromSandboxBackend.create()it is treated as terminal instead, since the model cannot influence provisioning.
- exception airflow.providers.common.ai.sandbox.base.SandboxTerminalError[source]¶
Bases:
SandboxErrorThe sandbox is unusable and retrying the same call cannot succeed.
Credentials were rejected, the daemon is unreachable, the sandbox is gone. The toolset lets this propagate and fail the task, so Airflow’s own retry handles it rather than the model burning its retry budget.
- exception airflow.providers.common.ai.sandbox.base.SandboxFileTooLargeError(path, size_bytes, max_bytes)[source]¶
Bases:
SandboxErrorA file is larger than the caller’s read budget, so it was not transferred.
- class airflow.providers.common.ai.sandbox.base.SandboxSpec[source]¶
What a single sandbox should be provisioned with.
Note
Experimental: this can change or be removed in a minor release of this provider. See Stable and experimental features.
Passed to
SandboxBackend.create(). Every field is optional and a backend may not be able to honor all of them; a backend that cannot enforce a field it was given must raise rather than silently ignore it, so a DAG author never believes a restriction is in force when it is not.- Parameters:
env – Environment variables to set inside the sandbox. Airflow never populates this itself – the DAG author decides what, if anything, the sandbox is given. Anything placed here is visible to model-generated code, so scope it to what that code legitimately needs.
block_network – Deny all outbound network access. Defaults to
True: an isolated sandbox that cannot phone home is the safe starting point, and egress is opened deliberately.allow_egress_to – Hostnames the sandbox may reach when
block_networkisTrue. An empty or unset value withblock_network=Truemeans no egress at all.allow_egress_to_cidrs – IPv4 address ranges, in CIDR notation such as
"203.0.113.0/24"or"203.0.113.7/32", the sandbox may reach whenblock_networkisTrue, on any port and protocol. This is the right field for one service at a fixed public address; it cannot serve a package registry behind a CDN, whose addresses rotate, and a hosted backend cannot reach private (RFC 1918) addresses at all. A backend that enforces it does so at the address layer, which is a stronger guarantee than a hostname list gives, so it needs no opt-in. Both lists may be set together; how a backend combines them, and what that costs, is the backend’s to document.owner – Who the sandbox is for, when a task provisions it for an agent task to attach to later. A
SandboxToolsetattaching to the sandbox has to present the same value, and by default it presents the Dag run it is part of, so the provisioning task in the same run writesowner=dag_run_owner(context). Unset for a sandbox nobody will attach to. A backend that cannot record it must refuse it.
- allow_egress_to: collections.abc.Sequence[str] | None = None[source]¶
- allow_egress_to_cidrs: collections.abc.Sequence[str] | None = None[source]¶
- airflow.providers.common.ai.sandbox.base.dag_run_owner(context)[source]¶
Return the owner token naming the Dag run a task is part of:
"<dag_id>/<run_id>".This is what a
SandboxToolsetpresents when it attaches to a sandbox without an explicitowner, so a task provisioning a sandbox for an agent task in the same Dag run stamps it withSandboxSpec(owner=dag_run_owner(context)). The pair is unique across the deployment where a barerun_idis not: two Dags on the same schedule share their run ids.contextis the task context, as a@taskreceives it in**contextorget_current_contextreturns it.
- airflow.providers.common.ai.sandbox.base.encode_network_policy(spec)[source]¶
Serialize a spec’s network policy for a sandbox tag, so an attaching toolset can read it back.
The toolset tells the model what the sandbox can reach, because a model that has to discover a denied network by failing wastes a turn, or a whole command budget. An attached sandbox was provisioned under a spec the toolset never sees, so the backend records the policy on the sandbox at create and
decode_network_policy()turns it back into a spec. Compact JSON with sorted keys, so the same policy always encodes the same way.
- airflow.providers.common.ai.sandbox.base.decode_network_policy(value)[source]¶
Read a
NETWORK_TAGvalue back into a spec carrying only the network fields.Raises
ValueErrorwhen the value is not whatencode_network_policy()writes, so the caller decides what a stamp written by something else means.
- class airflow.providers.common.ai.sandbox.base.AttachedSandbox[source]¶
What
AttachableSandboxBackend.attach()reports about the sandbox it just claimed.remaining_lifetimeis in seconds, orNonewhen the creator recorded no expiry, in which case nothing is claimed about it.networkis the policy the sandbox was provisioned with andworkdirits working directory, eachNonewhen the creator recorded nothing.- network: SandboxSpec | None[source]¶
- class airflow.providers.common.ai.sandbox.base.SandboxExecResult[source]¶
Outcome of one command executed inside a sandbox.
timed_outmeans the command hit the budget, soexit_codecarries no meaning.stdout_truncated/stderr_truncatedmean the backend dropped bytes while reading that stream, before any model-facing formatting.sandbox_terminatedmeans the backend destroyed the sandbox to stop the command, so the toolset must provision a fresh one before the next call.applied_timeoutis the deadline the backend actually gave the command, when that differs from the one it was asked for – a backend may have to shorten it, for instance to fit what is left of a sandbox’s life.Nonemeans the requested deadline was used as given. The toolset reports this rather than the request, so a model that times out is told the budget it really had and can ask for something that fits.
- class airflow.providers.common.ai.sandbox.base.SandboxBackend[source]¶
Bases:
abc.ABCContract for running commands and file operations in an isolated sandbox.
Note
Experimental: this can change or be removed in a minor release of this provider. See Stable and experimental features.
The lifecycle is create -> (any number of operations) -> destroy, driven by
SandboxToolset. The four operation methods are named after the four tools the toolset exposes, so the mapping from a model-facing tool to the backend call behind it is literal;createanddestroyare lifecycle and have no tool, and neither hasexport_file, which hands a finished file to the Dag author. A backend whose sandboxes can be found again from another process implementsAttachableSandboxBackendinstead, which adds the ownership rules a task-provisioned sandbox needs.Implementations must be cheap to construct, because constructors run at Dag-parse time: resolve credentials and open connections lazily, on first use.
destroymust be idempotent – destroying an already-gone sandbox is not an error. All methods are synchronous; the toolset offloads them to a thread, so a call may block for as long as its timeout allows.Raise
SandboxErrorfor a failure the model could work around, andSandboxTerminalErrorfor one it cannot.- abstractmethod create(*, spec=None)[source]¶
Provision one sandbox and return its handle (name or id).
specofNonemeans “no requirements stated”: the backend applies its own defaults and makes no guarantee. It is not the same as a defaultSandboxSpec, which is an explicit request for an isolated sandbox. The toolset always sends a concrete spec, soNoneonly reaches a backend a caller drives directly.Raise
SandboxTerminalErrorifspecasks for something this backend cannot enforce, rather than provisioning something weaker than was asked for. It is terminal rather than recoverable because it states a configuration fact the model cannot see and cannot fix by retrying.Every failure raised here is terminal, whichever class carries it. The model has no input into provisioning, so a
SandboxErrorfromcreateis not something it can work around; the toolset re-raises one asSandboxTerminalErrorand fails the task, so Airflow’s retry attempts the provisioning again.
- abstractmethod run_command(sandbox, command, *, timeout, max_output_bytes)[source]¶
Run
commandthrough a shell in the sandbox, bounded bytimeoutseconds.max_output_bytesbounds what the backend retains per stream while reading, so unbounded command output cannot exhaust worker memory before the toolset gets a chance to format it.
- read_file(sandbox, path, *, max_bytes)[source]¶
Read a file from the sandbox.
Raise
SandboxFileTooLargeErrorinstead of transferring a file larger thanmax_bytes.
- write_file(sandbox, path, content)[source]¶
Write
contenttopathin the sandbox, creating parent directories.The payload rides in the command itself, so this default is bounded by the guest’s command-line length. A backend that can stream stdin or upload directly should override.
- export_file(sandbox, path, dest, *, max_bytes)[source]¶
Copy a regular file out of the sandbox into
destand return the bytes written.destis a writable binary stream, typically an object-storage file, and the copy goes through it without the whole file ever being held in worker memory, so a file far overread_file()’s budget can leave the sandbox. Only a regular file is exported: a directory, a device, or a FIFO is refused, since none of them has a size to promise a caller. RaiseSandboxFileTooLargeErrorinstead of copying a file larger thanmax_bytes, andSandboxErrorwhen the file changed size while it was being copied, which means a process in the sandbox is still writing it.This default reads the file in slices through
run_command(), one command per slice, and needsstat,tail,headandbase64in the guest. It relies onrun_commandreturning each slice’s output intact, or settingstdout_truncatedwhen it could not, and on nothing but the command’s own output reaching stdout. Override it when the vendor can stream a file out, and bound the whole copy by_export_deadline()as this one does, since a guest that keeps sending a byte now and then never trips a stall timeout.
- class airflow.providers.common.ai.sandbox.base.AttachableSandboxBackend[source]¶
Bases:
SandboxBackendA backend whose sandboxes outlive the process that created them and can be found again.
Note
Experimental: this can change or be removed in a minor release of this provider. See Stable and experimental features.
This is what lets one task provision a sandbox and a later agent task use it: the provisioning task stamps the sandbox with
SandboxSpec.owner, theSandboxToolsetattaches withattach_to=<handle>, and the task that created the sandbox destroys it. A backend that has no way to reach a sandbox from another process, such assbx, stays a plainSandboxBackendand the toolset refusesattach_tofor it at construction.Two primitives are the vendor’s to implement:
read_tags()andwrite_tags(). The rules are written once, here, on top of them:Who may attach. A sandbox is attached only if its
OWNER_TAGequals the owner the toolset presents. A bare handle is never enough, so a wrong handle from an upstream XCom is refused rather than used. This stops a run reaching the wrong sandbox by mistake and gives attribution; it is not a boundary between authors, since anyone holding the vendor credential can rewrite the tags or drive the sandbox without the toolset.One holder at a time. Attaching stamps
HOLDER_TAGwith the attaching task and releasing clears it. A second, different holder is refused while the first is attached. The same holder may attach again, so a retry of the agent task finds its files after an attempt that died without releasing. The claim is a plain read-then-write over the vendor’s tags, re-read after the write to catch a competing writer, so two tasks attaching in the same instant can both pass; it stops the sequential mistakes, not a race, and a Dag that needs a workspace per task provisions one per task.The lifetime is the creator’s.
createstampsEXPIRES_AT_TAG, andattach()reports what is left so the toolset can bound its commands and tell the model the clock it is on.createalso records the network policy underNETWORK_TAG, so the toolset can tell the model what the sandbox reaches, and the working directory underWORKDIR_TAG, so the attaching backend resolves relative paths where the creator’s shell does.
- abstractmethod read_tags(sandbox)[source]¶
Return the tags on a sandbox.
Raise
SandboxTerminalErrorif the sandbox does not exist or has ended: attaching to it can never succeed.
- attach(sandbox, *, owner, holder)[source]¶
Claim
sandboxforholderand report what the creator recorded about it.Raises
SandboxTerminalErrorif the sandbox is not owned byowneror is held by someone else; either is a fact about the Dag that no retry of the agent task can change.