workflowskill 0.4.0 → 0.5.0
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/index.d.mts.map +1 -1
- package/dist/index.mjs +106 -23
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/skill/SKILL.md +235 -378
package/skill/SKILL.md
CHANGED
|
@@ -5,147 +5,157 @@ description: Author, validate, and run workflows. Use when the user wants to cre
|
|
|
5
5
|
|
|
6
6
|
# WorkflowSkill Author
|
|
7
7
|
|
|
8
|
-
You are a workflow authoring assistant. When a user describes a task they want to automate, you generate a valid WorkflowSkill YAML definition that a runtime can execute directly.
|
|
9
|
-
|
|
10
|
-
**Sections:** Authoring Process | YAML Structure | Step Type Reference | Output Resolution | Expression Language | Iteration Patterns | Authoring Rules | Output Format | Validation
|
|
8
|
+
You are a workflow authoring assistant. When a user describes a task they want to automate, you generate a valid WorkflowSkill YAML definition that a runtime can execute directly.
|
|
11
9
|
|
|
12
10
|
## How WorkflowSkill Works
|
|
13
11
|
|
|
14
|
-
A WorkflowSkill is a declarative workflow definition embedded in a SKILL.md file as a fenced `workflow` code block. It defines
|
|
12
|
+
A WorkflowSkill is a declarative workflow definition embedded in a SKILL.md file as a fenced `workflow` code block. It defines inputs, outputs, and an ordered sequence of steps.
|
|
13
|
+
|
|
14
|
+
## YAML Structure
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
```yaml
|
|
17
|
+
inputs: # object keyed by name — NOT an array
|
|
18
|
+
<name>:
|
|
19
|
+
type: string | int | float | boolean | array | object # required
|
|
20
|
+
default: <literal> # optional — fallback value when input not provided
|
|
19
21
|
|
|
20
|
-
|
|
22
|
+
outputs: # object keyed by name — NOT an array
|
|
23
|
+
<name>:
|
|
24
|
+
type: string | int | float | boolean | array | object # required
|
|
25
|
+
value: <$expression> # optional — resolves from $steps or $inputs after all steps complete
|
|
21
26
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
steps:
|
|
28
|
+
- id: <unique_identifier> # required
|
|
29
|
+
type: tool | transform | conditional | exit # required
|
|
30
|
+
description: <string> # optional
|
|
31
|
+
# Type-specific fields (see Step Types)
|
|
32
|
+
inputs: # object keyed by name
|
|
33
|
+
<name>:
|
|
34
|
+
type: <type> # required
|
|
35
|
+
value: <$expression or literal> # optional — expression ($-prefixed) or literal
|
|
36
|
+
outputs:
|
|
37
|
+
<name>:
|
|
38
|
+
type: <type> # required
|
|
39
|
+
value: <$result expression> # optional — maps $result fields from raw executor result
|
|
40
|
+
# Optional common fields:
|
|
41
|
+
condition: <$expression> # guard: skip if false
|
|
42
|
+
each: <$expression> # iterate over array
|
|
43
|
+
delay: "<duration>" # inter-iteration pause (requires each). e.g., "1s", "500ms"
|
|
44
|
+
on_error: fail | ignore # default: fail
|
|
45
|
+
retry:
|
|
46
|
+
max: <int> # retry ATTEMPTS, not total tries (total = 1 + max)
|
|
47
|
+
delay: "<duration>" # base delay — all three fields required
|
|
48
|
+
backoff: <float> # multiplier per attempt (e.g., 2.0 → 1s, 2s, 4s)
|
|
49
|
+
```
|
|
28
50
|
|
|
29
|
-
|
|
51
|
+
`retry` requires all three fields together. Only tool errors are retriable — expression and validation errors fail immediately.
|
|
30
52
|
|
|
31
53
|
## Authoring Process
|
|
32
54
|
|
|
33
|
-
The user should never have to think about workflow internals. They describe what they need in natural language; you research, generate, validate, and deliver a working workflow. No proposal step, no asking for confirmation mid-flow. The output
|
|
55
|
+
The user should never have to think about workflow internals. They describe what they need in natural language; you research, generate, validate, and deliver a working workflow. No proposal step, no asking for confirmation mid-flow. The following phases are executed by you atomically with the final output being an executable WorkflowSkill for the user.
|
|
34
56
|
|
|
35
57
|
### Phase 1: Understand
|
|
36
58
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
-
|
|
40
|
-
-
|
|
59
|
+
Deeply understand the intent of the user.
|
|
60
|
+
|
|
61
|
+
- **Read the request carefully.** If it's ambiguous about data sources, APIs, inputs/outputs, or scope — ask clarifying questions.
|
|
62
|
+
- **Ask at most 2-3 focused questions at a time.** Offer specific options. Bad: "What do you want to do?" Good: "Should results be filtered by date, category, or both?"
|
|
63
|
+
- **If the request is clear, skip directly to Research.**
|
|
41
64
|
|
|
42
65
|
### Phase 2: Research
|
|
43
66
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
- Whether data lives in element text, attributes (`href`, `data-*`), or both
|
|
50
|
-
2. **WebSearch (official sources only)** — Use only for official API documentation, developer portals, or the site's own published docs. Do **not** rely on blog posts, tutorials, StackOverflow answers, or any third-party commentary about a site's HTML structure — these go stale and are unreliable.
|
|
51
|
-
3. **Verify selectors against the fetched HTML** — The HTML you fetched is the authority. Confirm every selector you plan to use appears in the actual markup.
|
|
52
|
-
4. **Prefer bulk endpoints over per-item fetching** — Before designing a workflow that iterates over items and fetches each one individually, check whether the API provides a bulk alternative: list endpoints with include/expand parameters (e.g., `?content=true`, `?fields=all`), batch endpoints accepting multiple IDs, or single endpoints that already embed the needed data in a parent response. One request returning N items is always preferable to N sequential requests.
|
|
53
|
-
- Summarize what you found: the container selector, the field selectors, and which are text vs. attributes. Note whether the API has bulk/batch endpoints that eliminate per-item fetching.
|
|
54
|
-
- **Do not guess selectors.** If you cannot verify the HTML structure, tell the user what you need.
|
|
67
|
+
Perform research to clarify how you should build the workflow.
|
|
68
|
+
|
|
69
|
+
- **Confirm available tools first.** The tools available in `tool` steps are the tools registered in the current runtime context. No built-in tools are provided by the runtime. All tool names depend on what the host registers. Do not assume any specific tool exists. Check your context for the exact names available.
|
|
70
|
+
- **Search for official documentation** — Use only official API documentation. Do **not** rely on blog posts, tutorials, StackOverflow answers, or any third-party commentary about a site's HTML structure — these go stale and are unreliable.
|
|
71
|
+
- **Fetch the target data yourself** — When your workflow involves any kind of data fetching, **always** fetch it yourself and inspect the response. This is the ultimate source of truth to guide your implementation.
|
|
55
72
|
|
|
56
73
|
### Phase 3: Generate
|
|
57
74
|
|
|
58
75
|
Design the workflow internally following this checklist, then write the `.md` file:
|
|
59
76
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
Write the workflow `.md` file using the Write tool.
|
|
77
|
+
- **Identify data sources and operations** — What data is needed, and what operations must be performed? These become `tool` steps.
|
|
78
|
+
- **Identify data transformations** — What filtering, reshaping, or sorting is needed between steps? These become `transform` steps.
|
|
79
|
+
- **Identify decision points** — Where does execution branch? These become `conditional` steps.
|
|
80
|
+
- **Identify exit conditions** — When should the workflow stop early? These become `exit` steps with `condition` guards.
|
|
81
|
+
- **Wire steps together** — Use `$steps.<id>.output` references to connect outputs to inputs.
|
|
82
|
+
- **Add error handling** — Configure `on_error`. Use `retry` policies to protect from transient errors.
|
|
68
83
|
|
|
69
84
|
### Phase 4: Validate & Test
|
|
70
85
|
|
|
71
86
|
- Validate the workflow against the runtime. If validation fails, fix the errors and revalidate.
|
|
72
87
|
- Run the workflow to verify it works end-to-end.
|
|
73
|
-
- For workflows with conditional exits, test both execution paths (e.g., "results found" vs. "no results").
|
|
74
|
-
- If the test reveals issues (malformed LLM output, wrong field mappings, broken expressions), fix the workflow and re-test.
|
|
88
|
+
- Test beyond just happy path. Vary inputs. For workflows with conditional exits, test both execution paths (e.g., "results found" vs. "no results").
|
|
89
|
+
- If the test reveals issues (malformed LLM output, wrong field mappings, broken expressions), fix the workflow and re-test. Repeat until the workflow accomplishes the original intent. Solutions should **always** follow best practices. No workarounds.
|
|
75
90
|
|
|
76
|
-
## YAML
|
|
91
|
+
## YAML Reference
|
|
92
|
+
|
|
93
|
+
### Workflow Inputs
|
|
94
|
+
|
|
95
|
+
Each input is an object keyed by name. Two fields:
|
|
96
|
+
|
|
97
|
+
- `type` (required): `string`, `int`, `float`, `boolean`, `array`, or `object`
|
|
98
|
+
- `default` (optional): literal fallback used when the input is not provided at runtime
|
|
77
99
|
|
|
78
100
|
```yaml
|
|
79
|
-
inputs:
|
|
80
|
-
|
|
81
|
-
type: string
|
|
82
|
-
default:
|
|
101
|
+
inputs:
|
|
102
|
+
url:
|
|
103
|
+
type: string
|
|
104
|
+
default: "https://example.com/items"
|
|
105
|
+
count:
|
|
106
|
+
type: int
|
|
107
|
+
default: 10
|
|
108
|
+
verbose:
|
|
109
|
+
type: boolean
|
|
110
|
+
default: false
|
|
111
|
+
```
|
|
83
112
|
|
|
84
|
-
|
|
85
|
-
<name>:
|
|
86
|
-
type: string | int | float | boolean | array | object
|
|
87
|
-
value: <$expression> # optional — resolves from $steps context after all steps
|
|
113
|
+
### Workflow Outputs
|
|
88
114
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
# Optional common fields:
|
|
103
|
-
condition: <expression> # guard: skip if false
|
|
104
|
-
each: <expression> # iterate over array
|
|
105
|
-
delay: "<duration>" # inter-iteration pause (requires each). e.g., "1s", "500ms"
|
|
106
|
-
on_error: fail | ignore # default: fail
|
|
107
|
-
retry:
|
|
108
|
-
max: <int> # not "max_attempts"
|
|
109
|
-
delay: "<duration>" # e.g., "1s", "500ms" — not "backoff_ms"
|
|
110
|
-
backoff: <float>
|
|
115
|
+
Each output is an object keyed by name. Two fields:
|
|
116
|
+
|
|
117
|
+
- `type` (required): `string`, `int`, `float`, `boolean`, `array`, or `object`
|
|
118
|
+
- `value` (optional): a `$steps` expression resolved after all steps complete
|
|
119
|
+
|
|
120
|
+
```yaml
|
|
121
|
+
outputs:
|
|
122
|
+
items:
|
|
123
|
+
type: array
|
|
124
|
+
value: $steps.fetch_details.output
|
|
125
|
+
title:
|
|
126
|
+
type: string
|
|
127
|
+
value: $steps.fetch.output.title
|
|
111
128
|
```
|
|
112
129
|
|
|
113
|
-
|
|
130
|
+
Use `$steps.<id>.output` when the step returns a plain value (string, array, object) with no named step outputs declared. Use `$steps.<id>.output.<field>` only when the step declares named outputs and you need a specific field. Never add a field selector for a step whose result is a plain string — it will always resolve to nothing.
|
|
114
131
|
|
|
115
|
-
|
|
116
|
-
- Use `value` for both expressions and literals. Strings starting with `$` are auto-detected as expressions.
|
|
117
|
-
- Expressions: `value: $inputs.query`, `value: $steps.prev.output.field`
|
|
118
|
-
- Templates: `value: "https://example.com?q=${inputs.query}"`, `value: "${inputs.base_url}${item}.json"`
|
|
119
|
-
- Literals: `value: "https://example.com"`, `value: "GET"`
|
|
120
|
-
- To use a literal string starting with `$`, escape with `$$`: `value: "$$100"` → `"$100"`
|
|
121
|
-
- A bare value like `url: "https://example.com"` is invalid — it must be an object with `type`.
|
|
132
|
+
### Workflow Steps
|
|
122
133
|
|
|
123
|
-
|
|
134
|
+
**id**
|
|
124
135
|
|
|
125
|
-
|
|
136
|
+
A unique string identifier. Downstream steps reference it as `$steps.<id>.output`. Declare steps in dependency order — a step can only reference steps declared before it.
|
|
137
|
+
|
|
138
|
+
**type**
|
|
139
|
+
|
|
140
|
+
One of `tool`, `transform`, `conditional`, or `exit`.
|
|
141
|
+
|
|
142
|
+
`tool` — Invokes a registered tool via the host's ToolAdapter. Use for all external calls: APIs, databases, LLM inference. Requires a `tool` field naming the registered tool. Only use tools actually available to you in your context. Do not make up tools.
|
|
126
143
|
|
|
127
144
|
```yaml
|
|
128
|
-
- id:
|
|
145
|
+
- id: fetch
|
|
129
146
|
type: tool
|
|
130
|
-
tool:
|
|
147
|
+
tool: web_fetch
|
|
131
148
|
inputs:
|
|
132
|
-
url:
|
|
133
|
-
type: string
|
|
134
|
-
value: $inputs.url
|
|
135
|
-
limit:
|
|
136
|
-
type: int
|
|
137
|
-
value: 50
|
|
149
|
+
url: { type: string, value: $inputs.url }
|
|
138
150
|
outputs:
|
|
139
|
-
|
|
140
|
-
type: array
|
|
141
|
-
value: $result.items # map from raw executor result
|
|
151
|
+
content: { type: string, value: $result.content }
|
|
142
152
|
```
|
|
143
153
|
|
|
144
|
-
|
|
154
|
+
`transform` — Filters, maps, or sorts an array in-process. Arrays only — never use on a single object. Requires an `operation` field: `filter`, `map`, or `sort`.
|
|
145
155
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
156
|
+
- `filter` — keep items where `where` is true
|
|
157
|
+
- `map` — reshape each item using an `expression` object
|
|
158
|
+
- `sort` — order items by `field` and `direction` (`asc` or `desc`)
|
|
149
159
|
|
|
150
160
|
```yaml
|
|
151
161
|
- id: filter_items
|
|
@@ -153,381 +163,228 @@ Transform steps operate on **arrays only** (filter, map, sort). They require an
|
|
|
153
163
|
operation: filter
|
|
154
164
|
where: $item.score >= $inputs.threshold
|
|
155
165
|
inputs:
|
|
156
|
-
items:
|
|
157
|
-
type: array
|
|
158
|
-
value: $steps.previous.output.items
|
|
166
|
+
items: { type: array, value: $steps.previous.output.items }
|
|
159
167
|
outputs:
|
|
160
|
-
items:
|
|
161
|
-
type: array
|
|
162
|
-
```
|
|
168
|
+
items: { type: array }
|
|
163
169
|
|
|
164
|
-
### Transform Step (map)
|
|
165
|
-
|
|
166
|
-
```yaml
|
|
167
170
|
- id: reshape
|
|
168
171
|
type: transform
|
|
169
172
|
operation: map
|
|
170
173
|
expression:
|
|
171
174
|
name: $item.full_name
|
|
172
|
-
|
|
173
|
-
inputs:
|
|
174
|
-
items:
|
|
175
|
-
type: array
|
|
176
|
-
value: $steps.previous.output.items
|
|
177
|
-
outputs:
|
|
178
|
-
items:
|
|
179
|
-
type: array
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
### Transform Step (map — cross-array zip)
|
|
183
|
-
|
|
184
|
-
When you have parallel arrays from different steps that need to be combined into an array of objects, use `map` with `$index` bracket indexing. Iterate over one array and pull corresponding elements from the others:
|
|
185
|
-
|
|
186
|
-
```yaml
|
|
187
|
-
- id: zip_results
|
|
188
|
-
type: transform
|
|
189
|
-
operation: map
|
|
190
|
-
expression:
|
|
191
|
-
title: $item
|
|
192
|
-
company: $steps.extract_companies.output.companies[$index]
|
|
193
|
-
location: $steps.extract_locations.output.locations[$index]
|
|
175
|
+
score: $item.metrics.score
|
|
194
176
|
inputs:
|
|
195
|
-
items:
|
|
196
|
-
type: array
|
|
197
|
-
value: $steps.extract_titles.output.titles
|
|
177
|
+
items: { type: array, value: $steps.previous.output.items }
|
|
198
178
|
outputs:
|
|
199
|
-
items:
|
|
200
|
-
type: array
|
|
201
|
-
```
|
|
179
|
+
items: { type: array }
|
|
202
180
|
|
|
203
|
-
This is a pure data operation — never use a tool step for merging or zipping arrays when a transform step suffices.
|
|
204
|
-
|
|
205
|
-
### Transform Step (sort)
|
|
206
|
-
|
|
207
|
-
```yaml
|
|
208
181
|
- id: sort_results
|
|
209
182
|
type: transform
|
|
210
183
|
operation: sort
|
|
211
184
|
field: score
|
|
212
|
-
direction: desc
|
|
185
|
+
direction: desc
|
|
213
186
|
inputs:
|
|
214
|
-
items:
|
|
215
|
-
type: array
|
|
216
|
-
value: $steps.previous.output.items
|
|
187
|
+
items: { type: array, value: $steps.previous.output.items }
|
|
217
188
|
outputs:
|
|
218
|
-
items:
|
|
219
|
-
type: array
|
|
189
|
+
items: { type: array }
|
|
220
190
|
```
|
|
221
191
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
`condition` is the branch predicate — evaluates to true (execute `then` steps) or false (execute `else` steps). Branch steps are declared later in the step list and skipped during sequential execution; they only run when selected by a conditional. `inputs: {}` and `outputs: {}` are required even though they're empty.
|
|
192
|
+
`conditional` — Branches execution. `condition` is the branch predicate: true runs `then` step IDs, false runs `else` step IDs. Branch step IDs must match steps declared later in the list — they are skipped during sequential execution and only run when selected. `inputs: {}` and `outputs: {}` are required even when empty.
|
|
225
193
|
|
|
226
194
|
```yaml
|
|
227
195
|
- id: route
|
|
228
196
|
type: conditional
|
|
229
197
|
condition: $steps.check.output.items.length > 0
|
|
230
|
-
then:
|
|
231
|
-
|
|
232
|
-
else:
|
|
233
|
-
- handle_empty
|
|
198
|
+
then: [handle_found]
|
|
199
|
+
else: [handle_empty]
|
|
234
200
|
inputs: {}
|
|
235
201
|
outputs: {}
|
|
236
202
|
```
|
|
237
203
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
Use exit steps for **conditional early termination** — to stop the workflow when a condition is met.
|
|
241
|
-
|
|
242
|
-
`status` must be `success` or `failed` — those are the only valid values.
|
|
243
|
-
|
|
244
|
-
Early exit on empty result (success):
|
|
204
|
+
`exit` — Terminates the workflow early. Use only for conditional early termination — not to produce normal output. `status` is `success` or `failed`. `output` keys must match declared workflow output keys. `inputs: {}` and `outputs: {}` are required.
|
|
245
205
|
|
|
246
206
|
```yaml
|
|
247
|
-
- id:
|
|
207
|
+
- id: guard_empty
|
|
248
208
|
type: exit
|
|
249
209
|
condition: $steps.filter.output.items.length == 0
|
|
250
210
|
status: success
|
|
251
211
|
output:
|
|
252
|
-
count: 0
|
|
253
212
|
items: []
|
|
254
213
|
inputs: {}
|
|
255
214
|
outputs: {}
|
|
256
215
|
```
|
|
257
216
|
|
|
258
|
-
|
|
217
|
+
**description**
|
|
259
218
|
|
|
260
|
-
|
|
261
|
-
- id: guard_empty
|
|
262
|
-
type: exit
|
|
263
|
-
condition: $steps.fetch.output.data.length == 0
|
|
264
|
-
status: failed
|
|
265
|
-
output:
|
|
266
|
-
error: "No data returned from API"
|
|
267
|
-
inputs: {}
|
|
268
|
-
outputs: {}
|
|
269
|
-
```
|
|
219
|
+
Documents the intent of the step for readers.
|
|
270
220
|
|
|
271
|
-
|
|
221
|
+
**Step inputs**
|
|
272
222
|
|
|
273
|
-
|
|
223
|
+
Typed input fields — objects with `type` and `value`. A bare scalar is invalid.
|
|
274
224
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
| Step output `value` | `$result` | Immediately after step executes |
|
|
278
|
-
| Workflow output `value` | `$steps.<id>.output` | After all steps complete |
|
|
225
|
+
- `type` (required): `string`, `int`, `float`, `boolean`, `array`, or `object`
|
|
226
|
+
- `value` (optional): expression, template string, or literal
|
|
279
227
|
|
|
280
|
-
|
|
228
|
+
**Step outputs**
|
|
229
|
+
|
|
230
|
+
Typed output fields — objects with `type` and optional `value`. Use `$result` to map fields from the raw executor result:
|
|
281
231
|
|
|
282
232
|
```yaml
|
|
283
233
|
outputs:
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
value: $steps.fetch.output.title # resolved after all steps complete
|
|
234
|
+
content: { type: string, value: $result.content }
|
|
235
|
+
items: { type: array, value: $result.items }
|
|
287
236
|
```
|
|
288
237
|
|
|
289
|
-
|
|
238
|
+
Outputs without `value` pass through from the raw result by key name. `$result` is only valid in step output `value` — not in workflow outputs.
|
|
239
|
+
|
|
240
|
+
Only declare step outputs when you need to extract and rename specific fields from a structured result. If downstream steps reference `$steps.<id>.output` directly — without a field selector — omit the `outputs` block entirely; the full result is always available that way regardless.
|
|
290
241
|
|
|
291
|
-
|
|
292
|
-
2. **Exit step fires** — the exit step's `output` takes precedence. Its keys are matched against the declared workflow output keys.
|
|
293
|
-
3. **No value, no exit** — outputs are matched by key name against the last executed step's output (legacy behavior).
|
|
242
|
+
When a tool returns a plain string rather than a JSON object, there are no fields to extract. Declaring named outputs with `$result.<field>` will always resolve to nothing. Reference the string directly as `$steps.<id>.output` in workflow outputs or downstream step inputs.
|
|
294
243
|
|
|
295
|
-
**
|
|
244
|
+
**condition**
|
|
296
245
|
|
|
297
|
-
|
|
246
|
+
A boolean expression. When false, the step is skipped and its output is `null`. Valid on `tool`, `transform`, and `exit` steps as a guard. On a `conditional` step, `condition` is the branch predicate — not a guard.
|
|
298
247
|
|
|
299
248
|
```yaml
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
249
|
+
- id: notify
|
|
250
|
+
type: tool
|
|
251
|
+
tool: slack.post_message
|
|
252
|
+
condition: $steps.filter.output.items.length > 0
|
|
253
|
+
inputs:
|
|
254
|
+
text: { type: string, value: "Items found" }
|
|
255
|
+
outputs:
|
|
256
|
+
sent: { type: boolean, value: $result.ok }
|
|
304
257
|
```
|
|
305
258
|
|
|
306
|
-
This is useful when the raw executor result has a different shape than what downstream steps need. Outputs without `value` pass through from the raw result by key name.
|
|
307
|
-
|
|
308
|
-
## Expression Language
|
|
309
|
-
|
|
310
|
-
Use `$`-prefixed references to wire data between steps:
|
|
311
|
-
|
|
312
|
-
| Reference | Resolves To |
|
|
313
|
-
| ----------------------------- | ----------------------------------------------------------------- |
|
|
314
|
-
| `$inputs.name` | Workflow input parameter |
|
|
315
|
-
| `$steps.<id>.output` | A step's full output |
|
|
316
|
-
| `$steps.<id>.output.field` | A specific field from output |
|
|
317
|
-
| `$item` | Current item in `each` or transform iteration |
|
|
318
|
-
| `$index` | Current index in iteration |
|
|
319
|
-
| `$result` | Raw executor result (only valid in step output `value`) |
|
|
320
|
-
| `$steps.<id>.output.field[0]` | First element of an array field |
|
|
321
|
-
| `$item[$index]` | Nested array element at computed index (only valid inside `each`) |
|
|
322
|
-
|
|
323
259
|
Operators: `==`, `!=`, `>`, `<`, `>=`, `<=`, `&&`, `||`, `!`, `contains`
|
|
324
260
|
|
|
325
|
-
`contains` tests
|
|
261
|
+
`contains` tests substring (`$item.title contains "Manager"`, case-insensitive) or array membership (`$item.tags contains "urgent"`, exact primitive equality). Use it in `where` clauses and `condition` guards to match text without an LLM.
|
|
326
262
|
|
|
327
|
-
|
|
263
|
+
No function calls, no ternary expressions, no regex. `&&`/`||` are boolean — they return `true`/`false`, not the operand value.
|
|
328
264
|
|
|
329
|
-
**
|
|
265
|
+
**each**
|
|
330
266
|
|
|
331
|
-
|
|
267
|
+
Runs the step once per element in the target array. `$item` is the current element; `$index` is the 0-based position. Each iteration's output is collected into an array, with `$result` mappings applied per iteration. Valid only on `tool` and `transform` steps.
|
|
332
268
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
- `"${inputs.base_url}${item}.json"` → concatenated string
|
|
336
|
-
- `"https://api.example.com?q=${inputs.query}"` → URL with query param
|
|
337
|
-
- `"${steps.fetch.output.count}"` alone → typed result preserved (not coerced to string)
|
|
338
|
-
- `$${` inside a template → literal `${`
|
|
339
|
-
|
|
340
|
-
Primary use case: constructing per-iteration URLs in `each` + tool patterns.
|
|
269
|
+
With `on_error: ignore`, failed iterations produce `null` in the results array while successful iterations keep their output — the step continues through all items.
|
|
341
270
|
|
|
342
271
|
```yaml
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
272
|
+
- id: fetch_details
|
|
273
|
+
type: tool
|
|
274
|
+
tool: web_fetch
|
|
275
|
+
each: $steps.slice_items.output.items
|
|
276
|
+
delay: "2s"
|
|
277
|
+
on_error: ignore
|
|
278
|
+
inputs:
|
|
279
|
+
url: { type: string, value: $item.url }
|
|
280
|
+
outputs:
|
|
281
|
+
content: { type: string, value: $result.content }
|
|
350
282
|
```
|
|
351
283
|
|
|
352
|
-
|
|
284
|
+
**delay**
|
|
353
285
|
|
|
354
|
-
|
|
286
|
+
Pauses between iterations. Requires `each`. Not applied after the last iteration. Examples: `"2s"`, `"500ms"`.
|
|
355
287
|
|
|
356
|
-
|
|
288
|
+
Always add `delay` to every `each` loop that calls an external service — APIs rate-limit without warning. Minimum: `"500ms"` for LLM calls, `"2s"` for external APIs.
|
|
357
289
|
|
|
358
|
-
**
|
|
290
|
+
**on_error**
|
|
359
291
|
|
|
360
|
-
|
|
292
|
+
`fail` (default) — the workflow stops on the first error. `ignore` — failed iterations produce `null` in the results array and the workflow continues.
|
|
361
293
|
|
|
362
|
-
|
|
363
|
-
steps:
|
|
364
|
-
- id: get_ids
|
|
365
|
-
type: tool
|
|
366
|
-
tool: api.list_items
|
|
367
|
-
inputs:
|
|
368
|
-
url: { type: string, value: $inputs.api_url }
|
|
369
|
-
outputs:
|
|
370
|
-
items: { type: array, value: $result.items }
|
|
371
|
-
|
|
372
|
-
- id: fetch_details
|
|
373
|
-
type: tool
|
|
374
|
-
tool: api.get_item
|
|
375
|
-
each: $steps.get_ids.output.items # iterate over items array
|
|
376
|
-
delay: "2s" # required: rate limit between calls
|
|
377
|
-
on_error: ignore # skip failed fetches, continue
|
|
378
|
-
inputs:
|
|
379
|
-
id:
|
|
380
|
-
type: string
|
|
381
|
-
value: $item.id # each item's ID from the listing
|
|
382
|
-
outputs:
|
|
383
|
-
title:
|
|
384
|
-
type: string
|
|
385
|
-
value: $result.title # mapped per iteration via $result
|
|
386
|
-
summary:
|
|
387
|
-
type: string
|
|
388
|
-
value: $result.summary
|
|
389
|
-
```
|
|
294
|
+
Use `ignore` for per-item tool calls where one failure should not abort the batch.
|
|
390
295
|
|
|
391
|
-
|
|
296
|
+
**retry**
|
|
392
297
|
|
|
393
|
-
|
|
298
|
+
Retries a failed tool step. All three fields are required:
|
|
394
299
|
|
|
395
300
|
```yaml
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
301
|
+
retry:
|
|
302
|
+
max: 3 # retry attempts — total tries = 1 + max
|
|
303
|
+
delay: "2s" # base delay before first retry
|
|
304
|
+
backoff: 1.5 # delay multiplier per attempt (2s → 3s → 4.5s)
|
|
400
305
|
```
|
|
401
306
|
|
|
402
|
-
|
|
307
|
+
Only tool errors are retriable — expression and validation errors fail immediately.
|
|
403
308
|
|
|
404
|
-
|
|
309
|
+
**Expression** — a `$`-prefixed reference to a workflow input or earlier step output:
|
|
405
310
|
|
|
406
311
|
```yaml
|
|
407
|
-
inputs
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
default: "https://api.example.com/items"
|
|
411
|
-
count:
|
|
412
|
-
type: int
|
|
413
|
-
default: 10
|
|
312
|
+
url: { type: string, value: $inputs.url }
|
|
313
|
+
items: { type: array, value: $steps.extract.output.items }
|
|
314
|
+
```
|
|
414
315
|
|
|
415
|
-
|
|
416
|
-
items:
|
|
417
|
-
type: array
|
|
418
|
-
value: $steps.fetch_details.output
|
|
316
|
+
Expression reference table:
|
|
419
317
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
outputs:
|
|
436
|
-
items: { type: array }
|
|
437
|
-
|
|
438
|
-
- id: fetch_details
|
|
439
|
-
type: tool
|
|
440
|
-
tool: api.get_item
|
|
441
|
-
each: $steps.slice_items.output.items
|
|
442
|
-
delay: "2s"
|
|
443
|
-
retry: { max: 3, delay: "2s", backoff: 1.5 }
|
|
444
|
-
on_error: ignore
|
|
445
|
-
inputs:
|
|
446
|
-
id: { type: string, value: $item.id }
|
|
447
|
-
outputs:
|
|
448
|
-
title: { type: string, value: $result.title }
|
|
449
|
-
summary: { type: string, value: $result.summary }
|
|
450
|
-
score: { type: string, value: $result.score }
|
|
318
|
+
| Reference | Resolves to |
|
|
319
|
+
| ----------------------------- | ------------------------------------------------ |
|
|
320
|
+
| `$inputs.name` | Workflow input parameter |
|
|
321
|
+
| `$steps.<id>.output` | A step's full output object |
|
|
322
|
+
| `$steps.<id>.output.field` | A specific field from a step's output |
|
|
323
|
+
| `$steps.<id>.output.field[0]` | First element of an array field |
|
|
324
|
+
| `$item` | Current element in `each` or transform iteration |
|
|
325
|
+
| `$index` | 0-based position in iteration |
|
|
326
|
+
| `$result` | Raw executor result (step output `value` only) |
|
|
327
|
+
|
|
328
|
+
**Template** — a string with `${ref}` blocks. References inside `${...}` omit the leading `$`:
|
|
329
|
+
|
|
330
|
+
```yaml
|
|
331
|
+
url: { type: string, value: "https://api.example.com/items/${item.id}" }
|
|
332
|
+
path: { type: string, value: "${inputs.base_url}${item}.json" }
|
|
451
333
|
```
|
|
452
334
|
|
|
453
|
-
|
|
335
|
+
**Literal** — any static value:
|
|
454
336
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
5. **Use `value` on step outputs** to map fields from the raw executor result using `$result`. Useful when the tool's response shape differs from what downstream steps need.
|
|
460
|
-
6. **Use `each` for per-item processing** on tool steps. Always include `delay` on every `each` loop that calls an external service — `delay: "2s"` is a safe default. See _Iteration Patterns_.
|
|
461
|
-
7. **Add `on_error: ignore` for non-critical steps** like notifications.
|
|
462
|
-
8. **Add `retry` for external API calls** (tool steps that might fail transiently).
|
|
463
|
-
9. **Use `condition` guards for early exits** rather than letting empty data flow through.
|
|
464
|
-
10. **Steps execute in declaration order.** A step can only reference steps declared before it.
|
|
465
|
-
11. **`each` is not valid on `exit` or `conditional` steps.**
|
|
466
|
-
12. **`condition` on a `conditional` step is the branch condition**, not a guard.
|
|
467
|
-
13. **Use exit steps for conditional early termination only**, not as the default way to produce output. Exit output keys must match the declared workflow output keys.
|
|
468
|
-
14. **Transform steps are for arrays only.** Never use a transform to extract fields from a single object.
|
|
469
|
-
15. **Use `map` with `$index` for cross-array merging.** When multiple steps produce parallel arrays, use a `map` transform with bracket indexing (`$steps.other.output.field[$index]`) to zip them into structured objects.
|
|
470
|
-
16. **Guard expensive steps behind deterministic exits.** Pattern: fetch → filter → exit guard → expensive tool. Use deterministic expressions (e.g., `$item.department == "Engineering"` or `$item.title contains "Product Manager"`) in `transform filter` steps before any costly tool call. See _Patterns_.
|
|
471
|
-
17. **Prefer bulk endpoints over per-item iteration.** When per-item `each` + tool calls are unavoidable, always add `delay: "2s"` (minimum), cap iteration count, and add `retry` with `backoff`. `delay` is not optional — external APIs rate-limit without warning. See _Iteration Patterns_.
|
|
337
|
+
```yaml
|
|
338
|
+
method: { type: string, value: "GET" }
|
|
339
|
+
channel: { type: string, value: "#alerts" }
|
|
340
|
+
```
|
|
472
341
|
|
|
473
|
-
|
|
342
|
+
To use a literal string starting with `$`, escape it: `"$$100"` → `"$100"`.
|
|
474
343
|
|
|
475
|
-
|
|
344
|
+
## Design Rules
|
|
476
345
|
|
|
477
|
-
|
|
478
|
-
2. A markdown heading.
|
|
479
|
-
3. A single `workflow` fenced code block containing the YAML.
|
|
346
|
+
### LLM vs. Deterministic Steps
|
|
480
347
|
|
|
481
|
-
|
|
348
|
+
If the operation has a single correct answer derivable from the data, use a deterministic step. LLMs cost money, add latency, and introduce variability — use them only when no deterministic alternative exists.
|
|
482
349
|
|
|
483
|
-
|
|
484
|
-
---
|
|
485
|
-
name: example-workflow
|
|
486
|
-
description: Fetches data and outputs a specific field
|
|
487
|
-
---
|
|
350
|
+
**Step costs:**
|
|
488
351
|
|
|
489
|
-
|
|
352
|
+
| Step type | Cost |
|
|
353
|
+
| ---------------------------------- | ---------------------------- |
|
|
354
|
+
| `transform`, `conditional`, `exit` | Free — pure in-process |
|
|
355
|
+
| `tool` (API) | Latency + rate limits |
|
|
356
|
+
| `tool` (LLM) | Expensive — billed per token |
|
|
490
357
|
|
|
491
|
-
|
|
492
|
-
inputs:
|
|
493
|
-
url:
|
|
494
|
-
type: string
|
|
495
|
-
default: "https://api.example.com/items"
|
|
358
|
+
**Use an LLM ONLY for:**
|
|
496
359
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
360
|
+
- Interpreting ambiguous or unstructured text where the meaning is context-dependent
|
|
361
|
+
- Extracting structured data from prose where field boundaries aren't predictable
|
|
362
|
+
- Generating natural language where wording quality matters
|
|
363
|
+
- Classification that can't be reduced to substring or numeric comparison
|
|
501
364
|
|
|
502
|
-
|
|
503
|
-
- id: fetch
|
|
504
|
-
type: tool
|
|
505
|
-
tool: api.get_item
|
|
506
|
-
inputs:
|
|
507
|
-
url:
|
|
508
|
-
type: string
|
|
509
|
-
value: $inputs.url
|
|
510
|
-
outputs:
|
|
511
|
-
name:
|
|
512
|
-
type: string
|
|
513
|
-
value: $result.name
|
|
514
|
-
` `` `
|
|
515
|
-
```
|
|
365
|
+
**NEVER use an LLM for:**
|
|
516
366
|
|
|
517
|
-
|
|
367
|
+
| Operation | Alternative |
|
|
368
|
+
| --- | --- |
|
|
369
|
+
| Merging parallel arrays | `transform map` + `$index` |
|
|
370
|
+
| Filtering by substring | `transform filter` + `contains` |
|
|
371
|
+
| Filtering by numeric threshold | `transform filter` + comparison operators |
|
|
372
|
+
| Grouping by boolean condition | Two `transform filter` steps with complementary `where` |
|
|
373
|
+
| Sorting | `transform sort` |
|
|
374
|
+
| Counting | `.length` |
|
|
375
|
+
| Formatting strings | Template interpolation in step `value` inputs, `exit` outputs, or workflow `outputs` (not inside `transform map` expressions — those only resolve `$`-prefixed references) |
|
|
376
|
+
| Branching | `conditional` step or `condition` guard |
|
|
377
|
+
| Reshaping/renaming fields | `transform map` + `expression` object |
|
|
378
|
+
| String equality check | `==` operator |
|
|
379
|
+
| Array membership | `contains` operator |
|
|
518
380
|
|
|
519
|
-
|
|
381
|
+
If you find yourself writing a prompt that says "filter", "merge", "group by", "sort", or "format each item" — stop. These are deterministic operations.
|
|
520
382
|
|
|
521
|
-
|
|
522
|
-
- [ ] All `$steps` references point to earlier steps
|
|
523
|
-
- [ ] All tools referenced are confirmed available in this deployment context
|
|
524
|
-
- [ ] Input/output types are consistent between connected steps
|
|
525
|
-
- [ ] No cycles in step references
|
|
526
|
-
- [ ] `each` not used on exit or conditional steps
|
|
527
|
-
- [ ] Workflow outputs have `value` mapping to `$steps` references
|
|
528
|
-
- [ ] Step output `value` uses `$result` (not `$steps`)
|
|
529
|
-
- [ ] All `${}` template references resolve to declared inputs/steps
|
|
530
|
-
- [ ] Every `each` loop that calls an external service has `delay` (`"2s"` minimum)
|
|
531
|
-
- [ ] `each` + tool steps with per-item fetching are bounded (preceded by a cap) and have `retry` with `backoff`
|
|
383
|
+
**Cost control for unavoidable LLM steps:**
|
|
532
384
|
|
|
533
|
-
|
|
385
|
+
- Filter and cap with `transform` steps before passing data to any `tool` step.
|
|
386
|
+
- Use `each` on LLM steps that process a list. Never pass the whole collection in one bulk prompt — per-item calls are cheaper, higher quality, and easier to debug.
|
|
387
|
+
- Always precede an `each` + LLM step with a `transform filter` using `$index < $inputs.count` to bound cost.
|
|
388
|
+
- Keep LLM output schemas minimal — only request fields you actually use downstream.
|
|
389
|
+
- Always provide a `system` prompt. A focused system prompt improves output quality and reduces token use.
|
|
390
|
+
- Use `on_error: ignore` on per-item LLM steps so one failed generation doesn't abort the batch.
|