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

# Agent scripts

> The scripts an agent writes during a run can import @arg/sdk: in run_code, in run_bash and under arg exec, bound to the run's identity, workspace and permissions.

An agent working in a workspace can write JavaScript and run it. Those scripts import `@arg/sdk`, while custom apps use the host-provided `@arg-ai/sdk`. The run binds every call to the turn's identity, workspace, read-only state and switches before the script starts. Nothing in the script chooses those, and no long-lived credential is ever inside it.

## `run_code`

`run_code` executes a JavaScript body in an isolate with no network of its own. Static imports from `@arg/sdk` and its subpaths are hoisted out of the body; SDK calls cross the run's dispatcher, which enforces the turn's gates.

```ts theme={null}
import { actions, auth, chat, fs } from "@arg/sdk";

const principal = await auth.principal();
const notes = await fs.readJSON("notes.json");
const catalog = await actions.list({ query: "summarize" });
return { principal, notes: notes.length, actions: catalog.length };
```

Rules the isolate enforces:

* Only `@arg/sdk` may be imported; any other static import is refused with a message the agent can act on, wherever the declaration sits in the body.
* Each execution gets a fresh binding; cached source can never inherit another turn's authority.
* A tool-restricted subagent cannot use the SDK at all, and one script may make at most 100 SDK calls.
* `fs.open`, `host`, `db`, `ui` and `integrations.proxy` need a host the isolate does not have and report `unsupported_capability`. `fs.watch` polls metadata while the script runs.

## `run_bash` and `arg exec`

The sandbox image that runs shell commands carries the `@arg/sdk` package at its root, so a `.mjs` file under `/ws` imports it without installing anything:

```js title="/ws/scripts/copy-notes.mjs" theme={null}
import { actions, fs } from "@arg/sdk";

const text = await fs.read("notes.md");
await fs.write("notes-copy.md", text);
await actions.run("file_read", { path: "notes-copy.md" });
```

```bash title="run_bash" theme={null}
node /ws/scripts/copy-notes.mjs
```

Every shell command receives a freshly minted capability, scoped to the run's identity, workspace and read/write gate and expiring within 15 minutes; the SDK picks it up from the injected environment. The account's own tokens and API keys stay in Arg. Command output and server logs redact the injected values, and a script must never print, persist, copy or transmit the `ARG_*` variables it finds. Plain shell tools still read and write `/ws` directly; reach for the SDK when the script also needs Actions, chats, workspace discovery or the normalised API contracts.

## What a script may start

A script's capability follows the turn:

| The turn is                      | The script may                                                                   |
| -------------------------------- | -------------------------------------------------------------------------------- |
| Read-only                        | Read files, discover Actions, list chats; every write and every run is refused   |
| Writable, under an approval mode | Everything above plus writes and read-scoped Actions                             |
| Writable, under full access      | Also `chat.start` / `chat.continue` / `chat.create` / `chat.send` and `code.run` |

The last row is deliberate. A card that approves one command does not approve the chat turns or shell runs a script might start with a token that outlives that command, so those calls are refused with `permission_denied` unless the user already lets the agent act without a card. The Code and Servers switches on the turn also gate `code.run` and the `servers` namespace from a script, exactly as they gate the agent's own tools.

## Reading the results

Scripts see the same receipts as any other client: an Action's `status`, a chat send's `call_id`, a `code.run`'s `stdout` and `status`. Inspect them; a refused call is an ordinary rejection with an error `code` from the [reference](/guides/sdk/reference#error-codes), and the agent should report it rather than retry an accepted write.
