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

# Workflows from Chat

> What your agent can do with workflows in a conversation - start and steer runs, compose a workflow from a request, schedule it, and pursue a goal on a cadence

## Overview

When workflows are enabled on an agent, the agent gets a set of tools for working with them from a conversation: starting and steering runs of the workflows it already has, putting a workflow on a schedule or giving it a goal, and - when composition is enabled for the agent - composing a new workflow from what the user asked for.

| Group       | Tools                                                                                                                                                                                             |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Runs        | `startWorkflowRun`, `startWorkflowRunBatch`, `getWorkflowRun`, `listWorkflowRuns`, `resumeWorkflowRun`, `signalWorkflowRun`, `resolveWorkflowStep`, `raiseWorkflowRunBudget`, `cancelWorkflowRun` |
| Schedules   | `scheduleWorkflow`, `unscheduleWorkflow`, `describeWorkflow`                                                                                                                                      |
| Goals       | `setWorkflowGoal`, `listWorkflowGoals`, `getWorkflowGoal`, `pauseWorkflowGoal`, `resumeWorkflowGoal`, `closeWorkflowGoal`                                                                         |
| Composition | `composeWorkflow`                                                                                                                                                                                 |

Every tool answers with a result, never with a run's outcome. `startWorkflowRun` returns a handle (`runId` and `queued` or `gated`), and the agent is instructed not to describe what a run produced until `getWorkflowRun` reports it as completed. A start that spends above the organisation's threshold is gated on the user's consent first (`consent: 'ask'`), and every start carries an idempotency key so a retried tool call returns the original run.

<Note>
  Runs started from chat are ordinary runs: they appear in `lua workflows runs` with `trigger: chat`, stream the same events, and are approved, signalled and cancelled the same way. See [Runs and events](/workflows/runs-and-events).
</Note>

## When the agent composes a workflow

A request becomes a composed workflow when it needs **steps that run unattended, wait on a person or an event, or outlive the reply** - an approval in the middle, one job over many items in parallel, a wait for a signal or a delay, a recurring cadence, or a specialist-role review - whether or not the user says "workflow". Requests the agent answers inline instead: a single answer, a short chain of tool calls it can finish in the same reply, or a clarifying question.

| Request                                                                                        | What the agent does                                                                                 |
| ---------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| "Draft three taglines, then ask me to approve the best one before writing the launch blurb"    | Composes: two agent steps with an `approval` between them.                                          |
| "Write a one-sentence definition of each of these three topics in parallel, then combine them" | Composes: a `parallel` over three agent steps, then a merging agent step.                           |
| "Read the time, wait 30 seconds, read it again and tell me the difference"                     | Composes: agent, `sleep`, agent.                                                                    |
| "Have a security reviewer check this snippet"                                                  | Composes: an agent step on `$self` with a reviewer `role` (when ephemeral specialists are enabled). |
| "Every Monday, summarise last week's tickets"                                                  | Composes with a `name`, then `scheduleWorkflow`.                                                    |
| "What time is it?" / "Summarise this paragraph"                                                | Answers inline.                                                                                     |

The sequence is compose, present, start: the agent calls `composeWorkflow`, fixes every issue the result returns, tells the user the plan and the estimate, and only then calls `startWorkflowRun({ composeId, idempotencyKey })`. **Nothing runs until `startWorkflowRun` is called.** Passing `name` saves the composition as a reusable workflow on the agent (`lua workflows list --all` shows it as `dynamic`); `dryRun` validates and estimates without keeping a draft.

## The node grammar

A composed workflow in `form: 'graph'` is the envelope

```json theme={null}
{ "luaWorkflow": 1, "definition": { "id": "<name>", "inputSchema": { ... }, "graph": [ node, ... ] }, "budget": { "maxCredits": 20 } }
```

where every node carries a `type` and the members that type requires:

| Node            | Shape                                                                   | Notes                                                                                                                                                                                                                           |
| --------------- | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `agent`         | `{ id, agentId, promptTemplate }`                                       | One agent turn. `agentId` is `'$self'` or an agent id the compose result lists under `legalSources`. The instruction is `promptTemplate` and nothing else - there is no `objective`, `instructions`, `inputs` or `name` member. |
| `tool`          | `{ id, toolId, input? }`                                                | One tool call.                                                                                                                                                                                                                  |
| `workflow`      | `{ id, workflowId, input? }`                                            | A child run of a saved workflow.                                                                                                                                                                                                |
| `mapping`       | `{ id, mapConfig }`                                                     | Reshapes data between steps.                                                                                                                                                                                                    |
| `sleep`         | `{ id, duration }`                                                      | A wait, `duration` in milliseconds.                                                                                                                                                                                             |
| `parallel`      | `{ steps: [agent \| tool \| workflow, ...] }`                           | Concurrent arms.                                                                                                                                                                                                                |
| `conditional`   | `{ steps, predicates, exclusive?, otherwise? }`                         | Predicates align by index with `steps`; `exclusive: true` runs only the first true arm.                                                                                                                                         |
| `foreach`       | `{ step, opts: { concurrency? } }`                                      | Runs `step` once per item of an array input.                                                                                                                                                                                    |
| `loop`          | `{ loopType: 'dowhile' \| 'dountil', step, predicate, maxIterations? }` | A loop.                                                                                                                                                                                                                         |
| `approval`      | `{ id, approver, timeoutHours? }`                                       | A human decision. Top level only.                                                                                                                                                                                               |
| `waitForSignal` | `{ id, signal }`                                                        | Waits for a named signal. Top level only.                                                                                                                                                                                       |

Inside a `promptTemplate` two placeholders exist:

* `${initData.<field>}` - a field of the run input
* `${stepResults.<stepId>.text}` - the text an earlier agent step produced

There are no `steps`, `input` or `output` namespaces. A later node reads an earlier one by its id, so a chain of agent steps needs no `mapping` between them. The worked example the tool teaches:

```json theme={null}
{
  "form": "graph",
  "graph": {
    "luaWorkflow": 1,
    "definition": {
      "id": "haiku-word-count",
      "inputSchema": { "type": "object", "properties": { "topic": { "type": "string" } }, "required": ["topic"] },
      "graph": [
        { "type": "agent", "id": "haiku", "agentId": "$self", "promptTemplate": "Write a three-line haiku about ${initData.topic}." },
        { "type": "agent", "id": "count", "agentId": "$self", "promptTemplate": "Count the words in this haiku and answer with the number only: ${stepResults.haiku.text}" }
      ]
    }
  }
}
```

Two node types from the static grammar are not available to a composed definition: `step` (a code step - composed workflows carry no code) and `sleepUntil` (not executed by the engine yet - use `sleep` with a `duration`). When the agent's capabilities list `ephemeralSpecialists: true`, an `agent` node on `'$self'` may add `role: { name, instructions, tools }` to run the turn under a temporary role.

`form: 'script'` composes a JavaScript script instead - loops, budgets and judge panels; see [Script form](/workflows/authoring#script-form).

## Repair hints

`composeWorkflow` validates the definition against the executors the agent may use and returns `issues`, each with a `path` (`graph.0`, `graph.2.steps.1`, `graph.3.otherwise`), a message and a `repair` the agent can apply. The codes a composer meets most:

| Code                              | Cause                                                                                                               | What the message carries                                                                                                                                                      |
| --------------------------------- | ------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `unknown-node-key`                | A member no node of that type has (`objective`, `name`, `inputs`, ...).                                             | The node type, the legal keys, and a hint when the key is a stand-in for `promptTemplate` or for `id`.                                                                        |
| `agent-prompt-template-required`  | An `agent` node without `promptTemplate`.                                                                           | Which member to move the text from, the two placeholder forms, an example using the nearest earlier agent step, and the steps a placeholder may name as `legalSources`.       |
| `template-reference-unresolved`   | A `${...}` placeholder that names nothing upstream, or is outside the placeholder grammar (`${steps['x'].output}`). | The reference and the grammar.                                                                                                                                                |
| `node-type-unsupported-by-engine` | A `sleepUntil` node.                                                                                                | *the engine does not execute `sleepUntil` yet (node "wait") - replace it with a `sleep` node with a `duration` in ms*, with a repair to `{ type: 'sleep', duration: 60000 }`. |
| `code-step-not-allowed`           | A `step` node in a composed definition.                                                                             | -                                                                                                                                                                             |
| `unsupported-node-version`        | A member or node type above the platform's grammar phase (below).                                                   | The phase required.                                                                                                                                                           |
| `invalid-envelope`                | The envelope is not `{ luaWorkflow: 1, definition: { graph: [...] } }`, or a node has no `type`.                    | The legal node types at that position.                                                                                                                                        |

A draft that validated before a grammar change is revalidated when it is started; a draft that no longer validates answers `startIssues` and the agent recomposes it.

### Grammar phases

The platform enables the workflow grammar in phases. A composed definition is accepted from **phase 3**, which is also where the approval and signal nodes exist; on an earlier phase `composeWorkflow` answers `unsupported-node-version`.

| Phase | Adds                                                                                            |
| ----- | ----------------------------------------------------------------------------------------------- |
| 1     | `agent`, `tool`, `workflow`, `mapping`, `sleep`, `parallel`, `conditional`, `foreach`, `loop`   |
| 2     | `role` on an agent node, `toolScope`, `requiredConnections`                                     |
| 3     | `approval`, `waitForSignal`, composed definitions, `otherwise` and `exclusive` on a conditional |
| 4     | Job tier: `tier: 'job'`, `workspace`, `harness`, `jobResources`, `maxTurns`                     |
| 5     | `intervalSeconds` on a loop, `items` on a `foreach`                                             |

The `composeWorkflow` tool description and the repair hints only advertise members the current phase allows.

## Schedules from chat

`scheduleWorkflow` puts a saved workflow on a cadence - a cron expression or `{ cron, timezone }` entries, at most 5 - after the agent has confirmed the exact cadence and timezone with the user. It returns the schedule's id and the next fire time; nothing has run yet. One schedule per workflow: calling it again edits the schedule in place. `describeWorkflow` shows a saved workflow's input schema, its schedules and its steps before the agent starts or schedules it. `unscheduleWorkflow` removes a schedule by id; runs already in flight continue.

A schedule that belongs to a goal (below) is tagged with its `goalId` in `describeWorkflow` and cannot be removed with `unscheduleWorkflow`.

Schedules are also managed over HTTP (`/workflows/{agentId}/schedules`, see [Runs and events](/workflows/runs-and-events#starting-and-listing-runs-over-http)); `lua workflows schedules` is coming in the next CLI release.

## Goals

A **goal** is a standing objective for a workflow: the workflow runs on a cadence, and after every run a **judge** decides whether the objective is met. The goal ends when the judge says done, when it has used its `maxRuns`, or when someone closes it.

### Creating a goal

`setWorkflowGoal` creates the goal and its cadence schedule - nothing runs at that moment. Before calling it the agent confirms the objective and the cadence with its timezone, and asks once about `maxRuns`.

| Field                       | Notes                                                                                                                                                                    |
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `workflowId`                | A saved workflow of the agent (id or name).                                                                                                                              |
| `objective`                 | What done means, in plain language (at most 2000 characters). An agent judge sees it verbatim after every run, so it is written as an acceptance test.                   |
| `judge`                     | Either `{ predicate }` or `{ agentId, schema, role? }` - see below.                                                                                                      |
| `cadence`                   | When runs fire: the `scheduleWorkflow` grammar, at most 5 entries.                                                                                                       |
| `maxRuns`                   | Hard ceiling on runs; the goal pauses itself when reached. Optional - if the user declines to name one it is **3**. The organisation's ceiling applies (100 by default). |
| `budget`, `maxTotalCredits` | Per-run budget applied to every run, and a lifetime credit gate across all of them: a fire that would exceed it pauses the goal instead of running.                      |
| `input`, `initialState`     | Run input and state seed for every fire.                                                                                                                                 |
| `idempotencyKey`            | Required. Reusing it returns the same goal, never a second one.                                                                                                          |

### Judges

| Judge                                    | How it decides                                                                                                                                                                                                                                                                                                                                            |
| ---------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Predicate (`judge.predicate`)            | Deterministic, no agent turn: `{ path, op, value? }` evaluated over the finished run. `path` is a dotted path - `output.<field>`, `state.<key>`, `steps.<stepId>.<field>`, `iteration`, `runsUsed`, `runStatus`; `op` is one of `eq`, `neq`, `gt`, `gte`, `lt`, `lte`, `exists`, `truthy`. For example `{ path: 'output.ready', op: 'eq', value: true }`. |
| Agent (`judge.agentId` + `judge.schema`) | An agent turn after each run. `schema` must declare a boolean `done` at its root (add a `summary` so the user sees why). `agentId: '$self'` judges as the owning agent and then requires a `role`, with the same rules as a specialist step.                                                                                                              |

The agent prefers a predicate whenever done-ness is a field of the run output.

### Reading and steering a goal

| Tool                 | What it does                                                                                                                                                                                       | Refusals                                                                                                                                                                                                             |
| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `listWorkflowGoals`  | The agent's goals, newest first, with an optional `status` or `workflowId` filter: `goalId`, `objective`, `status`, `pauseReason`, `runsUsed` of `maxRuns`, `lastVerdict`, `nextRunAt`. Read-only. | -                                                                                                                                                                                                                    |
| `getWorkflowGoal`    | One goal by `goalId` or by a distinctive substring of its objective, with its iteration runs and their verdicts.                                                                                   | `goal_ambiguous` with candidates when several match; `goal_not_found`.                                                                                                                                               |
| `pauseWorkflowGoal`  | The goal becomes `paused (user)`: cadence fires are skipped until it is resumed, a run in flight finishes, nothing is deleted.                                                                     | `goal_not_active` if it already ended.                                                                                                                                                                               |
| `resumeWorkflowGoal` | Back to `active`; the cadence fires again from its next tick. Nothing completes at the moment of the call.                                                                                         | `goal_exhausted` for a goal that used all its runs (offer a new goal with a higher `maxRuns`); `goal_parked` when the cadence could not be re-armed or its schedule is gone (the goal is parked `paused (strikes)`). |
| `closeWorkflowGoal`  | Final, with an optional note: the cadence is switched off, no further iteration starts, the goal keeps its history. A closed goal cannot be resumed.                                               | `goal_not_active` if it already ended.                                                                                                                                                                               |

A goal's status is one of `active`, `paused`, `done` or `closed`; a paused goal carries a `pauseReason` of `user`, `budget`, `max_runs` or `strikes`. These tools are the only source of a goal's state - the agent reports from them and never reconstructs a verdict from run outputs. Every change appends a `goal.updated` event to the run it concerns.

### Close a goal, do not unschedule its job

A goal's cadence is a schedule on the workflow, and the platform watches it. Deleting that schedule does not stop the goal: it is recorded as a failure of the goal, which is parked `paused (strikes)`. `unscheduleWorkflow` therefore refuses a goal's schedule with `goal_schedule`:

```
Schedule job_... is the cadence of goal wfg_... and was NOT removed — nothing was changed. To stop the goal use
pauseWorkflowGoal (it can come back) or closeWorkflowGoal (final); unscheduling a goal's job is recorded as a
failure of the goal, never as stopping it.
```

Pause a goal the user may want back; close a goal whose objective is met or that the user wants gone for good.

`lua workflows goals` is coming in the next CLI release.

## Related

* [Workflows](/overview/workflows) - the concept
* [Authoring](/workflows/authoring) - the static grammar these nodes correspond to
* [Runs and events](/workflows/runs-and-events) - what a run started from chat looks like
* [Workflows Command](/cli/workflows-command) - `runs`, `status`, `watch` and `approve` for runs the agent started
