Placement — where does my agent run?

One vocabulary for where the whole workflow, an agent's tools, and its explicit code calls run

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.

python
from praisonaiagents import Agent, AgentFlow

writer = Agent(name="writer", instructions="Write /workspace/data.txt")
reader = Agent(name="reader", instructions="Read /workspace/data.txt")

# Every step's tools share one Docker sandbox; thinking stays here
flow = AgentFlow(run_on="docker", steps=[writer, reader])

print(flow.where_does_it_run())
Diagram

run_on=

compute=

sandbox=

thinking stays

thinking stays

🔀 AgentFlow / AgentTeam

📦 One shared sandbox

🤖 LocalAgent

☁️ Compute provider

🤖 Agent

⚙️ execute_code sandbox

💻 This machine

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.

    python
    from praisonaiagents import Agent, AgentFlow
    
    writer = Agent(name="writer", instructions="Write /workspace/data.txt")
    reader = Agent(name="reader", instructions="Read /workspace/data.txt")
    
    flow = AgentFlow(run_on="docker", steps=[writer, reader])
  2. Move one agent's tools

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

    python
    from praisonai import LocalAgent
    
    agent = LocalAgent(compute="docker")
  3. Isolate explicit code calls

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

    python
    from praisonaiagents import Agent
    
    agent = Agent(name="runner", instructions="Run code", sandbox=True)
    agent.execute_code_sync("print(6*7)")

How It Works

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

Diagram
SandboxLLMFlowUserSandboxLLMFlowUserflow.run(request)think (on this machine)call a toolrun shell / file tool (run_on=)resultresponse
ParameterWhat it movesWhat it acceptsWhere it lives
run_on=every step’s tools in a flow or team, into ONE shared sandboxcompute providers (see below)AgentFlow, AgentTeam, YAML top-level run_on:
compute=one agent’s tools; thinking stays localcompute providersLocalAgent, LocalManagedAgent
sandbox=the explicit agent.execute_code() callsTrue / SandboxConfigAgent(...)

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.

Diagram

The whole agent loop

Every step in a flow/team

One agent’s tools

Explicit execute_code calls

What do you want
to move?

run_on=
any hosted place

anthropic · docker · e2b · modal
daytona · flyio · tenki · novita

run_on=
shared sandbox

compute=
compute provider

sandbox=
SandboxConfig


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=.

ProviderDescriptionrun_on= hosts the loop
anthropicVendor-hosted managed runtime✅
dockerA Docker container✅
e2bAn E2B cloud sandbox✅
modalA Modal cloud sandbox✅
daytonaA Daytona cloud sandbox✅
flyioA Fly.io machine✅
tenkiA Tenki cloud microVM✅
novitaA Novita cloud sandbox✅

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:

PlaceWhy run_on= refuses it
localWould just run in your own shell — same as passing nothing
sshNeeds a host object (SSHSandbox(host=…)), not a bare name
subprocess / sandlockIsolate tools; aren’t 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:

ProviderDescription
subprocessA separate process on this machine (scrubbed env; not a security boundary)
sandlockA locked-down process on this machine (Landlock + seccomp-bpf)
nativeAlias — resolves to sandlock (accepted at construction since PR #4070)
sshA remote machine over SSH

The explicit Agent(sandbox=…) API accepts additional local sandbox backends — subprocess, sandlock, docker — through SandboxConfig. See Sandbox Backends.


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.

toml
# pyproject.toml — in the plugin package
[project.entry-points."praisonai.compute"]
runpod = "praisonai_compute_runpod:RunpodCompute"
python
from praisonaiagents import Agent

# 'runpod' resolves through the entry-point registry
agent = Agent(name="runner", instructions="Run code", tools_run_on="runpod")
BehaviourWhat happens
DiscoveryProviders load on demand from the praisonai.compute entry-point group — a broken plugin fails only for the caller that names it.
Friendly phraseA provider that sets display_name = "a Runpod cloud sandbox" on its class uses that phrase in where_does_it_run() and repr(agent) instead of the bare name.
managed psDerives its list from the registry, so contributed places are listed and stopped automatically — no extra wiring.

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).

python
from praisonaiagents import Agent, AgentFlow

writer = Agent(name="writer", instructions="Write")
reader = Agent(name="reader", instructions="Read")

flow = AgentFlow(run_on="docker", steps=[writer, reader])
print(flow.where_does_it_run())
text
Thinking (the AI model calls) happens on this machine.
Tools run on a Docker container (shared).
Every step shares that same sandbox, so a file written by one step is visible to the next.

A bare local agent reports that nothing is isolated:

python
print(Agent(name="builder", instructions="Build").where_does_it_run())
text
Thinking (the AI model calls) happens on this machine.
Tools run on this machine.
Nothing is isolated: tools you pass run in this program, with your permissions.

Migration

Placement parameters at a glance
ScopeParameterWhere
Whole flow / team → one shared sandboxrun_on="docker"AgentFlow, AgentTeam, YAML run_on:
One agent’s tools → a compute providercompute="docker"LocalAgent, LocalManagedAgent
Explicit execute_code() callssandbox=True / sandbox=SandboxConfig(...)Agent

YAML run_on: maps directly to the Python AgentFlow(run_on=…) kwarg — same name, same providers, validated at load time.

ExecutionConfig.code_sandbox_mode is inert

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

Use sandlock for a real local boundary

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()).

Share one sandbox with run_on= when steps hand off files

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.

sandbox= isolates only explicit execute_code() calls

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").


Related