# Core concepts

> Jobs, runs, attempts, workflows and steps, schedules, workers and environments: the handful of ideas the rest of the docs build on.

Source: https://galley.themes.dev/docs/getting-started/concepts/

Runnel has a small vocabulary. Once these terms are clear, the guides and the API reference read as variations on them.

## Jobs and runs

A **job** is a named function in your code, such as `send-invoice`, with its options: retries, timeout, concurrency. The name is the job's identity. Keep it stable; renaming a job creates a new one.

A **run** is one execution of a job with one payload. Every call to `enqueue`, every schedule tick and every `POST /v1/runs` creates a run with an id like `run_01JB7X2K9Q4MZ8`. A run moves through these statuses:

| Status | Meaning |
|---|---|
| `queued` | Stored and waiting for a worker, or delayed until `scheduled_for`. |
| `running` | A worker has it and is running an attempt. |
| `retrying` | An attempt failed and the next one is queued after a backoff. |
| `succeeded` | An attempt returned. Its return value is the run's `output`. |
| `failed` | The last attempt failed, or the job threw a fatal error. |
| `cancelled` | Cancelled from the dashboard, the CLI or the API. |

### Attempts

An **attempt** is one try at a run. When an attempt throws or times out, Runnel waits and tries again, up to the job's `retries.attempts`. Attempts belong to the run: they share its id, its payload and its log, and they do not count as extra runs on your bill. See [Retries and timeouts](/docs/guides/retries/).

## Workflows and steps

A **workflow** is a job whose function is split into named **steps**:

```ts
const order = await ctx.step('reserve-stock', () => inventory.reserve(input.items));
await ctx.step('charge-card', () => payments.charge(input.customerId, order.total));
await ctx.step('send-confirmation', () => mailer.confirm(order.id));
```

When a step finishes, its result is stored. If a later step fails and the run is retried, finished steps return their stored result instead of running again, so the card is charged once. Workflows can also wait: `ctx.sleep` for a duration, `ctx.waitForEvent` for something to happen elsewhere. See [Workflows](/docs/guides/workflows/).

## Schedules

A **schedule** creates a run of a job on a cron expression, in a time zone. Schedules are defined in code and registered when you deploy. Each tick is an ordinary run, so retries and logs work the same way. See [Schedules](/docs/guides/schedules/).

## Workers

A **worker** is a process you run that has your jobs loaded. It connects out to Runnel, asks for runs of the jobs it knows, runs them and reports the result. You can run as many workers as you like; Runnel hands each run to one of them. If a worker dies mid-run, its runs are given to another worker and counted as a failed attempt.

> **Note** Runnel only ever stores the payload, the output, the step results and the logs. Your code, your database and your credentials stay on your infrastructure.

## Projects, environments and versions

A **project** groups the jobs of one application. Each project has **environments**, by default `development`, `staging` and `production`, with separate runs, schedules and API keys. Each `runnel deploy` creates a numbered **version** of the job definitions in one environment (`v42`), and every run records the version it was enqueued under.

### Idempotency

Pass an `idempotencyKey` when you enqueue, and Runnel returns the existing run instead of creating a second one if the same key arrives again within 24 hours. Use it whenever an enqueue might be repeated, for example from a webhook handler that its sender retries.

```ts
await sendInvoice.enqueue({ invoiceId: 'inv_8812' }, { idempotencyKey: 'invoice:inv_8812' });
```

Steps give you idempotency inside a run; idempotency keys give it to you at the door.
