lua-cli 3.31.0 โ 3.32.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api-exports.d.ts +416 -103
- package/dist/api-exports.js +1992 -299
- package/dist/api-exports.js.map +1 -1
- package/dist/index.js +5262 -1914
- package/dist/index.js.map +1 -1
- package/dist/voice/test/index.d.ts +54 -54
- package/dist/workflow-builder.d.ts +257 -44
- package/dist/workflow-builder.js +1382 -265
- package/dist/workflow-builder.js.map +1 -1
- package/docs/README.md +2 -2
- package/docs/api/LuaWorkflow.md +44 -28
- package/docs/api/Workflows.md +12 -1
- package/docs/workflows/approvals.md +14 -1
- package/docs/workflows/connections-in-coding-turns.md +1 -0
- package/docs/workflows/correlation-keys.md +1 -0
- package/docs/workflows/git-credentials.md +22 -1
- package/docs/workflows/goals.md +46 -0
- package/docs/workflows/limits.md +6 -0
- package/docs/workflows/recovery.md +6 -2
- package/docs/workflows/replay-local.md +10 -10
- package/docs/workflows/schedules.md +15 -0
- package/docs/workflows/script-form.md +20 -10
- package/docs/workflows/testing-offline.md +25 -20
- package/docs/workflows/workspaces-and-long-steps.md +38 -2
- package/package.json +2 -2
- package/template/examples/workflows/CLAUDE.md +16 -13
- package/template/examples/workflows/pr-review-round.ts +61 -20
- package/template/examples/workflows/provision-tenant.ts +25 -8
- package/template/examples/workflows/refund-approval.ts +30 -17
- package/template/examples/workflows/support-triage.ts +59 -22
- package/template/examples/workflows/ticket-to-pr.ts +125 -46
- package/template/examples/workflows/vendor-invoices.ts +69 -16
- package/template/package.json +1 -1
package/docs/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# lua-cli v3.
|
|
1
|
+
# lua-cli v3.32.2
|
|
2
2
|
|
|
3
|
-
Welcome to the comprehensive API documentation for lua-cli v3.
|
|
3
|
+
Welcome to the comprehensive API documentation for lua-cli v3.32.2. This guide covers every API, class, and function exported by the package.
|
|
4
4
|
|
|
5
5
|
## ๐ Documentation Index
|
|
6
6
|
|
package/docs/api/LuaWorkflow.md
CHANGED
|
@@ -2,29 +2,45 @@
|
|
|
2
2
|
|
|
3
3
|
The workflow primitive: a **durable, multi-step program** you author in TypeScript, compile with `lua compile`, and push with `lua push workflow`. The engine executes the compiled graph server-side โ steps survive restarts, waits hold no compute, and approvals/signals park the run until a person or a webhook answers.
|
|
4
4
|
|
|
5
|
-
> **Execution model โ read this first.**
|
|
5
|
+
> **Execution model โ read this first.** _`execute` re-runs from the top after resume; execution is at-least-once โ dedupe on `occurrenceId`._ `occurrenceId` is `${lineageId}:${stepId}` โ the same key on retry, resume, `retry-step` and a repair run; an effect that must be unique across independent runs needs your own business key (`refund:${ticketId}`). Wrap external effects in `ctx.once(key, fn)`.
|
|
6
6
|
|
|
7
7
|
## Import
|
|
8
8
|
|
|
9
9
|
```typescript
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
createStep,
|
|
12
|
+
createWorkflow,
|
|
13
|
+
defineWorkflow,
|
|
14
|
+
step,
|
|
15
|
+
stepOf,
|
|
16
|
+
eq,
|
|
17
|
+
gt,
|
|
18
|
+
gte,
|
|
19
|
+
lit,
|
|
20
|
+
template,
|
|
21
|
+
fromInit,
|
|
22
|
+
fromStep,
|
|
23
|
+
fromKnowledge,
|
|
24
|
+
rows,
|
|
25
|
+
env,
|
|
26
|
+
} from 'lua-cli';
|
|
11
27
|
```
|
|
12
28
|
|
|
13
29
|
## `createStep(config)`
|
|
14
30
|
|
|
15
31
|
A typed code step. `inputSchema` / `outputSchema` / `resumeSchema` are zod schemas; the compiler serializes them to JSON Schema.
|
|
16
32
|
|
|
17
|
-
| Field
|
|
18
|
-
|
|
19
|
-
| `id`
|
|
20
|
-
| `inputSchema` / `outputSchema`
|
|
21
|
-
| `timeoutSeconds`
|
|
22
|
-
| `retry`
|
|
23
|
-
| `sideEffects`
|
|
24
|
-
| `onError`
|
|
25
|
-
| `requiredConnections`
|
|
26
|
-
| `tier` / `workspace` / `jobResources` / `jobTools` | Job-tier fields โ see [Workspaces and long steps](../workflows/workspaces-and-long-steps.md)
|
|
27
|
-
| `execute(ctx)`
|
|
33
|
+
| Field | Meaning |
|
|
34
|
+
| -------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
35
|
+
| `id` | Unique step id (referenced by `then`, containers, `fromStep`) |
|
|
36
|
+
| `inputSchema` / `outputSchema` | zod; outputs are validated after every attempt |
|
|
37
|
+
| `timeoutSeconds` | Per-attempt wall (worker tier โค 600; `tier:'job'` up to 86 400) |
|
|
38
|
+
| `retry` | `{ maxAttempts, backoffSeconds?, backoff?: 'fixed'\|'exponential', maxBackoffSeconds? }` โ waits `backoffSeconds ยท 2^(attempt-1)` capped at `maxBackoffSeconds` |
|
|
39
|
+
| `sideEffects` | `'external'` โ never auto-retried on a platform-fault reclaim โ the step **parks** instead (see [When a step parks](../workflows/recovery.md)) |
|
|
40
|
+
| `onError` | What the **final** failure does to the run: `'fail'` (default โ the run unwinds `failed`) ยท `'continue'` (the run goes on; the step's result is the **continued-failure value** `{ __lua_workflow:'continued_failure', failed:true, error:{code,message}, text:'' }` โ `${stepResults.<id>.text}` renders `''`, `getStepResult(id)` returns it, a `conditional` can branch on `stepResults.<id>.failed`) ยท `'park'` (the step parks on an exception gate for `retry-step` / `resolve-step` โ see [When a step parks](../workflows/recovery.md)) |
|
|
41
|
+
| `requiredConnections` | Connection ids that must mount before `execute` runs |
|
|
42
|
+
| `tier` / `workspace` / `jobResources` / `jobTools` | Job-tier fields โ see [Workspaces and long steps](../workflows/workspaces-and-long-steps.md) |
|
|
43
|
+
| `execute(ctx)` | `ctx`: `inputData`, `resumeData`, `getInitData()`, `getStepResult(id)`, `state`, `suspend()`, `bail()`, `bailRun()`, `once()` (worker tier โ not on the Job tier yet, LUA-706), `log()`, `env`, `signal`, `runtime`, `artefacts`, `occurrenceId`, `lineageId`, `workspace?` (Job tier; `exec?` / `$?` with it) |
|
|
28
44
|
|
|
29
45
|
`ctx.suspend(payload)` parks the step for `Workflows.resume(runId, stepId, resumeData)`; on resume **`execute` re-runs from the top** with `ctx.resumeData` set.
|
|
30
46
|
|
|
@@ -38,21 +54,21 @@ Config: `name`, `description?`, `inputSchema`, `outputSchema?`, `budget?` (`maxC
|
|
|
38
54
|
|
|
39
55
|
**The call site places.** Every builder call โ `then`, `agentStep`, `specialistStep`, `toolStep`, `map`, `approval`, `waitForSignal`, `sleep`, `sleepUntil`, `foreach`, `parallel`, `branch`, `switch`, `dowhile`, `dountil`, `workflow` โ appends **exactly one** entry to the chain where it is called. Containers take `StepRef`s: an inline step object, or a **string** naming an `agentStep`/`specialistStep`/`toolStep` declared elsewhere in the same chain โ a string ref inside a container means "declare here, inside me" and does not append a second top-level entry. An id declared but never placed is `WORKFLOW_UNPLACED_STEP` (warning locally, error at push); referenced but never declared is `unknown-step-ref` at `.commit()`.
|
|
40
56
|
|
|
41
|
-
| Verb
|
|
42
|
-
|
|
43
|
-
| `.then(step, input?)`
|
|
44
|
-
| `.agentStep(id, { agentId, prompt, outputSchema?, toolScope?, tier?, โฆ })`
|
|
45
|
-
| `.specialistStep(id, { role, prompt, โฆ })`
|
|
46
|
-
| `.toolStep(id, { toolId, input? })`
|
|
47
|
-
| `.map(descriptors, { id })`
|
|
48
|
-
| `.parallel([...arms], { merge? })`
|
|
49
|
-
| `.switch([[predicate, armRef]...], otherwise?)` / `.branch(...)`
|
|
50
|
-
| `.foreach(step, { concurrency?, maxItems?, chunk?, rateLimit? })` / `.foreach({ items })`
|
|
51
|
-
| `.dowhile(ref, predicate, { maxIterations, intervalSeconds? })` / `.dountil(...)`
|
|
52
|
-
| `.sleep(ms)` / `.sleepUntil(template)`
|
|
53
|
-
| `.approval(id, { title, approver, timeoutHours, onTimeout, editable?, editablePaths?, itemsPath?, โฆ })` | Human gate โ see [Approvals](../workflows/approvals.md)
|
|
54
|
-
| `.waitForSignal(id, { signal, schema?, timeoutHours, acceptedSources? })`
|
|
55
|
-
| `.workflow(id, ref, input?, { workspace? })`
|
|
57
|
+
| Verb | Entry |
|
|
58
|
+
| ------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
59
|
+
| `.then(step, input?)` | Sequential step |
|
|
60
|
+
| `.agentStep(id, { agentId, prompt, outputSchema?, toolScope?, tier?, โฆ })` | Agent turn (`prompt` is a `template(...)`) |
|
|
61
|
+
| `.specialistStep(id, { role, prompt, โฆ })` | Ephemeral role on the owning agent (D25) |
|
|
62
|
+
| `.toolStep(id, { toolId, input? })` | One tool call โ `toolId` must name a `LuaTool` the compiler can see as a tool primitive (declared in its own module and exported, or registered on the agent); a tool object defined inline in the workflow file is not detected and fails `WORKFLOW_TOOL_REF_UNRESOLVED` at compile |
|
|
63
|
+
| `.map(descriptors, { id })` | Data reshaping โ `id` is **required** once the workflow has โฅ 2 maps (`map-id-required`) |
|
|
64
|
+
| `.parallel([...arms], { merge? })` | Concurrent arms; a `[map, step]` pair is a legal arm (lowered to an implicit subrun) |
|
|
65
|
+
| `.switch([[predicate, armRef]...], otherwise?)` / `.branch(...)` | Conditional (exclusive / inclusive) |
|
|
66
|
+
| `.foreach(step, { concurrency?, maxItems?, chunk?, rateLimit? })` / `.foreach({ items })` | Fan-out over an upstream array |
|
|
67
|
+
| `.dowhile(ref, predicate, { maxIterations, intervalSeconds? })` / `.dountil(...)` | Loop |
|
|
68
|
+
| `.sleep(ms)` / `.sleepUntil(template)` | Engine-side waits โ `sleepUntil` lowers to an `<id>_at` mapping + `sleepUntil{dateFrom}` (D6-r1) |
|
|
69
|
+
| `.approval(id, { title, approver, timeoutHours, onTimeout, editable?, editablePaths?, itemsPath?, โฆ })` | Human gate โ see [Approvals](../workflows/approvals.md) |
|
|
70
|
+
| `.waitForSignal(id, { signal, schema?, timeoutHours, acceptedSources? })` | External event via `Workflows.signal` โ completes with `{ payload, source: { kind, id, principalId? }, signalId, receivedAt }`; payload keys named like a credential (`token`, `secret`, `apiKey`, `accessToken`, โฆ) are persisted `[REDACTED]` (the `signal-reserved-key` warning says so at push / compose) โ see [Reserved payload keys](Workflows.md#reserved-payload-keys) |
|
|
71
|
+
| `.workflow(id, ref, input?, { workspace? })` | Child run the parent waits for |
|
|
56
72
|
|
|
57
73
|
### Typed predicates
|
|
58
74
|
|
package/docs/api/Workflows.md
CHANGED
|
@@ -67,7 +67,17 @@ Resumes a step suspended with `ctx.suspend(...)`. The loser of a resume race rec
|
|
|
67
67
|
|
|
68
68
|
### `Workflows.signal(runId, name, payload?, opts?)`
|
|
69
69
|
|
|
70
|
-
Delivers a named signal to a run waiting on `waitForSignal(name)`. `opts.dedupeKey` makes a re-send a no-op (`{ accepted: true, duplicate: true }`). Returns `{ accepted, reason?: 'source_not_accepted' | 'duplicate' | 'parked' }`.
|
|
70
|
+
Delivers a named signal to a run waiting on `waitForSignal(name)`. `opts.dedupeKey` makes a re-send a no-op (`{ accepted: true, duplicate: true }`). Returns `{ accepted, reason?: 'source_not_accepted' | 'duplicate' | 'parked' }`. The waiting step completes with `{ payload, source: { kind, id, principalId? }, signalId, receivedAt }` โ read the payload as `stepResults.<id>.payload`.
|
|
71
|
+
|
|
72
|
+
#### Reserved payload keys
|
|
73
|
+
|
|
74
|
+
The run ledger redacts by **key name**, not by value: a payload member whose key is one of
|
|
75
|
+
|
|
76
|
+
`secret`, `token`, `password`, `passwd`, `pwd`, `passphrase`, `api_key`, `apikey`, `access_key`, `secret_key`, `private_key`, `client_secret`, `authorization`, `auth_token`, `access_token`, `id_token`, `refresh_token`, `session_key`, `credential`, `credentials`
|
|
77
|
+
|
|
78
|
+
is persisted as `[REDACTED]` whatever it holds. A key matches when one of those names is a whole segment of it โ segments split on `_`, `-`, `.` and spaces, case-insensitive (`token`, `user_token`, `x-token`, `Token`; the compounds also as one camelCase word: `apiKey`, `refreshToken`, `accessToken`) โ while a name glued to another word (`tokens`, `tokenCount`, `accessTokenExpiry`) is not. A member whose value is an object or array is walked, not replaced whole.
|
|
79
|
+
|
|
80
|
+
This is by design (a credential must never land in a run), so the placeholder is what `stepResults.<id>.payload`, the run output, `lua workflows status --steps`, exports and the chat run card show โ a workflow cannot read a data field with one of these names back. Name data fields otherwise (`ticketRef`, `handle`, `code`, `verifier`). The same rule applies to every step output and suspend payload. A `waitForSignal` whose `schema` declares a property with a reserved name draws the non-blocking `signal-reserved-key` warning at push / compose (the property, the placeholder it would read as, the rename), and `lua workflows signal` warns before sending a payload that carries a reserved key (the signal is still delivered).
|
|
71
81
|
|
|
72
82
|
### Reserved members
|
|
73
83
|
|
|
@@ -107,4 +117,5 @@ lua test workflow outreach --input @leads.json \
|
|
|
107
117
|
## Related
|
|
108
118
|
|
|
109
119
|
- `lua workflows` โ list, start, runs, status, watch, attach, cancel, resume, approve, signal, replay, logs, delete โ `attach` re-attaches to a running run's event stream (the same stream your chat card re-attaches to)
|
|
120
|
+
- `lua workflows goals <list|get|create|pause|resume|close>` and `lua workflows schedules <list|delete>` โ the operator surface for goals (R57โR62) and schedule Jobs; `lua workflows view --json` carries `schedules` (goal-owned rows tagged `goalId`) and `goals`. See [workflows/goals.md](../workflows/goals.md) and [workflows/schedules.md](../workflows/schedules.md).
|
|
110
121
|
- [LuaWorkflow](./LuaWorkflow.md) โ the builder primitive
|
|
@@ -2,7 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
_Source of truth: workflows-spec 06 ยง6.4. This page is the developer summary; the spec is normative._
|
|
4
4
|
|
|
5
|
-
`.approval(id, cfg)` parks the run until a person decides. The node completes with
|
|
5
|
+
`.approval(id, cfg)` parks the run until a person decides. The node completes with the decision as data โ branchable, never an exception. A human decision completes it with
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
{
|
|
9
|
+
approved: boolean;
|
|
10
|
+
note?: string; // the approver's note, when one was left
|
|
11
|
+
editedPayload?: unknown; // only when the approver edited (see below)
|
|
12
|
+
editRevision: number; // 0 when the payload was never edited
|
|
13
|
+
decidedBy: { id?: string; kind: string }; // who decided
|
|
14
|
+
evidence?: string[]; // decision artefact ids frozen with the decision
|
|
15
|
+
}
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
A timeout that ends the chain in `'deny'` completes it with the sweep's shape instead โ `{ approved: false, timedOut: true, escalations: <hops> }` (no `decidedBy`, `editRevision` or `evidence`); `'continue'` is treated as `'deny'` for approvals; `'cancel-run'` cancels the run. The original payload is not echoed back โ read it from the step's own input (`stepResults`), not from the output.
|
|
6
19
|
|
|
7
20
|
## Approver specs
|
|
8
21
|
|
|
@@ -4,6 +4,7 @@ _Source of truth: workflows-spec 05 ยง5.17.6 / 11 ยง11.5. This page is the devel
|
|
|
4
4
|
|
|
5
5
|
`toolScope.connectionIds` on a Job-tier agent step mounts MCP connections into the coding turn **through a local proxy**: the model calls `tools/list` / `tools/call`; the proxy holds the token โ the model never sees it.
|
|
6
6
|
|
|
7
|
+
- `requiredConnections` (and `workspace.credentialsRef`) accept a declared `connections[].key` instead of an id โ declare a key; it resolves against the owner agent's connections on any agent at run time (see [Git credentials](./git-credentials.md)).
|
|
7
8
|
- A tool that needs approval is **refused inside the turn** (`mcp_call_denied`) โ hand it to a worker-tier step where the approval card can park the run.
|
|
8
9
|
- Calls are capped per segment; the cap and usage appear in the step detail.
|
|
9
10
|
- Offline: connections are **not mounted** (no proxy, no token mint) โ `--mcp <connectionId>=@fixture.json` stubs `tools/list` / `tools/call` from a `{ tools:[โฆ], calls:{ [tool]: result } }` fixture; an unstubbed call returns the production `mcp_call_denied{reason:'not_in_scope'}` shape.
|
|
@@ -6,6 +6,7 @@ _Source of truth: workflows-spec (P1-3 / P1-14). This page is the developer summ
|
|
|
6
6
|
|
|
7
7
|
- `Workflows.signalByKey(nameOrId, correlationKey, name, payload?)` delivers a signal **by business key** โ no runId bookkeeping in your webhook; `allowMultiple` fans out when several runs share the key.
|
|
8
8
|
- `Workflows.list({ correlationKey })` / `lua workflows runs --correlation-key` find the run(s).
|
|
9
|
+
- A signal payload member named like a credential (`token`, `secret`, `password`, `apiKey`, `authorization`, `credentials`, โฆ) is persisted `[REDACTED]` by design and cannot be read back by the workflow โ name data fields otherwise; the full list and the rule are under [Reserved payload keys](../api/Workflows.md#reserved-payload-keys). `lua workflows signal` warns before sending one.
|
|
9
10
|
- `tags` are free-form labels (AND-ed in list filters); `replyTo: { channel, threadId }` routes the terminal reply back to the originating conversation.
|
|
10
11
|
|
|
11
12
|
Offline: `--correlation-key`, `--tag`, `--reply-to <channel>:<threadId>` and `--on-behalf-of` stamp `ctx.runtime.*`; the would-be reply prints on the terminal instead of sending.
|
|
@@ -6,6 +6,27 @@ _Source of truth: workflows-spec 11 ยง11.5.6 / 05 ยง5.17.3. This page is the dev
|
|
|
6
6
|
|
|
7
7
|
> **Never put a PAT in `env`.** Rotation or revocation of the underlying connection makes the next mint fail: the step fails `credentials_revoked{git_token_mint_failed}` and โ with `onError:'park'` โ enters the park/`retry-step` loop, so a re-grant resumes the run without losing work.
|
|
8
8
|
|
|
9
|
+
## Declare a key; it resolves on any agent
|
|
10
|
+
|
|
11
|
+
Don't freeze a connection id in source. Declare the connection once on `createWorkflow` and reference it by key:
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
createWorkflow({
|
|
15
|
+
name: 'ticket-pilot',
|
|
16
|
+
connections: [{ key: 'github', integrationType: 'github', required: true }],
|
|
17
|
+
workspace: { kind: 'git', repo: 'acme/app', credentialsRef: 'github' },
|
|
18
|
+
// โฆ
|
|
19
|
+
});
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
At run time the engine resolves `github` against the **owner agent's own connections** โ an agent-scoped GitHub connection first, then an org-scoped one; a user's personal connection is never picked. The same definition therefore runs unchanged on a template install (where the install binding still wins), on a hand-built agent and on a duplicated agent โ no "two builds of one workflow", no compile-time env var. `requiredConnections` on a step accepts declared keys the same way.
|
|
23
|
+
|
|
24
|
+
- The resolution is recorded once per run as `connection.resolved {key, connectionId, scope}`; `lua workflows status <runId> --steps --json` shows it under `connections.resolved`, beside `connections.declared` (the keys the version declares) โ a declared key missing from `resolved` never resolved on that run.
|
|
25
|
+
- A step that fails before the key resolves carries the reason on `error` (`message`, `providerStatus`, `providerMessage` and the engine's `detail` bag โ e.g. `detail.connectionId` names the ref the git mint was asked for).
|
|
26
|
+
- Two connections of the same type on the agent are ambiguous โ label the one to use with the key (its display name) or the step fails `credentials_unresolved` listing the candidates.
|
|
27
|
+
- No connection of that type at all โ `credentials_unresolved` (non-retryable, no turn billed) and a notice naming the key and the integration to connect; with `onError:'park'` the run resumes through `retry-step` once it is connected. A revoked or unhealthy connection still fails `credentials_revoked` exactly as before.
|
|
28
|
+
- A literal connection id keeps working exactly as it does today. A key that is not declared โ with or without a `connections` block โ fails `lua compile` and `lua push` with `connection-key-undeclared`, which names the declaration to add.
|
|
29
|
+
|
|
9
30
|
- `jobTools:['gh']` is what mints `GH_TOKEN` for a code step (`pull_requests:write` + `contents:write` + `issues:read`, repo-scoped). Merge is a contents write โ human-only-merge orgs keep `contents:write` off the App.
|
|
10
|
-
- Templates
|
|
31
|
+
- Templates declare `connections[].key` too: an install binds the key to the installer's connection; a definition installed without a binding falls back to the run-time resolution above.
|
|
11
32
|
- Offline, `credentialsRef` is printed and **not** resolved (no token mint) โ `--credentials-ref-ok` suppresses the notice; the local driver uses your own git credentials.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Goals
|
|
2
|
+
|
|
3
|
+
_Source of truth: workflows-spec 05 ยง5.20 / 09 ยง9.5.9. This page is the developer summary; the spec is normative._
|
|
4
|
+
|
|
5
|
+
A goal runs a workflow again and again until a judge says it is done: `objective` is what "done" means, `judge` decides it after every iteration (a deterministic `predicate` over the run's `{ output, state, steps.<stepId>, iteration, runStatus }`, or a judge agent answering a JsonSchema with a boolean `done`), `cadence` is when the next iteration starts (a schedule Job the goal owns; none โ the next iteration starts immediately), and `maxRuns` caps the loop. A goal is `active`, `paused` (by you, or by the platform on `budget` / `max_runs` / `strikes`), `done` (the judge said so) or `closed` (by you โ final).
|
|
6
|
+
|
|
7
|
+
Goals are created from chat (`setWorkflowGoal`), from the SDK (`Workflows.setGoal`) and from the CLI; the CLI is the audit surface for all of them.
|
|
8
|
+
|
|
9
|
+
## Verbs
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
lua workflows goals list [<workflow>] [-i <workflow>] [--status active|paused|done|closed] [--limit n] [--cursor c] [--json]
|
|
13
|
+
lua workflows goals get <goalId> [--json] # the goal card + its iteration runs with verdicts
|
|
14
|
+
lua workflows goals pause <goalId> # R60 โ the cadence stays with the goal
|
|
15
|
+
lua workflows goals resume <goalId> # R61 โ no backfill of missed fires
|
|
16
|
+
lua workflows goals close <goalId> [--note <text>] # R62 โ final
|
|
17
|
+
lua workflows goals create <workflow> --objective <text> --max-runs <n> [-v <semver|latest|versionId>] \
|
|
18
|
+
(--judge-predicate '<path> <op> [value]' | --judge-agent <agentId|'$self'> [--judge-role <json|@file>] --schema <json|@file>) \
|
|
19
|
+
[--cadence <cron|json|@file> โฆ] [--timezone <tz>] [--input <json|@file>] \
|
|
20
|
+
[--budget-credits <n>] [--max-total-credits <n>] [--idempotency-key <key>]
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
`lua workflows view <workflow>` prints the workflow's goals (and its schedules); `--json` carries them under `goals` and `schedules`.
|
|
24
|
+
|
|
25
|
+
### `goals create`
|
|
26
|
+
|
|
27
|
+
- `--judge-predicate` is the deterministic judge: `output.signups gte 50`, `output.done truthy`, `state.phase eq "shipped"`. The value is parsed as JSON when it parses (`50` โ number, `true` โ boolean) and kept as a string otherwise; ops are `exists ยท truthy ยท eq ยท neq ยท gt ยท gte ยท lt ยท lte`. A JSON object (`{"path":โฆ,"op":โฆ,"value":โฆ}`) or `@file` works too.
|
|
28
|
+
- `--judge-agent` runs a judge turn instead: another agent by id, or `'$self'` โ the workflow's own agent; quote it, an unquoted `$self` is expanded by the shell to an empty string (`self` is accepted as the same thing) โ then `--judge-role` with the D25 `{ name, instructions, tools }` is required. `--schema` is required for an agent judge and must declare a boolean `done` at the root; the judge answers it and `done` ends the goal.
|
|
29
|
+
- `--cadence` is repeatable (โค 5): a bare cron expression (`'0 9 * * 1'`, `--timezone Europe/London` applies to it) or a JSON slot / array for `interval` (`{"type":"interval","seconds":3600}`) and `once` entries โ a timezone for those goes inside the entry (`--timezone` beside JSON-only cadences is an error). `-v` pins the iteration version (a semver, `latest` or a version id โ resolved like `start -v`); without it every iteration runs the active version. Without a cadence the goal runs `immediate`: each iteration starts as soon as the previous one is judged.
|
|
30
|
+
- `--input` seeds every iteration's run input; `--budget-credits` is the per-run budget; `--max-total-credits` is the lineage-wide gate that pauses the goal (`budget`) when the sum of its runs reaches it.
|
|
31
|
+
|
|
32
|
+
Example:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
lua workflows goals create -i outreach \
|
|
36
|
+
--objective "Reach 50 trial signups from the March cohort" \
|
|
37
|
+
--judge-predicate 'output.signups gte 50' \
|
|
38
|
+
--cadence '0 9 * * 1' --timezone Europe/London \
|
|
39
|
+
--max-runs 12 --input '{"segment":"trial-march"}'
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Errors and exit codes
|
|
43
|
+
|
|
44
|
+
The usual `lua workflows` codes: 0 ok ยท 1 API refusal ยท 2 usage ยท 3 not found. The goal routes' refusals are printed with their hint: `GOAL_NOT_ACTIVE {status}` (only an active goal pauses, only a paused one resumes, a done/closed goal stays closed), `GOAL_CAP {cap}` (close or finish a goal first), `VALIDATION_FAILED {issues}` (each issue with its path โ `goal-judge-schema-missing-done`, `goal-cadence-invalid`, `goal-objective-too-long`, `ephemeral-role-required`, โฆ), `GOAL_MAX_RUNS_INVALID`, `WORKFLOW_NOT_ON_AGENT`.
|
|
45
|
+
|
|
46
|
+
`lua workflows schedules delete` on a goal's cadence Job is refused (`goal_schedule`) โ use `goals pause` / `goals close`; see [schedules.md](./schedules.md).
|
package/docs/workflows/limits.md
CHANGED
|
@@ -4,6 +4,12 @@ _Source of truth: workflows-spec 10 / 16. This page is the developer summary; th
|
|
|
4
4
|
|
|
5
5
|
Per-run: `budget.maxCredits` (over the org's `maxCreditsPerRun` default 250 โ push blocker `budget-exceeds-cap` until an org admin raises it, R23), `budget.maxDurationSeconds`, `budget.maxJobSeconds`. Structural: `foreach` `maxItems` (default 256 โ with `chunk:{size}` the cap bounds CHUNKS), loop `maxIterations` (default 100), nesting depth โค 3, `startBatch` per-item `idempotencyKey`. Push-time caps preflight reports `warnings[]` + `blockers[]`; `lua push --strict-caps` promotes warnings.
|
|
6
6
|
|
|
7
|
+
## Credits
|
|
8
|
+
|
|
9
|
+
`budget.maxCredits` counts agent steps, never tokens. An inline agent step settles a flat **1** credit when it completes; a `tier:'job'` attempt settles a flat **4** at its first claim (a retry is a new attempt; a seat-metered org settles 0 and is metered from the `workflow:job` usage event instead). Before dispatching an agent step the engine checks `remaining = maxCredits โ spent โ reserved` against the step's reserve (1 inline, 4 Job) and parks the run `suspended{gate.kind:'budget'}` โ `nextAction:'raise_budget'`, `lua workflows raise-budget <runId> --credits <n>` โ when it is short. So `maxCredits: 40` buys ten Job-tier attempts, and a `maxCredits` under 4 can never dispatch one.
|
|
10
|
+
|
|
11
|
+
Nothing meters a Job-tier attempt's tokens against the credits while it runs. Each attempt is bounded by its own ceilings: the step wall (`timeoutSeconds`), `maxInputTokens` (input-side tokens โ prompt + cache read + cache creation โ default **4M**, โ $1โ13 per attempt on a Sonnet-class model; 1M..500M), `maxMessages` (default 400) and `maxTurns`. Crossing one ends the attempt `attempt_budget_exhausted` from its last checkpoint (see [Long steps and checkpoints](long-steps-and-checkpoints.md)). Size `maxInputTokens` for what one attempt may cost and `maxCredits` for how many attempts the run may make.
|
|
12
|
+
|
|
7
13
|
## Org pacing
|
|
8
14
|
|
|
9
15
|
`workflowPolicy.pacing` is an org-admin rate limit **per outbound host** shared by every run in the org. A paced step shows as `ready (pacing_deferred โฆ)` and counts against no per-run budget except `maxDurationSeconds`. Code-step `fetch` calls are **not** paced (B26, normative) โ pacing governs `tool`/`http` steps. Only `lua features` / the desktop **Workflows policy** page / R23 can change it.
|
|
@@ -5,7 +5,9 @@ _Source of truth: workflows-spec 01 ยง1.6-r1 / 06 ยง6.3.5. This page is the deve
|
|
|
5
5
|
A **park** is the platform refusing to guess. Two things park a step:
|
|
6
6
|
|
|
7
7
|
1. **`sideEffects:'external'` + a platform fault.** A worker crash, an eviction or a network partition while an external-effect step was in flight means the platform cannot know whether the effect happened (`EFFECT_IN_DOUBT`). **The platform never re-runs an external step on its own** โ the step parks and the run gates `exception`.
|
|
8
|
-
2. **`onError:'park'`.** A final (retries exhausted) failure parks instead of failing the run โ for steps where an operator should decide.
|
|
8
|
+
2. **`onError:'park'`.** A final (retries exhausted, or non-retryable) failure parks instead of failing the run โ for steps where an operator should decide. The row carries `park: { reason: 'onerror_park' }`, the run is `suspended` with `gate.kind: 'exception'`, and the events show `step.parked` + `run.gated`; nothing downstream is skipped. A failure that happened **before dispatch** (`binding_unresolved`, `input_schema_invalid`, `credentials_unresolved`, `credentials_revoked`, `TOOL_REF_UNRESOLVED`, `BUNDLE_UNRESOLVED`) parks the same way โ but a plain `retry-step` re-renders the same input against the same definition and connections and parks again unless one of those was fixed in between; the retry response carries `detail.hint` saying so, and `resolve-step` is the way past it.
|
|
9
|
+
|
|
10
|
+
If a **sibling** step fails under `onError:'fail'` while the run is parked, the run keeps the park but is marked `failing`: after `retry-step` the parked step still re-runs, and the run then terminalizes `failed` (never `completed`) once nothing is in flight.
|
|
9
11
|
|
|
10
12
|
## The three verbs
|
|
11
13
|
|
|
@@ -15,13 +17,15 @@ A parked step waits for exactly one of:
|
|
|
15
17
|
- **`resolve-step`** โ you supply the step's output (validated against `outputSchema`); the run continues as if the step had returned it.
|
|
16
18
|
- **`skip`** โ marks the row skipped; downstream bindings render `status="skipped"`.
|
|
17
19
|
|
|
20
|
+
`onError:'continue'` never parks: the step is `failed` on the ledger, the run goes on, and every successor sees the step's **continued-failure value** `{ __lua_workflow:'continued_failure', failed:true, error:{code,message}, text:'' }` under `stepResults.<id>` (the default input, `${stepResults.<id>.text}` โ `''`, `getStepResult(id)`, a `conditional` on `stepResults.<id>.failed`, and the parallel / foreach join entry). A `retry-step` on such a row while the run is still running re-arms it, but a successor that already consumed the value is not re-run.
|
|
21
|
+
|
|
18
22
|
`fail` (or cancelling the run) applies the step's failure as-is. From the CLI: `lua workflows status <runId>` shows the park, `lua workflows resume` / the desktop inbox carry the verbs.
|
|
19
23
|
|
|
20
24
|
## Repair runs
|
|
21
25
|
|
|
22
26
|
When a whole run is beyond per-step repair, a **repair run** re-drives the graph seeded from the ledger (the ยง05 ยง5.5.4.1 seeding math โ the same `seedLedgerFromRun` the CLI's `--from-run` uses): every `completed` step keeps its outputs, attempts and settled effects, and the frontier restarts at the first non-completed step. Because `occurrenceId` is stable across retry, resume, `retry-step` **and** a repair run, an external effect executes at most once per occurrence.
|
|
23
27
|
|
|
24
|
-
> **The rule to remember:**
|
|
28
|
+
> **The rule to remember:** _execution is at-least-once โ dedupe on `occurrenceId`_; an effect that must be unique across **independent** runs needs your own business key (`refund:${ticketId}`).
|
|
25
29
|
|
|
26
30
|
## Rehearsing offline
|
|
27
31
|
|
|
@@ -8,10 +8,10 @@ _Source of truth: workflows-spec 03 ยง3.10, 04 ยง4.3.5, 05 ยง5.7.1, 12 ยง12.9.11
|
|
|
8
8
|
|
|
9
9
|
Two options were on the table:
|
|
10
10
|
|
|
11
|
-
| Option
|
|
12
|
-
|
|
|
13
|
-
| **A โ project-local runtime, resolved lazily**
|
|
14
|
-
| B โ a published replay runtime (`@lua/workflow-replay`) | Extract the wrapper source + `createSandboxContext` into a small published package the CLI depends on.
|
|
11
|
+
| Option | Shape | Verdict |
|
|
12
|
+
| ------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
13
|
+
| **A โ project-local runtime, resolved lazily** | `@lua/sandbox-runtime` is NOT bundled into the published CLI. The verb resolves it with `createRequire(<project>/package.json)` at call time; a miss is the typed error `REPLAY_RUNTIME_UNAVAILABLE` (exit 3), never a silent "stable". | **Chosen.** |
|
|
14
|
+
| B โ a published replay runtime (`@lua/workflow-replay`) | Extract the wrapper source + `createSandboxContext` into a small published package the CLI depends on. | Deferred: it would fork the wrapper text away from the runner (the ยง2.2 parity rule says one implementation), and the wrapper is versioned by `journalProtocolVersion` (N-1 window, 04 ยง4.3.5) โ a second package would have to track that window. Revisit at GA if partner projects outside the monorepo need `replay --local`. |
|
|
15
15
|
|
|
16
16
|
Why A: the wrapper drags the whole runtime plane (`@lua/sandbox-runtime` โ `@lua/shared-sandbox` polyfills, artefact/dataset contexts) into the CLI bundle otherwise; every project that can run a workflow-script replay today is a monorepo/dev checkout that already has the runtime in `node_modules`; and resolving from the **project** rather than the CLI guarantees the wrapper version matches the runner the project deploys.
|
|
17
17
|
|
|
@@ -19,15 +19,15 @@ Why A: the wrapper drags the whole runtime plane (`@lua/sandbox-runtime` โ `@l
|
|
|
19
19
|
|
|
20
20
|
- Inside the monorepo (or any project that lists `@lua/sandbox-runtime` in its devDependencies): `replay --local` and `lua test workflow <script>` work as-is.
|
|
21
21
|
- Elsewhere: `REPLAY_RUNTIME_UNAVAILABLE: replay --local needs @lua/sandbox-runtime resolvable from the project (โฆ)` โ add the devDependency, or run the verb from a monorepo checkout with `--project <dir>`.
|
|
22
|
-
- The graph-form `replay --local` (R4/R5 โ `replayLedger`) has no runtime dependency and always works.
|
|
22
|
+
- The graph-form `replay --local` (R4/R5 โ `replayLedger`) has no runtime dependency and always works. It re-evaluates every derived row over the same ancestor context the engine used โ including a `failed` ancestor under `onError:'continue'`, which binds its continued-failure value (`{ __lua_workflow:'continued_failure', failed:true, error, text:'' }`) exactly as the engine did, so a mapping over one replays stable rather than `binding_unresolved`.
|
|
23
23
|
|
|
24
24
|
## Exit matrix (script form)
|
|
25
25
|
|
|
26
|
-
| Exit | Meaning
|
|
27
|
-
|
|
|
28
|
-
| 0
|
|
29
|
-
| 3
|
|
30
|
-
| 4
|
|
26
|
+
| Exit | Meaning |
|
|
27
|
+
| ---- | ------------------------------------------------------------------------------------------------------------ |
|
|
28
|
+
| 0 | stable โ every async journal entry re-issued with the same `callHash` |
|
|
29
|
+
| 3 | runtime unavailable (`REPLAY_RUNTIME_UNAVAILABLE`) or the run/bundle could not be fetched |
|
|
30
|
+
| 4 | `LEDGER_DIVERGENCE` / `JOURNAL_DIVERGENCE` โ at least one `hash_mismatch` / `journal_longer_than_script` row |
|
|
31
31
|
|
|
32
32
|
## Related
|
|
33
33
|
|
|
@@ -9,3 +9,18 @@ _Source of truth: workflows-spec 07 ยง7.1. This page is the developer summary; t
|
|
|
9
9
|
A paused or auto-disabled schedule never replays missed fires by itself; opt in with `schedule.backfillOnEnable: { maxOccurrences }` (or `lua workflows activate --backfill` for one re-enable) and the most recent misses start as one batch, deduplicated against `lua workflows backfill` by the shared `backfill:<workflowId>:<occurrenceIso>` key, capped, and summarised in your inbox.
|
|
10
10
|
|
|
11
11
|
Offline, schedules do not fire (`backfillOnEnable` is not emulated) โ start runs with `lua test workflow` / `lua workflows start`.
|
|
12
|
+
|
|
13
|
+
## From the CLI
|
|
14
|
+
|
|
15
|
+
Schedules are `Job{kind:'workflow'}` rows on the agent; the CLI reads them through `GET /workflows/:agentId/schedules` and removes one through R28.
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
lua workflows schedules list -i outreach # this workflow's schedule Jobs (omit -i for every workflow of the agent)
|
|
19
|
+
lua workflows schedules list --json # { success, data: { items: [{ jobId, workflowId, trigger, paused, nextRunAt, lastFiredAt?, consecutiveFailures, autoDisabled?, goalId? }] } }
|
|
20
|
+
lua workflows schedules delete <jobId> [--yes] # R28 โ asks first unless --yes
|
|
21
|
+
lua workflows view outreach # the "Schedules:" line + table; --json carries the same rows under `schedules`
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Each row shows the trigger (`cron 0 9 * * 1 (Europe/London)` ยท `every 3600s` ยท `once <iso>`), the next fire, the last fire, its status (`active` ยท `paused` ยท `auto-disabled` after the PRO-726 strike limit) and the strike count.
|
|
25
|
+
|
|
26
|
+
**Goal-owned schedules.** A goal's cadence is a schedule Job too; it is tagged with the goal's id (`goalId` in `--json`, the `Goal` column in the table). The CLI refuses to delete it โ `schedules delete` on such a row answers `goal_schedule` (exit 1) without calling the API, the same refusal the chat tool gives โ because unscheduling a goal's job is recorded as a failure of the goal, never as stopping it. Stop the goal instead: `lua workflows goals pause <goalId>` (it can come back) or `lua workflows goals close <goalId>` (final). The server refuses it too: R28 answers `409 GOAL_SCHEDULE {goalId}` for every caller (SDK, desktop, curl), and the CLI renders that with the same message. See [goals.md](./goals.md).
|
|
@@ -5,9 +5,15 @@ _Source of truth: workflows-spec 04 ยง4.3 (D15-r2, Cluster J B12 โ WF-527, WB-
|
|
|
5
5
|
A script workflow is a plain JavaScript ES module โ no TypeScript, no `import`/`require` โ that begins with `export const meta = {โฆ}` and continues with a body run in an async context whose top-level `return` is the run output. The host API (`agent`, `tool`, `parallel`, `foreach`, `sleep`, `approval`, `waitForSignal`, `memo`, `step`, `shell`, `merge`, `log`, `phase`, โฆ) is injected as module-scope bindings; the same file is what the composer produces (`lua workflows export`) and what a template freezes.
|
|
6
6
|
|
|
7
7
|
```js
|
|
8
|
-
export const meta = {
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
export const meta = {
|
|
9
|
+
name: 'judge-panel',
|
|
10
|
+
description: 'Three drafts, three judges, synthesize the winner',
|
|
11
|
+
phases: [{ title: 'Draft' }, { title: 'Judge' }, { title: 'Synthesize' }],
|
|
12
|
+
};
|
|
13
|
+
const drafts = await parallel([
|
|
14
|
+
agent('Draft, technical angle', { phase: 'Draft' }),
|
|
15
|
+
agent('Draft, market angle', { phase: 'Draft' }),
|
|
16
|
+
]);
|
|
11
17
|
const scores = await foreach(drafts, (d) => agent(`Score this draft: ${d}`, { phase: 'Judge' }));
|
|
12
18
|
const picked = await step('pick-winner', () => ({ winner: drafts[scores.indexOf(Math.max(...scores))] }));
|
|
13
19
|
return picked;
|
|
@@ -27,17 +33,21 @@ Run in this order โ the same order lua-api re-validates on push (one implement
|
|
|
27
33
|
|
|
28
34
|
`lua compile` discovers every `src/workflows/*.workflow.script.js` (outside the TypeScript project โ never bundled) and emits `ManifestWorkflow{ form:'script', script, scriptHash, meta, journalProtocolVersion: 1, scriptSurface, roleBindings }` โ the graph members are absent. `lua push` sends `{ form:'script', script, scriptHash, meta, journalProtocolVersion, roleBindings, schedule?, scheduleInput?, budget? }` and skips the bundle upload. The stored row is `WorkflowVersion{ form:'script', dynamic:false }` โ identical to a composed script minus `dynamic:true`. `WORKFLOW_SCRIPT_FORM_NOT_PUSHABLE` is struck (never emitted). An org whose `workflowPolicy.composeForm` is `'graph'` refuses the push with `compose-form-forbidden`, exactly as it refuses a composed script.
|
|
29
35
|
|
|
36
|
+
**The parent's form (LUA-717).** `lua push` creates a workflow the server does not have with `form` taken from the compiled definition (`POST /developer/workflows/:agentId { name, description, form:'script' }`). A workflow's form is fixed by its first version: a version whose form differs from the parent's is refused `400 WORKFLOW_FORM_MISMATCH { workflowForm, versionForm }` โ rename the script, or delete the workflow (`lua workflows delete`) and push again. The one exception is an **empty** parent (no version of any kind, sandbox included), which adopts the form of its first pushed version: a lua-cli older than the LUA-717 release sent no `form` on create, so its script workflows were left as graph shells, and the first script push is what makes such a shell usable.
|
|
37
|
+
|
|
38
|
+
**Script workflows are pushed from the CLI only.** Chat compose (`composeWorkflow`, `POST โฆ/compose`) composes **graph** workflows; a `form:'script'` compose is refused `script-validation-tick-unavailable`, because the WF-323 validation tick is not bound on the production server (by design โ a script is never persisted un-validated). A script that should exist on the server goes through `src/workflows/<name>.workflow.script.js` + `lua push workflow`.
|
|
39
|
+
|
|
30
40
|
## `lua test workflow <name>` on a script
|
|
31
41
|
|
|
32
42
|
Runs the file through the runner's own replay wrapper (parity context) in an offline tick loop: every tick re-runs the script against the journal so far, the wrapper parks with its async intents, the driver settles each with a **fake completion** (no API call) and re-ticks until `done`/`bailed`/`failed`. Steering:
|
|
33
43
|
|
|
34
|
-
| Flag
|
|
35
|
-
|
|
|
36
|
-
| `--input <json
|
|
37
|
-
| `--step-output <label
|
|
38
|
-
| `--max-ticks <n>`
|
|
39
|
-
| `--ledger-out <file>`
|
|
40
|
-
| `--json`
|
|
44
|
+
| Flag | Effect |
|
|
45
|
+
| ----------------------------------- | --------------------------------------------------------------------------------------------------------- |
|
|
46
|
+
| `--input <json\|@file>` | the script's `args` (default `meta.sampleArgs ?? {}`) |
|
|
47
|
+
| `--step-output <label\|seq>=<json>` | the completion for the intent with that `narration.label` (an `agent()`'s `label`/`phase`) or journal seq |
|
|
48
|
+
| `--max-ticks <n>` | default 64 โ `SCRIPT_TICK_LIMIT` (exit 4) beyond it |
|
|
49
|
+
| `--ledger-out <file>` | the local journal (`{ form:'script', journal[] }`) |
|
|
50
|
+
| `--json` | the result envelope |
|
|
41
51
|
|
|
42
52
|
Exit 0 `done`/`bailed`; 4 `failed` (`SCRIPT_THREW`, `SCRIPT_DEADLOCK` โ parked with zero intents, `SCRIPT_TICK_LIMIT`); 3 when `@lua/sandbox-runtime` is not resolvable from the project (`REPLAY_RUNTIME_UNAVAILABLE`, see `replay-local.md`); 2 on flag grammar.
|
|
43
53
|
|
|
@@ -4,34 +4,39 @@ _Source of truth: workflows-spec 03 ยง3.9 (WF-204/WF-225/WF-332). This page is t
|
|
|
4
4
|
|
|
5
5
|
`lua test workflow <name>` (alias `lua workflows run <name>`) drives a compiled workflow **offline**: the same `compilePlan`, predicates and mappings the engine uses, code steps executed in the `lua test` sandbox, agent steps faked by default, waits fast-forwarded on a virtual clock. Exit codes: `0` completed ยท `2` usage/validation ยท `4` failed ยท `5` missing fixture.
|
|
6
6
|
|
|
7
|
+
**No API call.** The verb compiles first, but that compile skips the server โ YAML sync every other `lua compile` performs โ nothing is created on the agent and no run exists on the server. Only `lua push workflow` persists a definition; `lua workflows start` creates a run. The exceptions are the flags that ask for the platform: `--from-run <runId>` (reads the seed run) and `--agents live` (calls the dev API for agent steps).
|
|
8
|
+
|
|
7
9
|
## Steering flags โ fully scriptable, no stdin
|
|
8
10
|
|
|
9
|
-
| Flag
|
|
10
|
-
|
|
11
|
-
| `--input @file\|<json>`
|
|
12
|
-
| `--step-output <id>=@file\|<json>` (rep.)
|
|
13
|
-
| `--approve <id>[=@payload]` ยท `--deny <id>[=@reason]` | Pre-answer approvals (edited payloads use the server's `matchesEditablePath` + `editedPayloadSchema`)
|
|
14
|
-
| `--signal <name>=@file\|<json>` (rep.)
|
|
15
|
-
| `--fixtures <dir>` / `--record <dir>`
|
|
16
|
-
| `--from-run <runId>`
|
|
17
|
-
| `--park <id>` (rep.)
|
|
18
|
-
| `--fast-retries`
|
|
19
|
-
| `--real-time`
|
|
20
|
-
| `--now <iso>`
|
|
21
|
-
| `--artefacts-dir <dir>`
|
|
22
|
-
| `--env KEY=value` (rep.)
|
|
23
|
-
| `--step-wall <s>`
|
|
24
|
-
| `--max-foreach-items <n>`
|
|
25
|
-
| `--agents fake\|live`
|
|
26
|
-
| `--ledger-out <file>`
|
|
27
|
-
| `--json`
|
|
11
|
+
| Flag | Effect |
|
|
12
|
+
| ----------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
13
|
+
| `--input @file\|<json>` | Run input (validated against `inputSchema`) |
|
|
14
|
+
| `--step-output <id>=@file\|<json>` (rep.) | Completes the row **without** running it โ validated against the step's `outputSchema`. This is how a predicate over an agent output gets BOTH truth values under `--agents fake` |
|
|
15
|
+
| `--approve <id>[=@payload]` ยท `--deny <id>[=@reason]` | Pre-answer approvals (edited payloads use the server's `matchesEditablePath` + `editedPayloadSchema`) |
|
|
16
|
+
| `--signal <name>=@file\|<json>` (rep.) | Pre-supply `waitForSignal` payloads in order of arrival |
|
|
17
|
+
| `--fixtures <dir>` / `--record <dir>` | Replay / record agent+tool outputs (`<stepId>.<attempt>.json`); a missing fixture is exit **5** `FIXTURE_MISSING`, never a silent fake; `--step-output` wins over a fixture |
|
|
18
|
+
| `--from-run <runId>` | Seed every `completed` step from a real run (same math as repair runs); graph drift asks for `--force` |
|
|
19
|
+
| `--park <id>` (rep.) | Simulate a platform-fault park of a `sideEffects:'external'` step โ then `retry / skip / complete / fail`, the production verbs (see [When a step parks](./recovery.md)) |
|
|
20
|
+
| `--fast-retries` | Collapse retry backoff waits to 0 |
|
|
21
|
+
| `--real-time` | Actually wait on `sleep`/backoff instead of fast-forwarding the virtual clock |
|
|
22
|
+
| `--now <iso>` | Pin the virtual clock start (resolves `sleepUntil` dateFrom, business-hours deadlines) |
|
|
23
|
+
| `--artefacts-dir <dir>` | Back `ctx.artefacts.*` on disk (`<dir>/<artefactId>` + `<artefactId>.meta.json`, ids `local-<n>`) |
|
|
24
|
+
| `--env KEY=value` (rep.) | The local `env.template()` overlay โ a missing key is exit 2 `env-template-missing`; values never reach `--ledger-out` |
|
|
25
|
+
| `--step-wall <s>` | The cap on every step wall, default 600 โ a node's own `timeoutSeconds` (or its step definition's) applies below it; a step past its wall fails `TIMEOUT` and its retry policy applies; the attempt's `ctx.signal` aborts at the wall โ the driver cannot kill in-process code, so a step that ignores the signal keeps running in the background until it settles (LUA-689) |
|
|
26
|
+
| `--max-foreach-items <n>` | Override the local foreach cap |
|
|
27
|
+
| `--agents fake\|live` | Fake stubs (default) or the dev API |
|
|
28
|
+
| `--ledger-out <file>` | Write the in-memory ledger (steps, outputs, attempts, effects, events) โ the input of `replay --local` |
|
|
29
|
+
| `--json` | Machine-readable result envelope |
|
|
30
|
+
| `--workspace <dir>` | The checkout Job-tier **code** steps run in: gives them `ctx.workspace` and `ctx.exec` / `ctx.$` against that directory โ the pod's allowlist (`git`, `gh`, `npm`, โฆ, argv only, no shell) on your own PATH and git credentials. Without it a Job-tier step's `$` rejects `ExecError{code:'EXEC_UNAVAILABLE'}` naming the flag |
|
|
28
31
|
|
|
29
32
|
## Semantics worth knowing
|
|
30
33
|
|
|
34
|
+
- **`sleep(ms)`** advances the virtual clock by exactly `ms` **milliseconds** โ the SDK's unit and the engine's (`.sleep(3000)` is 3 s, printed as `<id> ยท sleep 3000 ms (3 s) (virtual clock)`); `sleepUntil` jumps to the resolved instant. `--real-time` waits the same `ms`.
|
|
31
35
|
- **Retry backoff** runs on the virtual clock: `backoffSeconds ยท 2^(attempt-1)` capped at `maxBackoffSeconds`, printed as `[retry:<id>] backoff <n>s`.
|
|
32
36
|
- **`foreach` `rateLimit`** is an in-memory token bucket โ delayed starts print `[throttle:<id>] resumeAt=<iso>`; loop `intervalSeconds` prints `kind=loop_interval`.
|
|
33
37
|
- **`suspend`** re-runs `execute` from the top with `resumeData` set โ the documented Mastra semantics, exercised offline.
|
|
34
|
-
- **`ctx.once`** settles against the in-memory effects map: a re-drive over the same ledger sends nothing twice.
|
|
38
|
+
- **`ctx.once`** settles against the in-memory effects map: a re-drive over the same ledger sends nothing twice. (On the platform it is served to worker-tier steps; the Job pod does not serve `ctx.once` yet โ LUA-706 โ so a Job-tier step dedupes on `occurrenceId` itself.)
|
|
39
|
+
- **`ctx.getInitData()`** is `null` on a run without `--input` and **`ctx.runtime`** is `{ trigger:'sdk', principalKind:'user' }` โ the same shapes the pod and the worker serve, so a step that branches on them behaves offline as it does on the platform.
|
|
35
40
|
- **Not emulated offline**: the org pacing policy (no org context โ `pacing_deferred` never appears locally), the runner mid-run kill (Ctrl-C is the local analogue), `backfillOnEnable`, and the `startWorkflowRunBatch` tool.
|
|
36
41
|
|
|
37
42
|
## Example
|