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

# Scripts

> Run a Playwright script on a node so a proven path repeats exactly, in milliseconds, for no model cost.

An agent node calls an AI model at each step to decide what to do. The model reads the page and picks each action from your instructions. That is what you want on a page that changes. It is waste on a page that never changes. In that case, you should convert the node into a script.

Give the node a **Script** and it runs a Playwright script first, before any model call. The same clicks happen every execution, in milliseconds, for no model cost.

<Card title="Let Astro script it" icon="wand-sparkles" href="/build/in-the-platform" horizontal>Ask Astro which nodes are stable enough to script. It writes the script from what the workflow already did, attaches it to the node, and runs it.</Card>

***

## Pick the right node

Script a node when all three hold.

* The page has been stable for months.
* The workflow takes the same actions every execution.
* You can name the selectors, or Astro can read them from a past execution.

Leave a node turn by turn in three cases. The page changes. The path branches on what the workflow finds. Or the step is new, and you are still learning the site.

***

## Add a script

Open the node's **Instructions** tab. The **Script** field sits at the top. Select a `.js` file from the node's [shared directory](/concepts/filesystem). The node becomes a scripted node, and an "If the script fails" picker appears beside the field. Remove the script and the node goes back to running turn by turn.

Astro does the same thing from the chat. Ask it to script a node and it writes the file, sets the field, and picks a failure action.

[Nodes](/concepts/nodes) covers where a script file lives and how the runtime resolves its path.

***

## What happens when the script runs

The script runs against the live browser session — the same browser the model would have used. Then one of three things happens.

| Condition                                                             | What the runtime does                                       |
| --------------------------------------------------------------------- | ----------------------------------------------------------- |
| A selector transition matches right after the script                  | Takes that transition. No model call.                       |
| The script succeeded and the node has exactly one outbound transition | Takes it. No model call.                                    |
| Anything else                                                         | Calls the model, with the script's output added as context. |

The first two paths are the fast ones. A scripted node with one outbound transition skips the model whenever the script succeeds.

***

## What the script returns

| Return value | Effect                                                              |
| ------------ | ------------------------------------------------------------------- |
| An object    | Each top-level key becomes an output variable for downstream nodes. |
| A string     | Wrapped under a single `script_output` variable.                    |
| A throw      | Treated as a failure.                                               |

***

## What happens when the script fails

The picker beside the Script field has two settings.

<CardGroup cols={2}>
  <Card title="Fall back to AI" icon="sparkles" horizontal>The failure context joins the model's turn. The workflow recovers and finishes the task from the instructions.</Card>
  <Card title="Cancel" icon="ban" horizontal>The execution cancels at once with the reason `script_failed`. No model call. This also fires when the script file is missing.</Card>
</CardGroup>

Choose **Fall back to AI** while a script is new. Choose **Cancel** for a step where a wrong action costs money — a payment, a submission, a deletion.

***

## Pass data into a script

Data that changes per execution reaches the script as `args`. Declare an input schema on the node, then read each input by name.

```javascript theme={null}
/**
 * @param {object} args
 * @param {string} args.patient_name - Patient to file the note against.
 * @param {string} args.date_of_birth - Date of birth, as shown in the EHR.
 */
module.exports = async ({ page, args }) => {
  await page.fill('#username', '##USERNAME##');
  await page.fill('#password', '##PASSWORD##');
  await page.click('button[type="submit"]');

  await page.fill('#patient-name', args.patient_name);
  await page.fill('#dob', args.date_of_birth);
  await page.click('#save');

  return { saved: true };
};
```

A scripted node that declares an input schema must use the `async ({ page, args }) => { ... }` signature. The plain `async (page) => { ... }` form fails validation.

Tell Astro which values change per execution and it declares the schema and wires the `args` for you.

<Tip>
  Write a JSDoc `@param` block for every argument. The workflow sees that JSDoc when a script fails and the node falls back. It is the only description of the arguments the workflow gets.
</Tip>

The values themselves come from the [inputs](/concepts/inputs-and-outputs) your code sends on the execute call.

***

## Credentials in a script

`##CREDENTIAL##` tokens are replaced at the tool boundary, straight from the credential store. They never pass through the model.

```javascript theme={null}
await page.fill('#username', '##USERNAME##');
await page.fill('#password', '##PASSWORD##');
```

Store the values on the [Agent profile](/concepts/profiles).

***

## Next

<CardGroup cols={2}>
  <Card title="Improve your workflows" icon="trending-up" href="/operate/improve" horizontal>The rest of the ways to make a workflow faster</Card>
  <Card title="Nodes" icon="box" href="/concepts/nodes" horizontal>Where a script lives, and the rest of a node's settings</Card>
  <Card title="Agent profiles" icon="id-card" href="/concepts/profiles" horizontal>Store the credentials a script fills in</Card>
  <Card title="Test, iterate, publish" icon="check-check" href="/build/test-and-publish" horizontal>Ship the scripted node</Card>
</CardGroup>
