---
title: Handle instance termination
description: How your agent finds out that Prefactor terminated its run, and how
  to handle it in TypeScript and Python.
editUrl: true
head: []
template: doc
sidebar:
  hidden: false
  attrs: {}
pagefind: true
draft: false
---

Someone on your team can stop a running agent from the [Agent instance page](/admin-ui/agent-instance) in the web app, or through the API — see [Instance](/platform/concepts/instance) for what termination means on the platform. This page covers how your agent finds out and how to handle it, with examples in TypeScript and Python.

## How your agent finds out

Detection is built into both SDKs; there is nothing to configure. While a run is active, the SDK learns about termination two ways:

- **Span responses.** Every span create and finish response carries a control signal once the instance is terminated, so an agent that is actively emitting spans finds out on its next span.
- **Polling.** An idle agent that isn't emitting spans polls the instance every 30 seconds, so termination is detected within about half a minute even when the agent is quiet.

Termination is cooperative. Prefactor records the termination and signals the agent, but it does not kill your process, and it keeps accepting spans until the agent stops. Responding is up to your code — or to your integration's middleware.

To watch for termination from outside the agent — in a dashboard or a supervisor process — subscribe to the instance over the [WebSocket API](/api/websocket). A terminated run arrives as an `agent_instances/updated` notification with status `terminated` and the operator's reason.

## Handle termination in TypeScript

The core runtime detects termination for you and exposes it through the termination monitor: a standard `AbortSignal` you can check in your own loop or pass to long-running work, plus callbacks that fire the moment termination is detected. Nothing throws on its own — checking the signal is up to your code.

```typescript
import { createCore } from '@prefactor/core';

const core = createCore({
  transportType: 'http',
  httpConfig: {
    apiUrl: process.env.PREFACTOR_API_URL!,
    apiToken: process.env.PREFACTOR_API_TOKEN!,
    agentIdentifier: 'my-agent-v1',
  },
});

const monitor = core.terminationMonitor;

monitor.onTerminated((reason) => {
  console.log('Run terminated by Prefactor:', reason);
});

core.agentManager.startInstance();

try {
  while (!monitor.signal.aborted) {
    // ... your agent's next step ...
  }
} finally {
  core.agentManager.finishInstance();
  monitor.reset();
}
```

Finishing an already-terminated instance is treated as success, so the `finally` block is safe however the run ended. `reset()` readies the monitor for the next run and replaces the signal — read `monitor.signal` fresh each run rather than capturing it once.

### LangChain

The LangChain integration (`@prefactor/langchain`) throws for you. Initialise through `init` from `@prefactor/core` with the LangChain provider, and the middleware raises an error named `PrefactorTerminatedError` at the next agent, model, or tool hook once termination is detected:

```typescript
import { init } from '@prefactor/core';
import { PrefactorLangChain } from '@prefactor/langchain';

const prefactor = init({
  provider: new PrefactorLangChain(),
  httpConfig: {
    apiUrl: process.env.PREFACTOR_API_URL!,
    apiToken: process.env.PREFACTOR_API_TOKEN!,
    agentIdentifier: 'my-agent-v1',
  },
});

try {
  await agent.invoke({ messages: [{ role: 'user', content: query }] });
} catch (err) {
  if (err instanceof Error && err.name === 'PrefactorTerminatedError') {
    // The run was terminated. Clean up, then continue to the next run.
  } else {
    throw err;
  }
} finally {
  prefactor.finishCurrentRun();
}
```

`PrefactorTerminatedError` is not an exported class — catch it by checking `error.name`, as above; the operator's reason is in `error.message`. Call `finishCurrentRun()` after every run, terminated or not: it finishes the instance if it's still open and resets the monitor for the next one.

Initialising with `init` from `@prefactor/langchain` instead — the form that returns middleware directly — detects termination internally but never throws it, and gives you no client handle to check the signal yourself.

### LiveKit

The LiveKit integration (`@prefactor/livekit`) shuts the session down without draining and finishes the run's open spans as failed with the termination error.

### AI SDK and Claude

The AI SDK (`@prefactor/ai`) and Claude (`@prefactor/claude`) adapters don't raise on termination yet. Check `monitor.signal` yourself in long-running code paths.

## Handle termination in Python

The core client detects termination internally, but its monitor isn't part of the public API — the supported reaction point is an integration's middleware.

### LangChain

The LangChain middleware (`prefactor-langchain`) raises `PrefactorTerminatedError` before the next agent, model, or tool hook once termination is detected. The exception is exported from `prefactor_core` and carries the termination reason:

```python
import os

from langchain.agents import create_agent
from prefactor_core import PrefactorTerminatedError
from prefactor_langchain import PrefactorMiddleware

middleware = PrefactorMiddleware.from_config(
    api_url="https://app.prefactorai.com",
    api_token=os.environ["PREFACTOR_API_TOKEN"],
    agent_id="01exampleagentid00000000000000000",
    agent_name="My Agent",
)

agent = create_agent(model, tools=[...], middleware=[middleware])

try:
    result = await agent.ainvoke({"messages": [{"role": "user", "content": query}]})
except PrefactorTerminatedError as e:
    logger.info("Run terminated by Prefactor: %s", e.reason)
finally:
    await middleware.close()
```

A service that runs its agent in a loop catches the error, waits, and starts the next run as a fresh instance:

```python
while True:
    try:
        await run_once()
    except PrefactorTerminatedError:
        logger.info("Run terminated — next run in %.0fs.", restart_delay)
        await asyncio.sleep(restart_delay)
```

### LiveKit

The LiveKit integration (`prefactor-livekit`) doesn't respond to termination yet; the run keeps recording until your process stops.

## Terminate a run from the API

Terminating is an operator action, not an SDK call — neither SDK exposes it. Use the [HTTP API](/api/http) directly with an account-scoped token:

```bash
curl -X POST https://app.prefactorai.com/api/v1/agent_instance/<instance-id>/terminate \
  -H "Authorization: Bearer $PREFACTOR_ACCOUNT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"reason": "Runaway retry loop"}'
```

The reason is required and is stored on the instance. Only an active run can be terminated; the request fails with a conflict otherwise. The deployment-scoped token your agent runs with cannot terminate its own instance — use an account-scoped token from the [Account › API tokens tab](/admin-ui/account/api-tokens). The full request and response shape is under [POST /agent_instance/{agent_instance_id}/terminate](/api/platform/operations/actionagentinstanceterminate) in the API reference.

## Termination vs shutdown

These two are easy to conflate. Termination is the platform stopping your run; `shutdown()` is your process exiting cleanly — it flushes queued telemetry and closes the transport. Call `shutdown()` (TypeScript) or `middleware.close()` (Python) when your app exits, regardless of how the run ended. `PrefactorShutdownError` in the TypeScript SDK means telemetry couldn't be flushed cleanly during shutdown; it is not a terminated-run signal.

## Limits

- Termination is cooperative. If your agent never observes the signal — because it does blocking work without yielding, or its integration doesn't check — it keeps running and Prefactor keeps recording its spans.
- An idle agent can take up to about 30 seconds to notice; an agent emitting spans notices on the next span.

## Verify

Terminate a run from the [Agent instance page](/admin-ui/agent-instance) while your agent is active. Your agent should stop at its next step — or within about 30 seconds if it's idle — and the instance shows as **Terminated** with your reason.

## Related

- [Instance](/platform/concepts/instance) — lifecycle states and what termination records.
- [Agent instance page](/admin-ui/agent-instance) — the Terminate control in the web app.
- [Stream agent instance updates](/api/stream-instance-updates) — watch a run's state change live over the WebSocket API.
- [Configuration and environment variables](/sdks/configuration) — transport and capture settings for both SDKs.
- [TerminationMonitor](/sdks/typescript-sdk/api/core/classes/TerminationMonitor) — generated TypeScript API reference.
- [prefactor_core.exceptions](/sdks/python-sdk/api/core/reference/prefactor_core.exceptions) — generated Python API reference including `PrefactorTerminatedError`.