Skip to main content
Placement decides where work happens: a whole flow, one agent’s tools, and its explicit code calls each answer a different question with a different parameter.

Quick Start

1

Share one sandbox across a flow or team

Thinking stays on this machine; every step’s shell, file and code tools run in one shared Docker sandbox.
2

Move one agent's tools

Give a single LocalAgent its own remote compute provider — thinking stays local, tools run on the provider.
3

Isolate explicit code calls

Agent(sandbox=…) configures the explicit agent.execute_code() API. It does not add tools the model can call.

How It Works

Each parameter moves a different scope, so the choices never overlap.

Which parameter do I want?

The scope you want to move picks the parameter. On a single Agent, run_on= hands the whole loop to any hosted place — see the eight-place table below.
Behaviour-change note (PR #4071). run_on= on Agent, AgentFlow, and AgentTeam now accepts every hosted place — anthropic, docker, e2b, modal, daytona, flyio, tenki, novita — not just anthropic and docker. One generic backend wraps any compute provider, so each place can host the whole loop. local, ssh, subprocess, and sandlock are refused for run_on= (they isolate tools or need a host object; they are not runtimes for a loop). The Modal path (run_on="modal" and execute_code(run_in="modal")) is also fixed and verified end to end.The same widening is now covered by tests for the underlying HostedAgent(provider=...) factory as of PR #4074run_on="daytona" is exactly backend=HostedAgent(provider="daytona"), and both are now regression-tested end to end. See HostedAgent Compute Runtimes.PR #4077 completes the picture — docker now goes through the same generic backend as the rest, so praisonai managed ps / managed stop reclaim Docker containers reliably (previously they could be listed but not stopped). PR #4107 finishes the job by making DockerCompute inherit SyncComputeProvider — the previous extraction missed four sites (provision, shutdown, execute, list_instances) that still called run_in_executor directly and shared the same shutdown leak. See Reclaim Stray Sandboxes. PR #4109 closes two remaining leaks in the same story: the timed-out docker execute() container is now killed by name, and ComputeManagedAgent finally registers a weakref.finalize so run_on= instances are reclaimed when the backend is collected or the process exits. See the run_on= finalizer.As of PR #4092, the vendor compute providers live in the standalone praisonai-sandbox package (same names, same behaviour; old praisonai.integrations.compute.* imports still work through a shim).
docker legitimately appears under both run_on= and tools_run_on= — same place, two scopes, carried by the parameter. run_on="docker" runs the whole loop in the container; tools_run_on="docker" keeps the loop local and moves only the tools. See Self-Hosted Agent (Docker).docker on both sides uses the same default image (python:3.12-slim), so run_on="docker" and tools_run_on="docker" hand you the same Python runtime. Pass an explicit image= to override.
tools_run_on= also accepts the aliases the resolver understands (nativesandlock, and any spelling in _compute_bridge._ALIASES). Since PR #4070 these are validated at construction, so Agent(tools_run_on="native") no longer raises TypeError.

Where run_on= and compute= can point

run_on= and compute= accept the same eight compute providers. As of PR #4071, run_on= hosts the whole agent loop on each of them (previously only anthropic and docker), and all eight are valid for both run_on= and tools_run_on=. A typo is rejected at load / call time with the full list of valid names. run_on= refuses the places that isolate tools or need a host object — they are not runtimes for a loop: tools_run_on= reaches additional local backends and aliases the resolver understands — every alias in _compute_bridge._ALIASES is accepted at construction as of PR #4070: The explicit Agent(sandbox=…) API accepts additional local sandbox backends — subprocess, sandlock, docker — through SandboxConfig. See Sandbox Backends.
local / subprocess is not a security boundary. Its blocked-command list is bypassable by ordinary shell syntax (cat $(echo /etc/passwd) reads the file). For untrusted code, use docker, a cloud provider, or sandlock (via Agent(sandbox=…)).

Bringing your own compute place

A compute backend can ship in its own package and register under the praisonai.compute entry-point group — no change to the main repo. Once installed, its name works with run_on=, compute=, and tools_run_on= like a built-in.
See Compute Provider Plugins for the full protocol and a minimal example.

Inspect placement

where_does_it_run() prints the answer in plain English. It is available on Agent, AgentFlow and AgentTeam, and reports two fields — thinks_on (the model calls) and tools_run_on (the tools).
A bare local agent reports that nothing is isolated:
thinks_on and tools_run_on are output fields from where_does_it_run() — they describe where work lands. They are not constructor parameters. Set placement with run_on=, compute=, or sandbox=.
Behaviour-change note (PR #4070). repr(agent) and where_does_it_run() now report a managed backend’s LocalCompute as “a plain shell on this machine (no policy applied)” rather than “a separate process on this machine”. The runtime behaviour did not change — the previous string understated the shell’s freedom and overstated the sandbox’s protection. See Where It Runs.

Migration

YAML run_on: maps directly to the Python AgentFlow(run_on=…) kwarg — same name, same providers, validated at load time.
ExecutionConfig(code_sandbox_mode="sandbox") still exists with a "sandbox" default and survives to_dict() / from_dict(), but no execution path reads it today. It does not isolate anything on its own. To isolate code the model runs, use AgentFlow(run_on="docker") or Agent(sandbox=…). See Execution Systems.

Best Practices

subprocess separates a process but does not contain it. sandlock enforces Landlock + seccomp-bpf, the only kernel-level boundary that runs on this machine. Configure it through Agent(sandbox=SandboxConfig.native()).
run_on= on a flow or team provisions one sandbox for the whole run, so a file written by one step is visible to the next. Leave it unset for zero-overhead local execution.
Agent(sandbox=…) does not add a model-visible tool and does not protect the host execute_command path. For model-driven isolation, run the whole workflow in a real container with AgentFlow(run_on="docker").

Self-Hosted Agent (Docker)

run_on="docker" runs the whole agent in a container you own.

Shared Sandbox

Run every step’s tools in one shared sandbox.

Sandbox

Run tools and code in an isolated environment.

Sandbox Guarantees

What each boundary does and does not protect.