---
title: dispatch
description: "Make a `dispatch` Effect: it starts a run of a Workflow."
---

# `dispatch`

Function, in `@jr2/orchestrator/src/dispatch-effect.ts`. It is an export of `@jr2/orchestrator`, on the [Authoring surface](../authoring-surface.md) page, in Effects.

```ts
function dispatch<
  S extends z.ZodObject | undefined = undefined,
  const T extends DispatchDefinition<S>["target"] = undefined,
>(
  workflow: string,
  def?: DispatchDefinition<S> & { target?: T },
): EffectLogic<DispatchInput<S>, DispatchOutput> & OpenOf<DispatchOpenParts<T>>
```

Make a `dispatch` Effect: it starts a run of a Workflow. Put it in `actors`, and invoke it from a
state: `actors: { triage: dispatch("triage", { input }) }`. The input of the invoke is the run
input of the new run, as `jr2 run --input` gives it.

The new run is a run of its own, with its own id, its own Workspaces and its own `CANCEL`. Its
record names this run as the run that dispatched it. A `CANCEL` of this run does not end it.

With no `target`, the run starts in this Orchestrator, under the current Assembly. With
`target: { url, token }`, it starts on the Orchestrator at `url`, and the Effect runs in the
Instance Effector. Every outcome is output ([`DispatchOutput`](type-dispatchoutput.md)): `started`, `settled`,
`refused`, `unreachable` or `unknown`. Each start carries a key, so a start that jr2 sends again
after a restart starts no second run.

It refuses:
- a name that is not a Workflow name: ``dispatch("<name>"): the Workflow is named as `jr2 run`
  names it — a Machine's filename stem ("review"), or "<wrapper>/<name>" for a named Chart:
  lowercase letters, digits and "-" (ADR-0071)``
- a `wait` that is not a boolean: `dispatch("<name>", { wait }): wait is true or false — a slot
  that needs both declares two`
- an `input` that is not a `z.object()`: `dispatch("<name>", { input }): the input is a
  z.object(...) — the run's body is a JSON object (ADR-0071)`
- a `timeout` that is not a duration: `dispatch("<name>"): timeout "<timeout>" is not a positive
  duration (like "30s", "10m", "1h")`
- a `target` url that is not an origin: `dispatch("<name>", { target }): "<url>" is not an
  origin — write the scheme and the host, with a port if it is not the scheme's, and no path,
  query or userinfo`, then two examples
- `secret(open)` in a target: `dispatch("<name>", { target }): secret(open) in a target — the
  target is the Open part, not its secret`, then the fix

`jr2 up` refuses a dispatch with no target whose Workflow the Assembly does not have, and an
`input` schema that the input of that Workflow does not take.

**Parameters**

- `workflow`: The Workflow to start, as `jr2 run` names it: `"triage"`, or
  `"<wrapper>/<name>"` for a named Chart.
- `def`: Optional. Each field tells what it does.

  - `input` (optional): `S`

    A zod schema of the run input of the Workflow. It types the `input` of the invoke and
    `fromPick`, and `jr2 up` compares it with the input that the Workflow declares. Without it,
    only the Workflow checks the input.
  - `wait` (optional): `boolean`

    `true`: end when the new run ends (`settled`). `false`, the default: end when the
    Orchestrator accepts the run (`started`).
  - `timeout` (optional): [`Duration`](type-duration.md)

    How long the Effect waits: by default 30s for the start, and 10m with `wait`.
  - `target` (optional): `{ url: string; token: NamedSecret; } | typeof open`

    A different Orchestrator: `url` is its origin, and `token` is the secret that holds its
    Instance token. Or `open`, which the composer binds with `customize(<import>, { effects: {
    <slot>: { target: { url, token } } } })`. Without it, the run starts in this Orchestrator.

**Returns**: An Effect, for `actors`.

**Example**

```ts
import { assign } from "xstate";
import { z } from "zod";
import { defineEvent, dispatch, jr2Setup } from "@jr2/orchestrator";

const triageInput = z.object({ request: z.string() });

const report = defineEvent({
  name: "report",
  description: "Report a request. A run of triage starts for it.",
  input: triageInput,
});

export const machine = jr2Setup({
  types: {} as { context: { request: string } },
  events: [report],
  // `wait: true`: the Effect ends when the run of triage ends.
  actors: { triage: dispatch("triage", { input: triageInput, wait: true }) },
}).createMachine({
  id: "intake",
  context: { request: "" },
  initial: "listening",
  states: {
    listening: {
      invoke: { src: "gate" },
      on: { report: { target: "starting", actions: assign({ request: ({ event }) => event.request }) } },
    },
    starting: {
      invoke: {
        src: "triage",
        input: ({ context }) => ({ request: context.request }),
        onDone: [
          { guard: ({ event }) => event.output.outcome === "settled", target: "done" },
          { target: "listening" },
        ],
      },
    },
    done: { type: "final" },
  },
});
```

**See also**

- [Start a run from a run](../../guides/start-a-run-from-a-run.md)
- [Effects](../../concepts/effects.md)
- [ADR-0071: A run starts a run through a `dispatch` Effect](../decisions/0071-a-run-starts-a-run-through-a-dispatch-effect.md)
