# Temporal workflow
`@ably/ai-transport/temporal/workflow` provides helpers to manage runs from inside a [Temporal](https://temporal.io/) workflow. You can interact with runs, adopt open runs, and suspend, end and clean up runs when you're done.
#### Javascript
```
import { openRun, withRun } from '@ably/ai-transport/temporal/workflow';
```
Every call here schedules one of the framing activities that [`createAblyTransportPlugin`](https://ably.com/docs/ai-transport/api/javascript/temporal.md#create-plugin) registers on your worker. The Ably client and the session both live on the worker side, which is why Temporal's workflow sandbox can load this module. Register the plugin on your worker before a workflow calls anything here, or the activities will not resolve.
The subpath declares `@temporalio/workflow` as an optional peer dependency. Install it alongside the SDK.
## Open a run and close it on failure
`withRun(invocation: InvocationData, body: (run: RunHandle) => Promise): Promise`
`withRun(invocation: InvocationData, options: OpenRunOptions, body: (run: RunHandle) => Promise): Promise`
Open a run, run `body` against it, and make a best-effort attempt to close the run if `body` throws. An unclosed run leaves every subscribed client waiting on a stream that never ends, so `withRun` is the entry point to reach for by default.
### Javascript
```
import { proxyActivities } from '@temporalio/workflow';
import { withRun } from '@ably/ai-transport/temporal/workflow';
import type * as activities from './activities.js';
const { runInferenceStep, runToolStep } = proxyActivities({
startToCloseTimeout: '5 minutes',
retry: { maximumAttempts: 3 },
});
export async function chatWorkflow(input) {
await withRun(input.invocation, async (run) => {
let outcome = await runInferenceStep({ ids: run.ids, invocation: input.invocation });
while (outcome.kind === 'server-tools') {
for (const toolCall of outcome.toolCalls) {
await runToolStep({ ids: run.ids, invocation: input.invocation, toolCall });
}
outcome = await runInferenceStep({ ids: run.ids, invocation: input.invocation });
}
});
}
```
The cleanup runs in a non-cancellable scope, so a cancelled or terminated workflow still closes its run, and cleanup's own failure is swallowed so `body`'s error reaches Temporal unmasked.
Cleanup is best-effort by design, and there are three cases where nothing is published:
- Cleanup gets one attempt with a short timeout. Retrying would let a hanging cleanup hold up a terminate.
- It no-ops when the run has already finished, or is parked suspended.
- It only fires on a throw. A `body` that returns without publishing a terminal leaves the run open.
On success `withRun` publishes nothing. Your application publishes its own terminal, and doing that inside an activity that already has the run loaded costs no extra adopt or load.
### Parameters
| Parameter | Required | Description | Type |
| --- | --- | --- | --- |
| invocation | required | The [invocation](https://ably.com/docs/ai-transport/concepts/runs.md#invocations) the client POSTed, as plain data. | InvocationData |
| options | optional | The invocation id to pin the run to, and per-activity timeouts and retry policies. See [`OpenRunOptions`](#open-run-options). | OpenRunOptions |
| body | required | The turn's work. Receives the [`RunHandle`](#run-handle). | (run: RunHandle) => Promise<T> |
### Returns
`Promise`, resolving to whatever `body` returns. Rejects with `body`'s error when `body` throws.
## Open a run
`openRun(invocation: InvocationData, options?: OpenRunOptions): Promise`
Create the run, locate its trigger event, publish the opening event, and return a handle. Use this when the workflow needs the handle outside a single lexical scope. You are then responsible for the failure-path cleanup, through `run.cleanup`.
"Open" covers two cases. A fresh turn creates a run and publishes `ai-run-start`. A continuation resumes the run its trigger names and publishes `ai-run-resume`, so opening does not always mean a new run. The SDK tells the two apart from the trigger's `run-id` header, which is also why the run-id pinning below applies only to a fresh run.
### Javascript
```
import { openRun } from '@ably/ai-transport/temporal/workflow';
export async function chatWorkflow(input) {
const run = await openRun(input.invocation, {
activityOptions: {
default: { startToCloseTimeout: '5 minutes' },
},
});
try {
await runInferenceStep({ ids: run.ids, invocation: input.invocation });
await run.end({ reason: 'complete' });
} catch (error) {
await run.cleanup(error instanceof Error ? error.message : 'workflow failed');
throw error;
}
}
```
### Parameters
| Parameter | Required | Description | Type |
| --- | --- | --- | --- |
| invocation | required | The [invocation](https://ably.com/docs/ai-transport/concepts/runs.md#invocations) the client POSTed, as plain data. | InvocationData |
| options | optional | The invocation id to pin the run to, and per-activity timeouts and retry policies. See [`OpenRunOptions`](#open-run-options). | OpenRunOptions |
### Returns
`Promise`. See [`RunHandle`](#run-handle).
## OpenRunOptions
| Property | Required | Description | Type |
| --- | --- | --- | --- |
| invocationId | optional | The run's invocation id. Used as the run id too, so a fresh-process retry re-enters the same run. Defaults to the Temporal workflow id. | String |
| activityOptions | optional | Per-activity timeouts and retry policies. See [`RunActivityOptions`](#run-activity-options). | RunActivityOptions |
## RunActivityOptions
Temporal `ActivityOptions` per framing activity. Each one is merged over `default`, which is in turn merged over the SDK's own defaults.
| Property | Required | Description | Type |
| --- | --- | --- | --- |
| default | optional | Applied to every framing activity unless overridden below. | ActivityOptions |
| openRun | optional | Overrides for opening the run. | ActivityOptions |
| endRun | optional | Overrides for publishing the run's terminal. | ActivityOptions |
| suspendRun | optional | Overrides for suspending the run. | ActivityOptions |
| cleanupRun | optional | Overrides for the failure-path cleanup. | ActivityOptions |
The SDK's own defaults:
| Activity | `startToCloseTimeout` | `retry.maximumAttempts` |
| --- | --- | --- |
| `openRun` | 2 minutes | 3 |
| `endRun` | 2 minutes | 3 |
| `suspendRun` | 2 minutes | 3 |
| `cleanupRun` | 30 seconds | 1 |
`cleanupRun` gets one attempt and a tight timeout so its own failure cannot hold up a terminate. Raise `openRun`'s `startToCloseTimeout` for a long conversation, because `openRun` pages channel history to locate the trigger event.
## RunHandle
A handle on an open run, held in workflow state. It carries plain data plus calls that schedule activities, and it never holds a live Ably session.
| Member | Description | Type |
| --- | --- | --- |
| ids | The run's identity, `runId` and `invocationId`. Thread it through your own activities so they can adopt the run. | RunIdentity |
| end | Publish the run's terminal. Takes `{ reason, errorMessage? }`, where `reason` is one of `complete`, `cancelled`, `error`, and `errorMessage` is used only when `reason` is `error`. | (params) => Promise<void> |
| suspend | Publish `ai-run-suspend`. Fails if a step is still open, because suspending mid-step would strand the step bracket. | () => Promise<void> |
| cleanup | Best-effort failure cleanup: end the run as `error` so a waiting client unsticks. Takes an optional `errorMessage`. Does nothing if the run already finished or is parked suspended. | (errorMessage?: string) => Promise<void> |
## Read next
- [Temporal worker](https://ably.com/docs/ai-transport/api/javascript/temporal.md): `createAblyTransportPlugin` and `stepIdFor`.
- [Get started with Temporal](https://ably.com/docs/ai-transport/getting-started/temporal.md): the workflow and worker built out in full inside a Next.js chat app.
- [Runs](https://ably.com/docs/ai-transport/concepts/runs.md): runs, steps, and invocations.
## Related Topics
- [Worker](https://ably.com/docs/ai-transport/api/javascript/temporal.md): API reference for @ably/ai-transport/temporal: the createAblyTransportPlugin worker plugin that registers the framing activities, and the stepIdFor helper.
## Documentation Index
To discover additional Ably documentation:
1. Fetch [llms.txt](https://ably.com/llms.txt) for the canonical list of available pages.
2. Identify relevant URLs from that index.
3. Fetch target pages as needed.
Avoid using assumed or outdated documentation paths.