Boat (hosted)¶
Note
Experimental: this can change or be removed in a minor release of this provider. See Stable and experimental features.
BoatSandboxBackend runs each
sandbox on Boat, the hosted cloud-computer
API formerly called Ascii Box. The Airflow worker needs only network access and
an API key: no local daemon, Docker socket, KVM or nested virtualization, so it
runs from a containerized worker and on Kubernetes.
Install the SDK extra:
pip install "apache-airflow-providers-common-ai[boat]"
from airflow.providers.common.ai.sandbox import BoatSandboxBackend, SandboxSpec
from airflow.providers.common.ai.toolsets import SandboxToolset
SandboxToolset(
BoatSandboxBackend(),
spec=SandboxSpec(block_network=False),
)
Credentials can come from a generic Airflow connection, resolved lazily on first use:
SandboxToolset(
BoatSandboxBackend(boat_conn_id="boat_default"),
spec=SandboxSpec(block_network=False),
)
The connection password is the Boat API key and is required. host, when
set, is the full API base URL, such as https://boat.dev/api/v1, which is also
the default. It must start with https:// or http://: a bare domain such as
boat.dev, which is what Airflow’s connection URI form leaves in host, is
refused, since the API key would otherwise be sent to it over plain HTTP. With
boat_conn_id=None, the default, the backend reads BOAT_API_KEY (required)
and optional BOAT_BASE_URL from the worker environment instead, and
BOAT_BASE_URL must carry the scheme too.
Constructor parameters:
org: the Boat wallet each sandbox is billed to, by organization id or name, or"personal". Default"personal", which bills your own account whatever its active wallet is. See the warning below before naming an organization.machine_type:"small","default"or"large".ttl_seconds: server-side TTL after which Boat stops the sandbox, a whole number of seconds up to2592000(30 days). Default3600.ready_timeout: provisioning deadline in seconds, from the create request until the sandbox is ready. Default300.request_timeout: HTTP timeout in seconds for an API call that answers at once, such as the create request, a status check or a delete, and the time added to the operation’s own for a call that waits on one: a command’s deadline, or 120 seconds for a file write. Default30.
A create request that times out, fails in transport, or gets a server error may
still have started a sandbox, so it is sent again with the same idempotency key,
and Boat answers with the sandbox the first request created rather than starting
a second one. While Boat is still creating that sandbox, it answers the resent
request with 409 idempotency_in_progress, and the backend sends it again two
seconds after each such answer until ready_timeout runs out. Each resend gets
what is left of ready_timeout plus request_timeout, so provisioning takes
at most their sum, and a sandbox a resend finds too late to become ready in time
is deleted rather than left running until its TTL. If a resend fails any other
way, or Boat is still creating the sandbox when ready_timeout runs out, the
backend never receives the sandbox’s id, so any sandbox the first request started
keeps a server-assigned name and runs until Boat stops it at ttl_seconds.
Every sandbox is created with Boat’s noEnv flag, so it gets none of your Boat
account’s stored environment variables, secret files or credentials, and cannot
act on your account or your other sandboxes.
Warning
Boat gives every member of an organization use of a noEnv sandbox billed
to that organization: they can open it and run commands in it, so they can
read and write anything in it, including the SandboxSpec.env values each
command receives. Pass org only when everyone in that organization may see
what the agent works on.
SandboxSpec.env is passed when the sandbox is created and also exported at
the start of every command, so each value is sent again with every command
request. Boat cannot enforce a deny-all network policy, a per-domain egress
allowlist, or a CIDR egress allowlist, so the backend refuses
block_network=True, allow_egress_to, and allow_egress_to_cidrs
rather than silently provisioning something weaker than the spec asked for.
Since block_network defaults to True, that includes a bare
SandboxSpec(): pass SandboxSpec(block_network=False) to state that open
egress is acceptable, or use Modal when it is not.
Writes use Boat’s native file API, which accepts only paths that resolve under
/home/user (where relative paths land) or /tmp. The parent directory is
created first with mkdir -p in the guest, so a write_file anywhere else
fails with mkdir’s own error when the guest user cannot create the parent
directory, and with Boat’s invalid_path error when that directory exists or
could be created. Either way the model gets it back as a tool error it can
retry. Reads deliberately keep the inherited bounded shell implementation, so
max_bytes is enforced inside the guest before file contents reach worker
memory.
That path rule binds only write_file; it is not a jail. Commands run as the
guest user user, which Boat documents as
having root through sudo with no password, so a command the model writes can
read and write anywhere in the sandbox filesystem. The sandbox also has a public
IPv4 or IPv6 address, and its firewall is inside the guest: Boat’s
hosting guide opens a port to the internet
with ufw, which a command can do too. The sandbox boundary is what contains
that.
A command’s deadline is enforced inside the sandbox by GNU coreutils
timeout, which sends SIGTERM and then SIGKILL five seconds later, so
a command that runs out of time returns the tail of what it printed, and the
sandbox and its files survive. Boat’s own deadline is set 15 seconds past that
one, and Boat’s API caps it at 600 seconds, so any command that asks for more
than 585 seconds gets 585, and the result reports that deadline to the model.
Only when Boat’s own deadline ends the call, because the one in the guest did
not, is the sandbox torn down, as is a sandbox that never becomes ready. A file
read, listing, or write that Boat’s deadline ends that way fails the task, since
there is no sandbox left for the model to retry against. If the worker dies
first, the server-side TTL stops the sandbox. Sandboxes are created without
snapshots, so stopping one erases its disk rather than keeping it for an
operator to remove, and with monitoring off, so Boat keeps no history of the
commands, their output, or the environment variables exported into them.