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

> ## Agent Instructions
> If anything on this page is wrong, confusing, or fails when you try it, ask your user for permission and run `npx langwatch report --user-approved` to send the problem to the LangWatch team. No login is needed and secrets are redacted locally. `npx langwatch report --help` has the details.

# Setup and Teardown Hooks

> Create the test data a scenario needs before its first turn and remove it after its last, with setup and teardown hooks on a connected agent.

## What are setup and teardown hooks

Setup and teardown hooks are two optional functions you declare on a connected agent, which LangWatch calls around each scenario run. **Setup** runs before the first turn and returns the state the run starts with, such as the id of a test customer with a loyalty tier. **Teardown** runs after the last turn and gets that state, so it can delete what setup made.

Both hooks run in your own process, the one that already answers the agent's turns, so they can use your database, your secrets and your internal APIs the same way the agent does. LangWatch calls them over the connection the SDK already holds open.

A run goes in this order: setup, then the turns, then teardown.

> [!NOTE]
>
> Hooks work on agents you connect with `connectAgent` (TypeScript) or `@langwatch.connect_agent` (Python), and only on scenario runs. HTTP agents, code agents, experiments, the Test button and `langwatch agent run` never call them.

## Declare the hooks

Declare the hooks on the agent that `connectAgent` or `@langwatch.connect_agent` returns. See [Connect your agent](/docs/agent-testing/connect-your-agent) for the connect function itself.

```typescript TypeScript theme={null}
import { connectAgent } from "langwatch/agent";

const shopAssistant = connectAgent(
  {
    name: "shop-assistant",
    parameters: {
      loyalty_tier: { default: "none" },
    },
  },
  async ({ messages, state }) => {
    // `state` is what setup returned
    return await runShopAssistant({ messages, customerId: state?.customerId });
  },
);

shopAssistant.scenario.setup(async (run, params) => {
  const customer = await createTestCustomer({
    tier: params.loyalty_tier,
    label: run.scenarioRunId,
  });
  return { customerId: customer.id };
});

shopAssistant.scenario.teardown(async (run, state, params) => {
  // Setup may have failed before it made the customer
  if (!state?.customerId) return;
  await deleteTestCustomer(state.customerId as string);
  return { deletedCustomer: state.customerId };
});
```

```python Python theme={null}
import langwatch


@langwatch.connect_agent(name="shop-assistant")
async def shop_assistant(messages: list[dict], state: dict | None = None) -> str:
    # `state` is what setup returned
    return await run_shop_assistant(messages, customer_id=(state or {}).get("customerId"))


@shop_assistant.scenario.setup
async def setup(run: langwatch.agent.ScenarioRun, loyalty_tier: str = "none") -> dict:
    customer = await create_test_customer(tier=loyalty_tier, label=run.scenario_run_id)
    return {"customerId": customer.id}


@shop_assistant.scenario.teardown
async def teardown(run: langwatch.agent.ScenarioRun, state: dict | None, loyalty_tier: str = "none") -> dict | None:
    # Setup may have failed before it made the customer
    customer_id = (state or {}).get("customerId")
    if not customer_id:
        return None
    await delete_test_customer(customer_id)
    return {"deletedCustomer": customer_id}
```

The hook arguments:

| Argument | What it is |
| - | - |
| `run` | The run the hook is called for: `scenarioRunId`, `scenarioName` and `batchRunId` in TypeScript, `scenario_run_id`, `scenario_name` and `batch_run_id` in Python. |
| `state` | A JSON object. Setup returns it, every turn receives the latest version, and teardown receives it last. |
| `params` | The [run parameters](/docs/agent-testing/run-parameters) of the run, with the same values the agent and the scenario read. In Python, declare the parameters a hook uses as keyword arguments with defaults, as `loyalty_tier` above. |

A turn can update the state by returning `{ output, state }` in TypeScript or `langwatch.AgentReply(output, state=...)` in Python. LangWatch keeps the newest state, and a turn that returns none leaves it unchanged. A setup that returns no value starts the run with an empty state.

Teardown's return value is optional. If it returns an object, the run shows it under **Returned by teardown**.

In Python, a hook's keyword arguments join the agent's run parameters. If the agent and a hook declare the same name with a different type or default, the SDK logs a `parameters_invalid` warning and does not register the agent. In TypeScript, the hooks receive the parameters you declare in `connectAgent`.

> [!NOTE]
>
> In the Python SDK, `state` is a reserved turn field. Do not name a run parameter `state`: the argument receives the scenario state, and the name is not registered as a parameter.

## What do the simulated user and the judge see

The simulated user and the judge get the current state on every turn, as a system message that starts with `Current test state:`, so they can check the agent's answers against the real test data. That message is part of their prompts, which means the state costs tokens and is sent to your model provider.

## What happens when a hook fails

| Situation | What happens |
| - | - |
| Setup succeeds | The turns run with the state setup returned. |
| Setup fails, times out, or the agent is offline | No turn runs. The run ends in `ERROR` with no verdict, shows as **Not scored**, and does not count towards the pass rate. LangWatch still calls teardown; if the agent is still offline, teardown shows as not run. |
| The turns pass or fail | Teardown runs either way. |
| Teardown fails, times out, or does not run | The score does not change. The run shows the error, the state teardown was given, and "Test data may be left behind." |
| Live instances of the agent exist, but none declared the hook | The call fails with `hook_not_declared`, and the agent function does not run. |
| A state, or teardown's returned object, is over 64 kilobytes | The call fails with `agent_payload_too_large`. The cap applies to a turn's state update too. |

Each hook has a 60 second timeout. The agent's per-turn timeout does not apply to hooks, and you cannot change the hook timeout per scenario.

A failed setup is not scored because a fault in your test data is not the agent failing, so it should not lower the pass rate. Teardown never changes the score, because the score is about the agent's conversation.

## Where to see what a hook did

Open the run in **Agent Testing** or in **Simulations**. For each hook, the run shows its status (`ok`, `failed`, `timed_out`, `skipped` or `not_run`), duration, state or returned object, and error, with a **Trace** button for the hook's own trace. The run keeps the first 1,000 characters of an error, and keeps no copy of a state over 64 kilobytes.

### Rerun and keep the test data

To inspect the data a run made, open the run in **Simulations** and choose **Rerun and keep data**. The rerun skips teardown, and the run shows "Teardown skipped". The action appears only for a run that recorded a teardown.

"Teardown not run" means LangWatch called teardown but no instance of the agent was online to take the call, so teardown did not happen even though no one asked to skip it.

### Limits

* Not yet available: teardown for a run whose scenario process is killed. Name your test data with the run id, so you can find what is left behind.
* Not yet available: teardown for a run you cancel during its turns.

## Best practices

**Create test data in setup and remove it in teardown.** Data made anywhere else, such as inside a turn, is not in the state, so teardown cannot know about it.

**Return references, not data.** Put ids and URLs in the state, never whole records or secrets. The state goes into every call, into the prompts of the simulated user and the judge, and into the run record, and it is capped at 64 kilobytes.

**Name all test data with the run id.** Use `run.scenarioRunId` (`run.scenario_run_id` in Python) in a name or a tag, as the setup above does with `label`. Runs of a suite can overlap, so a unique name keeps one run's data apart from another's, and lets you find leftovers by name if a teardown is missed.

**Make teardown safe to run twice and safe on partial state.** Teardown runs after a failed setup too, so the state it gets can be missing or incomplete. Check that each id exists before you delete it, treat "already gone" as success, and remove everything you can.

**Return what teardown did.** Return what you deleted and what you left behind, so the run shows whether anyone needs to clean up by hand. If setup made something you have no way to delete, say so in the returned object.

**Keep hooks fast.** A hook that runs past 60 seconds counts as failed. Seed slow data once, ahead of time, and have setup pick from it instead of building it on every run.

**Throw in setup when the data cannot be made.** The run is then marked **Not scored** and stays out of the pass rate. If setup catches the error and returns an empty state instead, the agent is graded against data that does not exist.

**Keep secrets out of the state and the returned object.** Read credentials from your own process's environment, because the state and the returned object are stored on the run and sent to the model provider.

**Use run parameters to pick the test data.** For example, one scenario can cover several loyalty tiers when setup reads the `loyalty_tier` parameter and the scenario text reads the same value as `{{ params.loyalty_tier }}`. See [Run parameters](/docs/agent-testing/run-parameters).

Also check: [Connect your agent](/docs/agent-testing/connect-your-agent), [Run parameters](/docs/agent-testing/run-parameters), [Scenarios](/docs/agent-testing/scenarios), [Results](/docs/agent-testing/results), [TypeScript agent SDK reference](/docs/integration/typescript/agent-reference), [Python agent SDK reference](/docs/integration/python/agent-reference).


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