---
title: "Dex Web"
description: "Dex Web is the operator UI for Dex. It runs as a separate dex-web process that"
canonical_url: "https://docs.superdurable.io/zh-Hans/reference/web/dex-web/"
source: "docs/reference/web/dex-web.mdx"
locale: "zh-Hans"
---

# Dex Web

Dex Web is the operator UI for Dex. It runs as a separate **dex-web** process that
connects to a FlowService, such as [server-zd](https://docs.superdurable.io/zh-Hans/reference/server.md).
For local development, **dexcli dev** starts server-zd and Dex Web together.

<a id="run-dex-web"></a>

## Run dex-web

**dex-web** is a standalone binary with an embedded UI. It serves the UI,
**/api/**, **/healthz**, and **/readyz**, and it does not start a server.

```bash
dex-web --flow-service-address 127.0.0.1:8801
```

Open <http://127.0.0.1:8802>. The **superdurable/dex-web**
container image runs the same binary. It binds to **0.0.0.0:8802**, runs as UID
and GID 65532, and includes the system CA bundle. Each push to the main branch
publishes **superdurable/dex-web:latest**, and each **web/vX.Y.Z** release
publishes **superdurable/dex-web:vX.Y.Z**. Mount Flow Definition Graph
directories and token files read-only, with permissions that let that UID read
them.

```bash
docker run --rm -p 8802:8802 \
  -e DEX_WEB_FLOW_SERVICE_ADDRESS=host.docker.internal:8801 \
  superdurable/dex-web:latest
```

Every setting accepts a flag or an environment variable. A flag overrides its
environment variable. Settings do not change after startup, except the contents
of the credential file. Other environment variables are ignored, including
Kubernetes service-link variables such as **DEX_WEB_PORT**.

| Flag                               | Environment variable                     | Default and meaning                                                                           |
| ---------------------------------- | ---------------------------------------- | --------------------------------------------------------------------------------------------- |
| **--bind-address**                 | **DEX_WEB_BIND_ADDRESS**                 | **127.0.0.1**; the image sets **0.0.0.0**. Pass **0.0.0.0** when remote clients must connect. |
| **--port**                         | **DEX_WEB_HTTP_PORT_NUMBER**             | **8802**; the HTTP port.                                                                      |
| **--flow-service-address**         | **DEX_WEB_FLOW_SERVICE_ADDRESS**         | **127.0.0.1:8801**; the FlowService gRPC address.                                             |
| **--flow-rendering-dir**           | **DEX_WEB_FLOW_RENDERING_DIRECTORY**     | Empty; the Flow Definition Graph JSON directory that enables **v2**.                          |
| **--tls**                          | **DEX_WEB_TLS**                          | **false**; encrypts and verifies the FlowService connection.                                  |
| **--tls-ca-file**                  | **DEX_WEB_TLS_CA_FILE**                  | Empty; a PEM file added to the system roots. Requires **--tls**.                              |
| **--tls-server-name**              | **DEX_WEB_TLS_SERVER_NAME**              | Empty; overrides the name checked against the server certificate. Requires **--tls**.         |
| **--flow-service-credential-file** | **DEX_WEB_FLOW_SERVICE_CREDENTIAL_FILE** | Empty; a file with the bearer token that Dex Web sends to FlowService.                        |

Start Flow always uses pull Workers. Two legacy Worker routing options are deprecated and have no effect: **--start-flow-worker-target-pull** (**DEX_WEB_START_FLOW_WORKER_TARGET_PULL**) and **--start-flow-worker-target-headless** (**DEX_WEB_START_FLOW_WORKER_TARGET_HEADLESS**). Dex Web still accepts them and prints a startup warning when one is provided.

The credential file holds one bearer token. Surrounding whitespace is ignored.
A missing or empty file fails startup. If the file later becomes empty or
unreadable, RPCs fail until it is restored. Dex Web checks the file for a new
token at most once per second, so an external agent can rotate short-lived
tokens through it. The token stays in the Dex Web process and never reaches the
browser. Use **--tls** when you send a credential to a remote FlowService.

```bash
dex-web --flow-service-address dex.example.com:443 --tls \
  --flow-service-credential-file /var/run/secrets/dex/token
```

**/healthz** reports only that the process is alive. **/readyz** validates the
current definition snapshot and then calls FlowService **HealthCheck** with the
configured credential. Use **/readyz** to gate traffic and **/healthz** for
liveness, so a FlowService outage does not restart Dex Web. On shutdown, Dex Web
waits up to ten seconds for open HTTP requests.

<a id="dex-web-access-control"></a>

### Access control

Dex Web has no end-user authentication. Anyone who can reach its port acts with
the configured FlowService credential, and Dex Web can show Flow inputs, outputs,
Attributes, and Worker stack traces. Place it behind TLS and an
authentication-enforcing reverse proxy or gateway, and authorize access by role.
Do not expose Dex Web or FlowService directly to the public Internet. Audit
access to production Flow details according to your data-handling policy.

<a id="api-requests"></a>

### The /api requests

**POST**, **PUT**, **PATCH**, and **DELETE** requests under **/api/**, including
Flow search, must send **Content-Type: application/json**. A missing or
different media type returns HTTP 415. Add the header to **curl** calls that use
**-d**:

```bash
curl -H 'Content-Type: application/json' -d '{}' http://127.0.0.1:8802/api/...
```

Cross-origin browser requests return HTTP 403. Dex Web uses the browser's fetch
metadata, so a same-origin request works behind a proxy that terminates TLS or
rewrites the **Host** header. It needs no trusted-origin setting. The proxy must
pass the **Sec-Fetch-Site** and **Origin** headers through unchanged. For a
client that sends no fetch metadata, the **Origin** host must equal the request
**Host**, so configure the proxy to keep the public **Host**. Forwarded headers
do not override these checks. A client that sends neither **Origin** nor
**Sec-Fetch-Site**, such as **curl**, is accepted when it sends JSON.

These checks do not stop DNS rebinding. Restrict accepted host names at the
reverse proxy, and keep direct access to the Dex Web port limited to trusted
clients. When FlowService fails, the browser sees a generic message and the
original error goes to the Dex Web log.

Dex Web has two URL prefixes. **v1** is the existing Flows search, run details, and Flow Rendering workspace. **v2** is the operator workspace: a run list and filters on the left, and the Flow Definition Graph on the right.

Start Dex without a JSON directory to open **v1** at **/v1/flows**. Start Dex with **--flow-rendering-dir** to open **v2** at **/v2**. The top-right Version menu switches back to **v1**.

```bash
dexcli visualize ./refund_flow.go --schema-version 2.0 --json --out ./build/refund
dexcli dev --flow-rendering-dir ./build
```

**v2** searches current runs for one Flow type. The canvas uses that Flow type's generated definition, so you do not pick a JSON file again. Historical runs stay on **v1**.

Each definition uses the Flow type and Step types that your Workers register, as described in [Flow and Step names](https://docs.superdurable.io/zh-Hans/reference/web/flow-visualization.md#flow-and-step-names). Go Flows need no **GetFlowType** or **GetStepType** override unless the analyzer reports one of the name diagnostics listed there. Generate definitions with the **dexcli** release that matches your Dex Web, and regenerate them after upgrading.

Version 1 remains the default analyzer schema. Versions 1 and 2 support Go, Python, and TypeScript. Python and TypeScript support a subset of the Go analyzer features; see [Flow source visualization](https://docs.superdurable.io/zh-Hans/reference/web/flow-visualization.md). Version 1 and Version 2 definition files can coexist. Dex Web rejects malformed Version 2 files and duplicate valid definitions for the same Flow type at startup. For example, two package-main binaries that both define **main.Flow** resolve to one Flow type, and the error names both files. An analyzer result with **valid: false** remains available on **v1** Flow Rendering but does not appear as a **v2** Flow type.

<a id="dexcli-web"></a>

## View a remote server with dexcli web

**dexcli web** serves the same embedded UI against an existing server. It does
not start a server. It connects to the global **--server** address with the
same TLS and credential settings as other **dexcli** commands.

When the FlowService port of a remote server is bound only to the remote host's
loopback address, reach it through an SSH tunnel. **ssh -N** keeps running in
the foreground, so open the tunnel in one terminal and leave it running:

```bash
ssh -N -L 8801:127.0.0.1:8801 operator@dex-host
```

In another terminal, start Dex Web:

```bash
dexcli --server 127.0.0.1:8801 web
```

Open <http://127.0.0.1:8802>. The command accepts
**--bind-address** (default **127.0.0.1**), **--web-port** (default **8802**),
**--flow-rendering-dir**, and **--open** (default true; pass **--open=false** to
skip the browser). Ctrl+C stops Dex Web, and the server keeps running.

A running **dexcli dev** also uses ports 8801 and 8802. To run both, choose
another local tunnel port and another Dex Web port:

```bash
ssh -N -L 18801:127.0.0.1:8801 operator@dex-host
dexcli --server 127.0.0.1:18801 web --web-port 18802
```

Before it prints its address, **dexcli web** calls FlowService **HealthCheck**.
It fails when the server or the tunnel is unreachable. It also checks protocol
compatibility and prints a warning on a mismatch. **--timeout** bounds only these
startup checks, not the running Dex Web. Connector setup is disabled, because
Connector configuration belongs to a local **dexcli dev** environment.

Dex Web has no authentication. Anyone who can reach its port can operate Flows
on the server with your credential. Keep **--bind-address** on a loopback
address; **dexcli web** prints a warning for any other address. Keep the server's
FlowService port bound to the remote host's loopback address too, so the SSH
tunnel stays the only way in.

## Named directives

Version 2 directives use named values. Parameter position within one line has no meaning. Quote strings containing spaces as JSON strings, and write multiple values as a JSON array.

**Go**

```go
// dex:field attribute-key:customer-email value-type:string editable:false description:"Customer email" ui-slot:title
// dex:field attribute-key:case-status value-type:string editable:false description:"Case status" ui-slot:status
// dex:field attribute-key:in-email value-type:string editable:false description:"Customer request" ui-slot:subtitle
// dex:field attribute-key:in-charge-ref value-type:string editable:false description:"Charge reference"
// dex:field attribute-key:evidence-state value-type:string editable:false description:"Evidence state"
// dex:field attribute-key:recommended-action value-type:string editable:false description:"Recommendation" ui-slot:recommendation
// dex:field attribute-key:recommendation-rationale value-type:string editable:false description:"Recommendation rationale" ui-slot:reason
// dex:field attribute-key:guardrail-verdict value-type:string editable:false description:"Guardrail verdict"
// dex:field attribute-key:guardrail-rule value-type:string editable:false description:"Guardrail rule" ui-slot:reason
```

例子: [examples/go/products/customer-refund/agentic/workflow.go](https://github.com/superdurable/dex/tree/main/examples/go/products/customer-refund/agentic/workflow.go)

Unknown or repeated parameters, missing required parameters, and invalid JSON are blocking diagnostics. Separate indexed Attribute, field, Action, and input lines keep source order. This order becomes column, detail-field, Action, and form order. There is no separate order property.

Every registered Step has one group declaration and one explanation declaration. The explanation is one sentence that states what the Step does. Group order follows the first member's registration position in **GetSteps**. Step order within a group also follows **GetSteps**.

A group should do one thing. Make it either one run of Steps that lead to each other, or one choice that a single Step makes between its Steps, such as the tools an agent picks from. The analyzer warns about any other group with **v2_step_group_shape** and names the group's separate parts, so you can split it. The warning does not make the definition invalid.

The analyzer judges a group by its main story only. A failure-handling Step is a Step that only failure transitions reach, directly or through other failure-handling Steps. A record of exhausted retries and a repair after a crash are examples. These Steps belong to no group on the canvas, and failure transitions never join a group. Failure-handling Steps do not need a group of their own.

## Flow canvas

The **v2** canvas draws one card per Step. The card's icon says who acts at the Step: a person, a message from outside, time, a child Flow, an AI model, an app through a Connector, or the Flow itself.

A group of two or more Steps that forms one run or one choice is a stage. Any other group shows its Steps in its labeled band instead. **Collapsed** draws each stage as one card. Select the card or its **⊕** to open the stage in place, and select **⊖** on the open stage to close it. **Expanded** opens every stage. Both buttons also return the view to where the canvas opened.

Each Step is drawn with one line in: the transition from the latest Step that leads to it, preferring a Step in its own group. Every other transition is a **↪** chip on the card it leaves. Select a chip to show where it leads. The Actions that answer a waiting Step, the RPC that starts a Step, and how a Step ends the Flow are also chips on its card.

The canvas draws the Flow's main story first. The main story is every Step that the Flow reaches on purpose, however rarely. It includes business rejections, failed checks, and Steps that an RPC moves the Flow to. Failure handling is hidden in both **Collapsed** and **Expanded**. A Step card that a failure transition leaves shows **⚠** and the number of its failure transitions. Stage cards do not show this count, so the collapsed view stays quiet.

Select a card with **⚠** to draw its failure handling beside it. The Steps its failures lead to appear in dashed cards. A failure that goes straight to a main-story Step appears as a dashed **↪** pointer, and **↩** chips lead back to the main story. Nothing else moves. Select anything else to hide the failure handling again.

A Step that catches its own error and then chooses the next Step makes an ordinary decision, so it stays in the main story. To hide that handling, return the error so the Step's failure route runs.

When you select a run, the stage that holds the waiting Step opens, and the Step is ringed and labeled **now**. Failure handling that the run went through is drawn beside the Step that failed into it.

## Start Flow

The Run workspace shows **Start Flow** when the selected definition contains a generated start schema. The dialog asks for a Flow ID and the start input. The Flow runs on a [pull Worker](https://docs.superdurable.io/zh-Hans/primitives/components/application.md) that is connected to the server. It renders nested objects, arrays, string-key maps, enums, booleans, datetime values, optional fields, and explicit null values. Fixed arrays keep their declared length. Integer values remain decimal text in the browser until serialization, so int64 values keep their exact precision.

The Go analyzer derives the schema from the input type of the Step registered with **DefineStartStep**. It follows JSON field names, ignored fields, pointers, and **omitempty**. It supports nested and imported structs, named string and integer constants, **time.Time**, ordinary numeric widths, slices, fixed arrays, and string-key maps. A recursive or unsupported input produces a warning and omits the start schema without hiding the rest of the definition.

The Server reloads the current definition revision and validates the complete input before calling **StartFlow**. It rejects unknown object fields, missing required fields, invalid nulls, type, enum, range, datetime, array, and map errors. The Server selects the start Step type and creates the request ID.

The **POST /api/v2/start** request does not need a Worker target. Dex Web starts the Flow on a pull Worker. **workerTargetPull: true** is accepted. **workerTargetPull: false** returns HTTP 400. Dex Web does not check for a registered pull Worker before starting, because registrations can change before dispatch. See [Run against server-zd](https://docs.superdurable.io/zh-Hans/reference/web/dex-web.md#run-against-server-zd).

## Worker routing

Flow detail shows the **Worker target**, which is **Pull**. It also shows **Flow type queue**, named after its Flow type, and **Sticky app Worker ID**, the last app Worker recorded as serving the Flow.

Active Steps and Step history show routing for **WaitFor** and **Execute**. **Serving app Worker ID** identifies the Worker that served the method. **Queue source** shows the queue that served the task: **Sticky queue** or **Flow type queue**. A Worker may serve a task from the Flow type queue even when its ID matches the **Dispatch sticky app Worker ID**. For older records without queue metadata, Dex infers the source from those IDs. A pending dispatch shows **—** until its queue is known. A failed dispatch may show its queue even if no serving Worker is known.

A Step that no Worker takes waits in the queue instead of failing. A dispatch failure for no available Worker, or for an unknown RPC name that exhausted the dispatch wait, applies to RPCs only. A Worker disconnect still fails a handed-out attempt. Older Step failure records may show **Unknown Step type: dispatch timed out**. RPC history shows the serving app Worker ID for recorded invocations. Older runs without routing metadata show **—**.

Active Steps show the current **WaitFor invocation** and **Execute invocation**. **Phase** is **Pending**, **Running**, or **Backoff**. **Attempt** is the attempt that waits or runs. During **Backoff**, it is the next attempt. A pending attempt shows its **Pending reason** and **Pending since**, with how long it has waited. The reason is one of these:

- **Waiting for locks**: another Step or a transactional RPC holds one of its locks, or an earlier Step waits for one.
- **Waiting for a Worker**: no Worker of the Flow type has taken the attempt yet.
- **Waiting for a Worker that supports this Step type**: Workers of the Flow type are connected, but none of them supports the Step type. The Step stays queued until such a Worker connects, the Flow times out, or the Flow is stopped.

A running attempt shows **Attempt started**. A backoff shows **Backoff ends**. Older Servers do not report invocations, so Dex Web hides them.

Step history shows **Scheduled**, the time the final attempt became pending. The time from **Scheduled** to **Started** is the wait for locks and a Worker.

## Search and Summary

An indexed Attribute declaration binds the Dex Attribute key, physical search index key, index type, application value type, and description. The analyzer compares every value with the Go definition and the SDK's effective index configuration.

The physical key can be a generic namespace slot such as **keyword2**. The selected Flow type scopes its meaning in this workspace, so the UI behavior does not change when different Flow types reuse that slot.

The **v2** list searches one Flow type at a time. Filters include Flow ID, execution status, start and close time, and the declared indexed Attributes. Dex Web compiles these controls into a visibility query. It does not accept a raw query from the browser. Different fields use AND; multiple values for one field use OR.

Each row calls **GetDexSummary** for the logical Flow ID's current run. The RPC accepts **dex.None** and returns a **map\[string]any**. Dex Web loads at most eight summaries concurrently, applies a five-second timeout to each call, and isolates a failure to its row. Indexed Attributes appear before Summary fields.

## Display and edits

Selecting a row keeps the Flow ID in the URL, not a run ID. **GetDexDisplay**, Attribute reads, edits, and Actions omit the run ID, so Dex resolves the current run. Refreshing the same URL always addresses the Flow's current run.

On **v2**, selecting a Step opens a panel split into **Definition** and **Execution**. Definition shows the Step **explanation**, WaitFor, and Execute branches from the Flow Definition Graph. Long Definition content scrolls so Execution stays visible. When a Step has more than three Execute branches, the branch list starts collapsed. Execution has a dropdown when the Step ran more than once, then **Input**, **Output**, and **Context** for the selected WaitFor or Execute history event in the same structured form as **v1** Selected event. The execution payload defaults to Details and can switch to Raw JSON of the hydrated payload. AttributeMap and ChannelMap instances that are decimal numbers sort by numeric value, matching **v1**. Execute shows the live next Steps from that run's decision, not every FDG exit. Use **Load more from previous run** to see earlier executions of a Step.

A field can also name the UI slot it fills: **title**, **subtitle**, **status**, **recommendation**, or **reason**. A UI slot says what part the field plays, so one template serves every Flow while each Flow maps its own Attributes onto it. UI slots are optional and most fields fill none.

Dex Web reads **title** and **status** for each row in the run list, and falls back to the Flow ID and the execution status when a Flow declares neither. The selected-run panel reads all five, in UI slot order, ahead of the remaining fields. **title**, **subtitle**, **status**, and **recommendation** admit one field each. **reason** admits several, because a recommendation can rest on more than one.

A row carries indexed Attributes and Summary fields only. A UI slot on an Attribute that appears in neither is read in the panel and not in the row. A UI slot is not an index: it says where a value belongs, never that it is searchable.

Display fields are otherwise ordered by their directive lines. Primitive string, integer, double, boolean, and datetime fields can be editable. JSON, objects, arrays, and AttributeMap values remain read-only. Missing fields must be returned as null. The backend rejects omitted, undeclared, or mistyped values.

Edits are available only while the current run is active. Dex Web writes the Attribute through **SetAttributes** and includes its declared index configuration when the Attribute is indexed. When the edited Attribute is an Action condition source, Dex Web also sends the complete Action permission mapping. Other edits omit it.

## Actions

An Action is an RPC registered with **RPCOptions.Action**. **DefineAction** receives its label, an Attribute condition, and one **ActionRequiresPermission** option. The visualizer reads this typed Go registration directly. Dex Web re-reads the condition Attribute before invoking the RPC.

An Action without input accepts **dex.None** and renders as a direct button. An Action with input accepts a named Go struct. User-sourced fields render controls in directive order. Attribute-sourced fields stay hidden and use the selected run's Attribute snapshot.

A user-sourced string field can declare **capture:qr-code**. Dex Web keeps the text field for scanners, paste, and manual entry, and adds **Scan QR code**. Opening the scanner requests the camera and loads the QR reader. A successful scan copies the decoded text into the field; it does not submit the Action. The user can review the value before choosing the Action.

Camera capture requires HTTPS or localhost. A denied permission, missing camera, unsupported environment, or host policy leaves manual entry available. Closing the scanner, changing runs, or leaving the page releases its camera tracks. Scanning does not grant permission, bypass the Action condition, or change the RPC protocol.

Each Action requires one permission such as **refund.manage**. Permission keys begin with a lowercase letter and contain lowercase letters, digits, dots, or hyphens. When a successful Worker invocation writes an Action condition source, the Worker sends the complete mapping with that response. Unrelated writes and query-only RPCs omit it.

The Server overlays the business writes on authoritative Attribute state and evaluates the mapping atomically with those writes. It sorts and deduplicates the permissions of currently available Actions, then adds newly matching permissions to the internal **DexWorkQueuePermissions** KeywordList Search Attribute. Once added, a permission remains for that Flow execution even when no Action currently matches it. Projection maintenance does not require application Steps to lock every Action source.

The permission history lasts for the Flow execution and remains searchable after completion. A Server upgrade preserves permissions that are still stored but does not reconstruct permissions removed before the upgrade. A new, independent Flow execution starts from its own initial Attributes. A changed Action definition is first evaluated when a later source write sends its mapping or a new Flow starts. Removing or renaming an Action permission does not clear the old value from existing permission history.

Work Queue offers the permissions declared by the selected Flow type. Choosing **Working as** one permission sends it as **workQueuePermissions** to **POST /api/v2/search**. The API also accepts several permissions for callers that derive them from multiple user roles. A run matches when its permission history contains any requested permission. This permission group is joined with the Flow type and other filters using AND.

A matching permission means an Action is or was available; it does not mean the Action is available now. A keyed gate also needs its current key, and the list reads an eventually consistent index. The run decides which Actions are currently available once it is open.

The browser condition only controls presentation. Every Action RPC must check the current state again before changing durable state or publishing a Channel message. Use locks when that check and the effect must be atomic.

Work Queue does not add login or enforce authorization. **Working as** selects work; it does not grant the chosen permission. The trusted application boundary remains responsible for authenticating users, mapping roles to permissions, and limiting which permissions it sends. Deploy Dex Web behind that boundary.

<a id="run-against-server-zd"></a>

## Run against server-zd

[server-zd](https://docs.superdurable.io/zh-Hans/reference/server.md) does not serve Dex Web. Point **--flow-service-address** at the server-zd
gRPC address. When server-zd authentication is on, pass **--flow-service-credential-file**
with a static token or service JWT. Dex Web also supports TLS for the upstream
connection through **--tls**, **--tls-ca-file**, and **--tls-server-name**.

```bash
dex-web --bind-address 0.0.0.0 --port 8802 \
  --flow-service-address dex-server-zd:8801 \
  --flow-service-credential-file /etc/dex/web-token
```

For local development, **dexcli dev** starts server-zd and Dex Web.
**--engine zd** remains accepted for existing scripts. The dexcli release package includes **dex-server-zd**, so a
Homebrew install of dexcli needs no separate download. To use another
**dex-server-zd** binary, pass **--zd-binary** or set **DEX_SERVER_ZD_BINARY**.
Without either, dexcli uses the **dex-server-zd** next to the dexcli executable,
then the one on your PATH. Pass
**--zd-config** with a server-zd YAML file to override settings such as closed
Flow retention, Streams, or the search backend. The
[CLI README](https://github.com/superdurable/dex/blob/main/cli/README.md#start-locally)
lists every flag.

The **--durability** flag of **dexcli dev** chooses how server-zd stores
acknowledged writes. It accepts three modes:

- **strict** is the default. server-zd flushes every write to disk with fsync
  before it replies. A killed process, a host crash, or a power loss loses no
  acknowledged Flow, and the next start resumes from the same state directory.
- **process-crash-safe** writes every acknowledged write to its files without
  fsync. A killed process loses no Flows, but a host crash or power loss can
  lose recent writes.
- **relaxed** keeps writes in memory until a graceful stop. After a crash or
  kill in relaxed mode, server-zd refuses to start until you delete the state
  directory.

Each fsync waits for the disk, so **strict** is slower. On macOS a Flow takes
several times longer than with **process-crash-safe**, which suits short-lived
test servers. The flag overrides **dev.durabilityMode** in the **--zd-config**
file. Without the flag, server-zd uses that setting, or **strict**.

```bash
dexcli dev --flow-rendering-dir ./build
```

Start Flow uses pull Workers, as described in [Start Flow](https://docs.superdurable.io/zh-Hans/reference/web/dex-web.md#start-flow).

<a id="run-attempts"></a>

### Run attempts

Time travel keeps the run ID and creates a new run attempt. Run attempts are
numbered from 1.

The run details page shows a **Run attempt** selector in place of **Run
chain**. The page opens from a **v1** run or from **Inspect** on **v2**.
Choosing an earlier attempt adds **?runAttempt=N** to the page URL and shows
that attempt's history. Live history stops for an earlier attempt. Live state
always belongs to the current attempt. Time travel from that view searches the
selected attempt. A **TimeTravelFork** event links to the previous run attempt.

When the run attempt changes, the page reloads the whole history. Events after
the fork point no longer belong to the current attempt.

**GET /api/flows/history** accepts an optional **runAttempt**. Zero or absent
selects the current attempt. Each event and **GET /api/flows/summary** report
**runAttempt**. **POST
/api/flows/time-travel** accepts an optional **runAttempt** and returns the new
one.
