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

# Run workflows

> Start workflow runs from triggers, forms, the CLI, the API, and agent tools.

Workflows can be run in a variety of ways:

| Surface | Best for |
| - | - |
| **Triggers** | The primary way workflows run in production: webhooks, schedules, polls, and app events start runs automatically |
| **Forms** | A public shareable link where people fill in the workflow's inputs and submit |
| **CLI** | Running and inspecting workflows during development |
| **Agent tool** | Letting an agent run a workflow as a single, durable tool |
| **HTTP API** | Invoking a workflow from your own services |

Every run, no matter the surface, validates the input against the workflow's schema and is recorded in [run history](https://docs.os.meetluno.ai/learn/logs/workflow-runs.md).

## Run workflows from triggers

In production, most workflows run from a [trigger](https://docs.os.meetluno.ai/learn/triggers/overview.md). You attach a source (a webhook, schedule, poll, or app event) to a workflow, and each matching event starts a run.

```ts src/triggers/signup.ts theme={null}
import { defineWebhookSource } from "@meetluno/luno/trigger";
import { z } from "zod";
import workflow from "../workflows/signup-pipeline";

export default defineWebhookSource({
  slug: "signup",
  name: "Signup",
  description: "Fires when a new signup is posted.",
  endpoint: "signup",
  payload: z.object({ name: z.string(), email: z.string().email() }),
}).attach({
  workflow,
  transform: (payload) => ({ name: payload.name, email: payload.email }),
});
```

The optional `transform` maps the source payload to the workflow's input. See [triggers](https://docs.os.meetluno.ai/learn/triggers/overview.md) for webhooks, schedules, and polling.

## Run workflows from a form

Publish a [workflow form](https://docs.os.meetluno.ai/learn/workflows/forms.md) to get a public URL. Anyone with the link can fill in the workflow's inputs and start a run — no CLI or API key required.

```bash theme={null}
luno workflows forms publish signup-pipeline --title "Sign up"
luno workflows forms url signup-pipeline
```

You can also publish from the workflow's **Share** menu in the web app. See [workflow forms](https://docs.os.meetluno.ai/learn/workflows/forms.md) for field presentation, unpublish, and link rotation.

## Run workflows from the CLI

The CLI is the most practical way to run a workflow while you build. Deploy your project, then invoke the workflow against the deployed project:

```bash theme={null}
luno deploy --project <slug>   # or --filter workflows/signup-pipeline
luno workflows run signup-pipeline \
  --input '{"name":"Ada","email":"ada@example.com"}' \
  --wait \
  --timeout 60
```

The `signup-pipeline` value is the workflow `slug` from `defineWorkflow()`, and `--input` is JSON validated against the workflow's `input` schema.

`--wait` returns when the run finishes or the bounded timeout elapses. Choose the duration with `--timeout <seconds>`; without it, workflow waits default to 60 seconds. A timeout means the run is still active, not failed. Continue waiting with:

```bash theme={null}
luno workflows runs get signup-pipeline <run-id> --wait --timeout 60
```

List the workflows in the active project:

```bash theme={null}
luno workflows list
```

`luno workflows` runs against your deployed cloud project:

```bash theme={null}
luno workflows run signup-pipeline --input '{"name":"Ada","email":"ada@example.com"}' --wait
```

## Run workflows as an agent tool

Import a workflow into an [agent](https://docs.os.meetluno.ai/learn/agents/overview.md)'s `tools` array and it becomes a tool automatically. The agent calls it like any other tool, and the workflow runs with its normal step recording.

```ts theme={null}
import { defineAgent } from "@meetluno/luno/agent";
import refundOrder from "../workflows/refund-order";

export default defineAgent({
  slug: "support",
  name: "Support",
  description: "Helps customers and can run the refund-order workflow.",
  systemPrompt: "Help customers. To issue a refund, call the refund-order tool.",
  model: "deepseek/deepseek-v4-flash",
  tools: [refundOrder],
});
```

The workflow runs as its own queued child run, linked to the calling agent session. The agent waits for its result, and the child can use durable waits (`.sleep()`, `hookStep`) normally. Child runs count toward your organization's concurrency, but do not incur an additional workflow-run dispatch fee; model, web, and sandbox usage is still metered normally. See [workflows as tools](https://docs.os.meetluno.ai/learn/agents/build-agents.md#workflows-as-tools).

## Run workflows via API

You can also invoke a deployed workflow over HTTP. The route validates the input, enqueues the run, and returns a `runId` to inspect later:

```bash theme={null}
curl -X POST https://<your-project>/api/projects/<project-id>/workflows/signup-pipeline \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <org-api-key>" \
  -d '{ "name": "Ada", "email": "ada@example.com" }'
```

This is mainly useful for wiring workflows into your own services. For most internal automation, triggers and the CLI cover what you need.

## Resume a suspended run

A workflow with a [`hookStep`](https://docs.os.meetluno.ai/learn/workflows/build-workflows#hooks) suspends until something resumes it. The step's `onSuspend` callback receives the `token` and the `resumeUrl`. Resume the run with `POST` for structured payloads, or `GET` for simple link/button flows (Slack, email). No API key or session is required — the hook token in the URL is the credential:

```bash theme={null}
# POST — JSON body (structured payloads)
curl -X POST https://<your-project>/api/projects/<project-id>/hooks/<token>/resume \
  -H "Content-Type: application/json" \
  -d '{ "approved": true }'

# GET — query params (buttons and links)
curl "https://<your-project>/api/projects/<project-id>/hooks/<token>/resume?approved=true"
```

With `POST`, the JSON body becomes the hook step's output and the route returns `202` with the `runId`. With `GET`, query params become the payload (shallow) and the route returns a minimal HTML confirmation page. Query values arrive as strings, but if the hook declared a `schema` they are coerced to the schema's primitive types first — so `?approved=true` resolves to the boolean `true`. Values that can't be coerced (e.g. `?approved=banana`) are rejected with `400` and the run stays suspended.

If the hook declared a `schema`, the resume endpoint validates the payload against it first. An invalid payload is rejected with `400` and the run stays suspended — you can fix the payload and resume again, instead of failing the run. (Validation is structural; the hook step's `schema.parse` remains the authoritative parse for coercion and refinements.)

Hooks with [`responders`](https://docs.os.meetluno.ai/learn/workflows/build-workflows#hooks-answered-by-people-outside-the-project) do not accept the raw token (`403`): each responder answers through the signed link from `responderLinks`, a hosted form at `/api/projects/<project-id>/hooks/respond/<link>`. Expired hooks answer `410` on both routes.

Runs waiting on a hook show **Waiting for a response** in [run history](https://docs.os.meetluno.ai/learn/logs/overview.md). Runs paused by `.sleep()` / `.sleepUntil()` show **Sleeping** and resume on their own when the timer is due, so you don't resume those manually. A parent waiting for queued agent or sub-workflow children shows **Waiting on child runs** and resumes automatically when they reach a terminal outcome.

### Find the resume token for a suspended run

When a run's status is `waiting_hook`, [run detail](#review-workflow-runs) already includes pending hooks (`token`, `status`, and `resumeUrl`) — including lean polls like `luno workflows runs get <run-id> --summary`.

If you need every hook for a run (including resumed ones), or you didn't look at run detail:

```bash theme={null}
luno workflows runs hooks <workflow-key> <run-id>
```

This returns each hook's `token`, `status`, and a ready-to-use `resumeUrl`.

## Restart a run

A finished run can run again under the same run id, so its history stays in one place:

* **Failed run** — after fixing the cause (a missing credential, a down API, a code fix you deployed), restart it. It resumes from the step that failed; steps that already completed keep their results and do not run again.
* **Any finished run, from a step** (time travel) — re-run from a step you choose, optionally with new input for that step. Steps before it keep their results; that step and everything after run again. Hooks after that point ask again instead of reusing old answers.

```bash theme={null}
luno workflows runs restart <workflow> <run-id>
luno workflows runs restart <workflow> <run-id> --step charge-order --input '{"orderId":"A-1","amount":99}'
luno workflows runs get <workflow> <run-id> --wait
```

The API is `POST /workflows/{slug}/runs/{runId}/restart` with `{ "step"?, "input"? }`. A run that is still running, or still has a retry queued, cannot be restarted (`409`).

Only the person who started the run, or a project admin (the project **Admin** role, or an org owner/admin), can restart it (`403` otherwise). Runs nobody started by hand (triggers, schedules) restart only by a project admin. When an admin restarts someone else's run, it runs as the admin, with the admin's own credentials, never as the original user. Restarts are recorded in the audit log.

## Review workflow runs

Every surface that runs a workflow creates a workflow run you can review later. Open **Runs** (`/history`) in the web app and filter **Type** to **Workflow**. The detail panel shows the input, output or error, steps, usage, trigger context, timing, and trace data.

From the CLI, use run commands while debugging:

```bash theme={null}
luno workflows runs list signup-pipeline --status failed
luno workflows runs get signup-pipeline <run-id> --wait --timeout 60
luno workflows runs get <run-id> --summary
luno workflows runs get signup-pipeline <run-id> --include trigger,steps,trace,children
```

Cancel a queued or running run by ID:

```bash theme={null}
luno workflows runs cancel <run-id>
```

See [workflow runs](https://docs.os.meetluno.ai/learn/logs/workflow-runs.md) for the full run history view.

## Next steps

> **[Triggers](https://docs.os.meetluno.ai/learn/triggers/overview.md)**
>
> Attach webhooks, schedules, and polls to start workflows.

> **[Workflow forms](https://docs.os.meetluno.ai/learn/workflows/forms.md)**
>
> Publish a public form that starts a run from a shareable link.

> **[Workflow runs](https://docs.os.meetluno.ai/learn/logs/workflow-runs.md)**
>
> Inspect input, output, steps, errors, and traces.

> **[Test workflows](https://docs.os.meetluno.ai/learn/workflows/test-workflows.md)**
>
> Run workflows in tests before deploying changes.

> **[Deploy a project](https://docs.os.meetluno.ai/learn/projects/deploy-a-project.md)**
>
> Ship workflow changes to the platform.


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