> ## Documentation Index
> Fetch the complete documentation index at: https://praison.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Placement

> Where an Agent runs: backend, runOn, and toolsRunOn.

Placement answers one question — *where does this run?* — along two axes: the whole agent (`runOn` / `backend`) or only its tools (`toolsRunOn`).

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph TD
    A[🤖 Agent] --> Q{Where?}
    Q -->|runOn / backend| W[☁️ managed runtime runs everything]
    Q -->|toolsRunOn| T[🔧 tools remote, loop stays local]
    Q -->|nothing| L[💻 local]

    classDef agent fill:#8B0000,stroke:#7C90A0,color:#fff
    classDef q fill:#189AB4,stroke:#7C90A0,color:#fff
    classDef place fill:#189AB4,stroke:#7C90A0,color:#fff

    class A agent
    class Q q
    class W,T,L place
```

## Quick Start

**1. A custom backend runs the whole agent**

```typescript theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
import { Agent } from 'praisonai';

const agent = new Agent({
  instructions: 'Hosted agent',
  backend: {
    async execute(prompt) {
      return `handled remotely: ${prompt}`;
    },
  },
});
await agent.chat('Do the work');
```

**2. Only the tools run elsewhere**

```typescript theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
import { Agent } from 'praisonai';

// Both 'local' and 'docker' are registered automatically on import.
const agent = new Agent({ instructions: 'Local loop', toolsRunOn: 'local' });
```

**3. Run a tool inside Docker**

```typescript theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
import { Agent, ComputeToolPlace, DockerCompute } from 'praisonai';

// A place carries a command template per tool. 'greet' knows how to run itself.
const place = new ComputeToolPlace(new DockerCompute(), {
  greet: 'echo hello-{{who}}',
});

const agent = new Agent({ instructions: 'Greeter', toolsRunOn: place });
// When the model calls greet({ who: 'world' }), the shell command runs inside
// Docker and its stdout comes back as the tool result. setCommand(name, template)
// declares a tool's command after construction.
```

***

## The two axes

| Option | Type | Meaning |
| - | - | - |
| `runOn` | `string \| ManagedBackendLike` | Names a **managed runtime** that hosts the whole agent — model calls, loop and tools. `runOn: 'x'` is shorthand for `backend: <hosted agent for x>`. |
| `backend` | `ManagedBackendLike` | A custom object exposing `execute(prompt, options)` (and optional `stream()`, `managedSessionId`). |
| `toolsRunOn` | `string \| ToolPlaceLike` | Names a **tool place** — the tools run there, thinking stays local. |

`ManagedBackendLike` and `ToolPlaceLike` are exported interfaces. The default place, `LOCAL_TOOL_PLACE`, runs a tool unchanged in this process.

## Exclusion rules

The two axes are disjoint, so naming two places for the same thing is a typo worth catching. Any of these throws at construction:

> [!WARNING]
>
> ```text theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
> TypeError: Agent(runOn, backend) sets the agent's runtime twice. ...
> TypeError: Agent(runOn, toolsRunOn) points the tools at two machines. ...
> TypeError: Agent(backend, toolsRunOn) points the tools at two machines: ...
>
> # a name used on the wrong axis
> TypeError: Agent(runOn: 'X') is not a known managed runtime. ...
> TypeError: Agent(runOn: 'X') is not valid: runOn places the whole agent ... use toolsRunOn
> TypeError: Agent(toolsRunOn: 'X') is not valid: 'X' hosts an entire agent, not individual tools.
> TypeError: Agent(toolsRunOn: 'X') is not a known place. ...
>
> # a backend without execute()
> TypeError: Agent(backend) does not support execute(): a managed backend must expose execute(prompt, options) returning the response.
> ```

Before this change a mis-typed name survived construction and only surfaced on the first model turn — after the prompt was built and, for a real run, after the API spend. Validation now happens at the call site.

## Registering hosts

A hosted runtime and a remote tool place are provider integrations that do not ship in the package. A host fills the registries:

```typescript theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
import { registerManagedRuntime, registerToolPlace } from 'praisonai';

registerManagedRuntime('my-runtime', () => ({
  async execute(prompt) { return `ran ${String(prompt)}`; },
}));

registerToolPlace('my-sandbox', () => ({
  placeName: 'my-sandbox',
  async runTool(_name, _args, run) { return run(); },
}));
```

Once registered, `runOn: 'my-runtime'` and `toolsRunOn: 'my-sandbox'` resolve by name. `unregisterManagedRuntime` / `unregisterToolPlace` undo a registration (`local` cannot be removed).

`registerComputeProvider('e2b', …)` now **also** registers a matching placement entry, so `new Agent({ toolsRunOn: 'e2b' })` works after a single call — a caller no longer has to remember two registrations. The two move together.

## Where a tool call goes

A tool runs on the provider only when it declares a `command` template; a tool without one runs locally and says so, rather than implying isolation it does not have.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph TB
    T[🔧 Tool call] --> H{Has command<br/>template?}
    H -->|No| L[💻 Local fallback<br/>says so]
    H -->|Yes| A{Provider<br/>available?}
    A -->|No| R1[❌ Raise ComputeError]
    A -->|Yes| E[▶️ Run in provider]
    E --> O{Result}
    O -->|exit 0| S[✅ stdout returned]
    O -->|non-zero exit| R2[❌ Raise ComputeError]
    O -->|timeout| R3[❌ Raise ComputeError]

    classDef call fill:#8B0000,stroke:#7C90A0,color:#fff
    classDef check fill:#F59E0B,stroke:#7C90A0,color:#fff
    classDef local fill:#6366F1,stroke:#7C90A0,color:#fff
    classDef run fill:#189AB4,stroke:#7C90A0,color:#fff
    classDef ok fill:#10B981,stroke:#7C90A0,color:#fff
    classDef err fill:#8B0000,stroke:#7C90A0,color:#fff

    class T call
    class H,A,O check
    class L local
    class E run
    class S ok
    class R1,R2,R3 err
```

The shorthand `toolsRunOn: 'docker'` builds a `ComputeToolPlace` for you, but with no command templates — so every tool falls through to its local implementation. That fallback **warns via `console.warn`**, once per tool name on each place (a hot loop can't spam it), so a caller who asked for a real sandbox (`'docker'`, `'e2b'`, …) gets a visible signal instead of a silent downgrade. A `'local'` place skips the warning — running on the host **is** what `'local'` means, so a warning there would be noise:

```text theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
[praisonai] tool '<toolName>' has no command mapping for place '<placeName>',
so it runs on the HOST, not in the sandbox.
Give it a shell command (place.setCommand('<toolName>', '...')) to run it there.
```

To route a specific tool remotely, build a `ComputeToolPlace` with the `command` template as in the Quick Start (or call `setCommand(name, template)`).

## Compute providers behind `toolsRunOn`

`toolsRunOn` is powered by the compute-provider registry. `praisonai-ts` ships two providers — `'local'` and `'docker'` — via `LocalCompute` and `DockerCompute`.

```typescript theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
import { listComputeProviders, registerComputeProvider } from 'praisonai';

listComputeProviders();                        // ['docker', 'local']
registerComputeProvider('my-remote', () => new MyRemoteCompute());
```

`listComputeProviders()` returns what's available in this build; `registerComputeProvider(name, factory)` adds a remote one without touching the SDK. The `'local'` and `'docker'` **tool places** resolve automatically on first use via `ensureBuiltinToolPlaces()` — no import step is needed at all for those names to work. `new Agent({ toolsRunOn: 'local' })` and `new Agent({ toolsRunOn: 'docker' })` both work out of the box; the placement registry's `toolPlaceNames()` returns `['docker', 'local']` in a stock build. An unknown name (for example `'e2b'`) still raises rather than silently running on the host. See [Compute Providers (TypeScript)](https://praison.ai/docs/features/js-compute-providers.md).

## Reading the resolved placement

```typescript theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
import { Agent } from 'praisonai';

const agent = new Agent({ instructions: 'x', toolsRunOn: 'local' });
console.log(agent.getManagedBackend()); // undefined
console.log(agent.getToolPlace());      // the 'local' place
```

## Team-wide `toolsRunOn`

An `AgentTeam` can share one sandbox across every member — one `ToolPlace` instance for the whole team, not one per member.

```typescript theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
import { Agent, AgentTeam } from 'praisonai';

const a = new Agent({ instructions: 'Collect data' });
const b = new Agent({ instructions: 'Summarise it' });

const team = new AgentTeam({ agents: [a, b], toolsRunOn: 'local' });
a.getToolPlace() === b.getToolPlace();   // true — same instance
```

Every member that declared no `toolsRunOn` of its own adopts the team's place, so state a tool leaves persists between members' tool calls in the same sandbox. A member's own `toolsRunOn` wins — it keeps its place. An unknown place name throws at team construction.

```typescript theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
// Precedence — a member's own place wins
const own = new Agent({ instructions: 'Sandboxed', toolsRunOn: 'local' });
new AgentTeam({ agents: [own], toolsRunOn: 'docker' });  // own keeps 'local'

// Unknown place → throws at construction
new AgentTeam({ agents: [a, b], toolsRunOn: 'nowhere' }); // TypeError
```

The team's place also reaches an agent introduced on a **task** (`TeamTask.agent`) or built lazily from **`agentConfig`** — an agent that never appeared in the `agents` array at construction. It joins the same shared sandbox on first use.

```typescript theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
import { Agent, AgentTeam } from 'praisonai';

const member = new Agent({ instructions: 'Collect data' });
const team   = new AgentTeam({ agents: [member], toolsRunOn: 'local' });

// A second agent that lives on a task — never in the team's agents array
const taskOnly = new Agent({ instructions: 'Also runs' });
team.addTask({ agent: taskOnly, prompt: 'do it' });

// On first use, taskOnly joins the SAME sandbox as member
taskOnly.getToolPlace() === member.getToolPlace();   // true
```

Under the hood, the team calls `Agent.adoptToolPlace(place)` on each member. It returns `false` when the agent already has its own place (its own `toolsRunOn` or one adopted earlier — a safe no-op), and `true` when adopted. The call is idempotent, so a member that already has a place is never overridden. See [AgentTeam](https://praison.ai/docs/js/agent-team.md) for the full team-level option list.

## Related

> [card] [Runtime](https://praison.ai/docs/js/runtime.md)
>
> Delegate the whole turn

> [card] [Sandbox](https://praison.ai/docs/js/sandbox.md)
>
> Where code runs

> [card] [Compute Providers (TS)](https://praison.ai/docs/features/js-compute-providers.md)
>
> LocalCompute, DockerCompute, and the registry


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.