Skip to content

Add SandboxToolset for sandboxed agent shell and file access - #68847

Merged
kaxil merged 9 commits into
apache:mainfrom
zozo123:add-sandbox-provider
Aug 13, 2026
Merged

Add SandboxToolset for sandboxed agent shell and file access#68847
kaxil merged 9 commits into
apache:mainfrom
zozo123:add-sandbox-provider

Conversation

@zozo123

@zozo123zozo123 commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds SandboxToolset to the Common AI provider: an agent gets a shell and a filesystem inside a disposable sandbox that runs off the Airflow worker process.

ToolWhat it does
run_commandRuns a shell command. Pipes, redirection, && and globs work. A non-zero exit is returned as output, not raised, so the model reads stderr and corrects itself.
read_fileReads a text file head-first and reports the next offset, so the model can page through a long file.
write_fileWrites text, creating parent directories.
list_directoryLists a directory, marking directories with a trailing /.

These are the same four names and shapes pydantic-ai's own sandbox capabilities use, so a model that has seen one already knows this one, and a vendor with an adapter for one is close to having written this one.

One backend ships: SbxSandboxBackend, driving Docker Sandboxes through the sbx CLI. It needs no Python dependency, so this PR adds no runtime requirement and leaves uv.lock untouched.

Which boundary this is

"Sandboxed" means different things at different layers, and picking the wrong layer is the common way to end up with less protection than you expected.

BoundaryWhat moves insideProtects against
A tool callWhat these four tools do. This PR.Model-written code damaging the worker host, reading its files, or reaching the network from it
The glue between toolsGenerated orchestration code, via code modeGenerated code touching anything but registered tools. Still runs in the worker process
The agent processThe agent loop, its LLM credentials, its message historyThe agent's own credentials leaking. Not available today
The whole taskThe complete Airflow task, as KubernetesExecutor doesEverything, including Airflow's worker context

This is the first row, and the docs say so plainly. Airflow puts none of its context, connections, variables or worker environment into the sandbox. It does not contain the agent: the agent loop and every other toolset on the same agent still run in the worker with the worker's credentials, so an agent holding SandboxToolset alongside a toolset that can reach connections has a contained code tool sitting next to an uncontained credential path.

Design decisions

Four tools rather than one. An earlier revision exposed a single run_python_in_sandbox, which let an agent run code but never get a result back out. Files are how work leaves a sandbox, so the file tools are not a convenience.

A backend contract whose method names match the tools.SandboxBackend is create / run_command / read_file / write_file / list_directory / destroy. Four of the six are named after the tool they serve, so the mapping from a model-facing tool to the call behind it is literal.

A spec a backend must honour or refuse.SandboxSpec carries environment and network policy, and defaults to no environment and no egress. A backend that cannot enforce a field it was given raises instead of quietly provisioning something weaker, so a DAG author can never believe a restriction is in force when it is not. SbxSandboxBackend refuses a per-sandbox egress allowlist outright, because Docker Sandboxes governs egress with a host-level policy, and refuses block_network=True unless the Deployment Manager declares that policy through host_network_policy.

A toolset, not a capability. pydantic-ai models its own sandboxes as capabilities, but Airflow already has a composition unit in AgentOperator(toolsets=...), and durable execution only wraps toolsets it can reach. A capability that builds its own tools internally gets no replay caching, so shipping this as a capability would silently break durable=True.

run_command is marked as a code surface. Its ToolDefinition carries code_arg_name metadata. Without it, code mode folds the tool into run_code and the model ends up writing orchestration that runs in the worker process and passes a second program to the sandbox as a string literal, which is the opposite of what the toolset is for. With it, run_command stays a normal tool beside run_code while the three file tools fold in, where they are more useful as callables.

Usage

fromairflow.providers.common.ai.operators.agentimportAgentOperatorfromairflow.providers.common.ai.sandboximportSbxSandboxBackendfromairflow.providers.common.ai.toolsetsimportSandboxToolsetAgentOperator(
task_id="sandboxed_analyst",
prompt="Estimate pi with a Monte Carlo simulation of one million points.",
llm_conn_id="pydanticai_default",
toolsets=[SandboxToolset(SbxSandboxBackend(host_network_policy="deny-all"))],
)

Two sandboxes on one agent need tool_prefix, since tool names must be unique:

toolsets= [
SandboxToolset(SbxSandboxBackend(host_network_policy="deny-all"), tool_prefix="py"),
SandboxToolset(AcmeSandboxBackend(), tool_prefix="hosted"),
]

Tradeoffs and limitations

The sbx backend is for local development, not production. Docker Sandboxes is built for running coding agents against a checkout on your own machine: sbx create takes an agent name and bind-mounts host paths, and its stock network profile allows "typical development traffic". A production worker would need the binary on the host, an authenticated Docker account, a one-time sbx policy init, and on Linux, KVM or nested virtualisation, which an unprivileged container cannot provide. The docs lead with this. Verified end to end against sbx 0.38.0, covering the file round trip, environment injection, non-zero exits, output bounding, timeout enforcement and teardown.

No hosted backend ships yet, so there is no production story on Kubernetes until one lands. SandboxBackend is the extension point; a vendor adapter is roughly the six methods above.

sbx reclaims nothing automatically. There is no server-side TTL, so a worker killed outright leaves the microVM and its workspace directory behind. Sandboxes are named airflow-sandbox-* so an operator can find and remove them.

SandboxSpec.allow_egress_to is defined but unenforceable by the only backend here. It is part of the vendor-neutral contract; SbxSandboxBackend raises rather than accepting it.

Durable execution and HITL regeneration are not supported. The sandbox is destroyed when a run ends, so a replayed write_file result would describe a file in a sandbox that no longer exists.

Scope

This is the toolset and its contract only. The islo backend that appeared in earlier revisions is dropped from this PR so the abstraction can be reviewed on its own merits and so the diff carries no vendor SDK, extra, or uv.lock change. It is expected as a follow-up owned by the islo maintainers.

Not in scope: moving AgentOperator, the LLM calls, or the Airflow task off the worker; releasing the worker slot while sandbox code runs; and any executor SPI. Those are different boundaries, tracked separately.

Related work

@boring-cyborg

Copy link
Copy Markdown

Congratulations on your first Pull Request and welcome to the Apache Airflow community! If you have any issues or are unsure about any anything please check our Contributors' Guide
Here are some useful points:

  • Pay attention to the quality of your code (ruff, mypy and type annotations). Our prek-hooks will help you with that.
  • In case of a new feature add useful documentation (in docstrings or in docs/ directory). Adding a new operator? Check this short guide Consider adding an example Dag that shows how users should use it.
  • Consider using Breeze environment for testing locally, it's a heavy docker but it ships with a working Airflow and a lot of integrations.
  • Be patient and persistent. It might take some time to get a review or get the final approval from Committers.
  • Please follow ASF Code of Conduct for all communication including (but not limited to) comments on Pull Requests, Mailing list and Slack.
  • Be sure to read the Airflow Coding style.
  • Always keep your Pull Requests rebased, otherwise your build might fail due to changes not related to your commits.
    Apache Airflow is a community-driven project and together we are making it better 🚀.
    In case of doubts contact the developers at:
    Mailing List: dev@airflow.apache.org
    Slack: https://s.apache.org/airflow-slack

@kaxil

Copy link
Copy Markdown
Member

Converted to draft to ensure, this is discussed on the mailing list first. My first main concern (without looking into much details) is how would this integrate with Common AI Provider and why should it be separate provider instead of part of common.ai.

@potiuk

potiuk commented Jun 25, 2026

Copy link
Copy Markdown
Member

Agrees with @kaxil - we also need to know that we have real person behind who is comitted to maintain it - not only drop AI generatad code, so discussion on devlist is a minimum, ideally some good discussion about why this is a good idea - and explaining (in human words) some rationale behind it, and choice of the sandbox "providers" (which cannot be named providers due to obvious conflict) - ideally you should present your idea, how it is already used and what actual real-use problems it solves at our dev call.

@eladkaleladkal removed the backport-to-v3-3-test Backport to v3-3-test label Jun 25, 2026
@zozo123

Copy link
Copy Markdown
ContributorAuthor

Thanks @kaxil@potiuk — pivoted per your feedback.

It's no longer a separate provider: it's now a SandboxToolset inside common.ai (a normal pydantic-ai toolset, like SQLToolset/MCPToolset). It adds a run_code tool that runs the agent's Python in a real per-session microVM — off the worker — closing code_mode's gap, where the Monty run_code still executes in-process on the worker with full creds. The small create/run/poll/destroy backend contract lives in common.ai too, and is deliberately not named "providers".

Not single-vendor: the same per-session-microVM primitive now exists in AWS Lambda microVMs (Firecracker, shipped 2026-06-22 for "user or AI-generated code"), Daytona, E2B, Modal, and islo. The local backend needs no credentials.

On maintenance: I'll own it — I'm at Incredibuild and islo (islo.dev) is our product, so there's a committed maintainer and real usage behind it, not a drive-by AI PR. The reference backend I commit to is islo.

It already works end to end: a real agent's run_code executing in a live islo microVM, including through AgentOperator in airflow dags test. Happy to walk through the rationale and demo it live at a dev call — just point me at a slot.

@eladkal

eladkal commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

On maintenance: I'll own it — I'm at Incredibuild and islo (islo.dev) is our product, so there's a committed maintainer and real usage behind it, not a drive-by AI PR. The reference backend I commit to is islo.

You will need to elaborate on that. Since you are new to the community we need to make sure there is a long term promise/ownership. Check out guidelines for adding new providers
https://github.com/apache/airflow/blob/main/providers/ACCEPTING_PROVIDERS.rst#approval-process

@zozo123

Copy link
Copy Markdown
ContributorAuthor

Thanks @eladkal, that's a fair bar and I appreciate you spelling it out.

Two named maintainers at Incredibuild will own this long term: myself (@zozo123, yossi.eliaz@incredibuild.com) and Yuval Raz (yuval.r@incredibuild.com). support@incredibuild.com is our team alias, also committed and monitored by the islo team for issues and escalations. Incredibuild builds islo (islo.dev), a commercial product, so we have a direct reason to keep this alive. We'll land the first commit and maintain both the SandboxToolset framework and the islo backend.

The backend interface is pluggable so any vendor can own its own. We plan to invite E2B, AWS (Lambda microVMs), Daytona, and Tensorlake; whether they pick it up is on them. We're not asking the community to carry single-vendor code.

We'll answer issues and PRs, and meet the incubation and governance commitments in ACCEPTING_PROVIDERS.

One scope note: this is a SandboxToolset added to common.ai, not a new top-level provider.

I'm new here, so who's the right committer to approach as a sponsor? I'll start the [DISCUSS] thread next, and I'm happy to demo a live islo run at a dev call.

@zozo123
zozo123 marked this pull request as ready for review June 29, 2026 11:13
@eladkal

Copy link
Copy Markdown
Contributor

I'm new here, so who's the right committer to approach as a sponsor? I'll start the [DISCUSS] thread next, and I'm happy to demo a live islo run at a dev call.

Should there be a need for this new provider I am happy to be the sponsor but lets take it one step at a time.

The item in #68847 (comment) must be answered first.

@zozo123zozo123 changed the title Add Sandbox provider with SandboxExecutor, SandboxOperator and @task.sandboxAdd Sandbox provider with SandboxExecutor, SandboxOperatorJul 12, 2026
Comment threadairflow-core/src/airflow/executors/base_executor.py Outdated
@zozo123
zozo123force-pushed the add-sandbox-provider branch 2 times, most recently from 79b43fc to 196317bCompareAugust 10, 2026 09:43
@zozo123

Copy link
Copy Markdown
ContributorAuthor

@kaxil Done, scope is SandboxToolset only now. New head 196317bbd is 20 files, down from 104, all under providers/common/ai/ except docs/spelling_wordlist.txt and uv.lock. No airflow-core changes at all, so base_executor.py is untouched. Executor and operator work is out of scope here and tracked in #68845 and #69862. CI has no failures, with one Finalize tests / Deps job still running. Ready for another look when you have time.


Drafted-by: Claude Code (Opus 5); reviewed by @zozo123 before posting

@gopidesupavan

Copy link
Copy Markdown
Member

@zozo123 can you point me the github page for islo? i see the sdk is auto generated as per https://github.com/islo-labs/python-sdk i wanted to understand this more

@zozo123

zozo123 commented Aug 10, 2026

Copy link
Copy Markdown
ContributorAuthor

@gopidesupavan

https://docs.islo.dev/getting-started/quick-start

Let me know if this addresses the question.

@zozo123
zozo123 requested a review from kaxilAugust 11, 2026 14:39
Give common.ai agents a run_code tool that executes model-generated Python
off the Airflow worker, in a disposable per-run microVM, closing the
isolation gap in code mode (whose run_code runs in-process on the worker
with full credentials). A vendor-neutral SandboxBackend contract ships two
backends: Docker Sandboxes via the sbx CLI (local microVM) and islo (hosted
microVM). Airflow injects none of its context, connections, or worker
environment into the sandbox; the islo backend keeps outbound network off
by default.
Give each agent run its own sandbox so concurrent runs cannot share or tear
down each other's; stream sandbox command output with a hard cap so generated
code cannot exhaust worker memory; name the tool run_python_in_sandbox so it
coexists with the run_code meta-tool that Monty code_mode reserves; report a
run as timed out only on GNU timeout's unambiguous exit 124; clean up an
orphaned microVM and workspace when provisioning fails; cap islo output like
the sbx backend and keep timeout-driven teardown best-effort; and state the
islo timeout honestly - it is enforced by the backend's poll deadline, not by
the islo API.
The toolsets page is not a supported provider how-to guide path, which prevents static provider validation from succeeding.
Review feedback: GNU timeout exits 137 instead of 124 when generated code
ignores SIGTERM and --kill-after escalates, so a stubborn timeout was
reported as timed_out=False and indistinguishable from an OOM kill. Exit
137 now counts as a timeout when the call also outlived its budget.
The passthrough args validator let a tool call without a string "code"
reach call_tool and fail the task with KeyError; a real schema validator
now turns malformed calls into retryable validation errors so max_retries
works as intended.
The provider.yaml completeness check requires every module under toolsets/ to be listed; the new toolset registry category (apache#70122) landed after this branch was created.
Verify the code-execution boundary with a deterministic agent run and clarify that boundary size alone does not define security.
@kaxil
kaxilforce-pushed the add-sandbox-provider branch from 196317b to e9e1899CompareAugust 13, 2026 09:50
@kaxilkaxil changed the title Add SandboxToolset for isolated agent code execution in common.aiAdd SandboxToolset for sandboxed agent shell and file accessAug 13, 2026
@kaxil
kaxilforce-pushed the add-sandbox-provider branch 3 times, most recently from 0cccfef to 553829fCompareAugust 13, 2026 12:59
…ntract
The toolset exposed one run_python_in_sandbox tool. pydantic-ai's own sandbox
capabilities, and the vendors writing adapters for them, have converged on
run_command plus read_file, write_file and list_directory, so an agent could
run code here but never get a result out, and a vendor had two shapes to
implement instead of one.
The backend contract now names its four operation methods after those four
tools, and create() takes a SandboxSpec describing environment and network
policy. A backend that cannot enforce a field it was given raises rather than
provisioning something weaker than the Dag author asked for, so a spec can no
longer imply a restriction that is not in force.
run_command carries code_arg_name metadata. Without it, code mode folded the
sandbox tool into run_code and the model wrote orchestration that ran in the
worker process and passed a second program to the sandbox as a string literal,
which is what the toolset exists to avoid.
@kaxil
kaxilforce-pushed the add-sandbox-provider branch from 553829f to f75a3a2CompareAugust 13, 2026 13:32

@kaxilkaxil left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Merging the base

@kaxil
kaxil merged commit abdf3f8 into apache:mainAug 13, 2026
84 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@zozo123@kaxil@potiuk@eladkal@gopidesupavan