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/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. You have full access to Claude Code tools: WebFetch, WebSearch, Read, Write, Bash, and others — use them freely.
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
- - **Inputs**: Typed parameters the workflow accepts
17
- - **Outputs**: Typed results the workflow produces
18
- - **Steps**: An ordered sequence of operations
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
- Each step is one of four types:
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
- | Type | Purpose |
23
- | ------------- | -------------------------------------------------------------------------------- |
24
- | `tool` | Invoke a registered tool via the host's ToolAdapter (APIs, functions, LLM calls) |
25
- | `transform` | Filter, map, or sort data |
26
- | `conditional` | Branch execution based on a condition |
27
- | `exit` | Terminate the workflow early with a status |
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
- All external calls — including LLM inference — go through `tool` steps. The runtime itself has no LLM dependency. The host registers whatever tools are available in the deployment context.
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 should feel like magic.
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
- - Read the request carefully. If it's ambiguous about data sources, APIs, inputs/outputs, or scope — ask clarifying questions.
38
- - Ask at most 2-3 focused questions at a time. Offer specific options.
39
- - Bad: "What do you want to do?" Good: "Should results be filtered by date, category, or both?"
40
- - If the request is clear, skip directly to Research.
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
- - **Confirm available tools first.** The tools available in `tool` steps are the tools registered in the current runtime context — in an interactive agent session, these are the tools listed in your 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.
45
- - If the workflow involves APIs, web services, or web scraping, investigate before generating:
46
- 1. **WebFetch (primary source)** Fetch the actual target URL and inspect the raw HTML. This is the ground truth. Look for:
47
- - The repeating container element (e.g., `li.result-row`, `div.job-card`)
48
- - CSS classes on child elements that hold the data you need (title, price, URL, etc.)
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
- 1. **Identify data sources and operations** — What tools or APIs are needed? These become `tool` steps. All external calls (including LLM inference) are tool steps.
61
- 2. **Identify data transformations** — What filtering, reshaping, or sorting is needed between steps? These become `transform` steps.
62
- 3. **Identify decision points** — Where does execution branch? These become `conditional` steps.
63
- 4. **Identify exit conditions** — When should the workflow stop early? These become `exit` steps with `condition` guards.
64
- 5. **Wire steps together** — Use `$steps.<id>.output` references to connect outputs to inputs.
65
- 6. **Add error handling** — Mark non-critical steps with `on_error: ignore`. Add `retry` policies for flaky APIs.
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"). If the primary path targets data that might currently be empty, test with known data to verify the non-empty path works.
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 Structure
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: # object keyed by name — NOT an array
80
- <name>:
81
- type: string | int | float | boolean | array | object
82
- default: <optional> # default value for optional inputs
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
- outputs: # object keyed by name — NOT an array
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
- steps:
90
- - id: <unique_identifier>
91
- type: tool | transform | conditional | exit
92
- description: <what this step does>
93
- # Type-specific fields (see below)
94
- inputs: # object keyed by name (the field is "inputs", not "params")
95
- <name>:
96
- type: <type> # required
97
- value: <$expression or literal> # the value: expression ($-prefixed) or literal
98
- outputs:
99
- <name>:
100
- type: <type>
101
- value: <$expression> # optional — maps from $result (raw executor result)
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
- **Step input rules:**
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
- - Every step input requires `type`.
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
- ## Step Type Reference
134
+ **id**
124
135
 
125
- ### Tool Step
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: fetch_data
145
+ - id: fetch
129
146
  type: tool
130
- tool: api.get_items
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
- results:
140
- type: array
141
- value: $result.items # map from raw executor result
151
+ content: { type: string, value: $result.content }
142
152
  ```
143
153
 
144
- ### Transform Step
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
- Transform steps operate on **arrays only** (filter, map, sort). They require an `items` input of type `array` and always output an `items` array. Do NOT use transform steps to extract fields from a single object — use an exit step with `$`-references for that.
147
-
148
- **filter:**
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
- email: $item.contact.email
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 # or asc (default)
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
- ### Conditional Step
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
- - handle_items
232
- else:
233
- - handle_empty
198
+ then: [handle_found]
199
+ else: [handle_empty]
234
200
  inputs: {}
235
201
  outputs: {}
236
202
  ```
237
203
 
238
- ### Exit Step
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: early_exit
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
- Early exit on error condition (failed):
217
+ **description**
259
218
 
260
- ```yaml
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
- For normal workflow output, prefer `value` on workflow outputs instead of a trailing exit step.
221
+ **Step inputs**
272
222
 
273
- ## Output Resolution
223
+ Typed input fields — objects with `type` and `value`. A bare scalar is invalid.
274
224
 
275
- | Context | Reference | When resolved |
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
- Workflow outputs use `value` to map data from step results:
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
- title:
285
- type: string
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
- **Resolution rules:**
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
- 1. **Normal completion** each workflow output with `value` (an expression) is resolved from the final runtime context using `$steps` references.
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
- **Use `value` on workflow outputs** to explicitly declare where each output comes from. This eliminates the need for a trailing exit step just to produce outputs. Reserve exit steps for conditional early termination.
244
+ **condition**
296
245
 
297
- **Step output `value`** maps fields from the raw executor result using `$result`:
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
- outputs:
301
- results:
302
- type: array
303
- value: $result.items # maps from the tool's raw response
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 for substring or membership: `$item.title contains "Manager"` (string substring, case-sensitive); `$item.tags contains "urgent"` (array membership for primitives). Use in `transform filter` `where` clauses and `condition` guards to match text without an LLM.
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
- Bracket indexing: `[0]`, `[$index]`, or any expression inside `[]` for array element access.
263
+ No function calls, no ternary expressions, no regex. `&&`/`||` are boolean — they return `true`/`false`, not the operand value.
328
264
 
329
- **Expression language limitations:** No function calls, no ternary expressions, no regex. Use `contains` for substring and array membership tests. Use `${}` template interpolation to build computed strings.
265
+ **each**
330
266
 
331
- ### Template Interpolation and Dynamic URLs
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
- String `value` fields may contain `${ref}` blocks for interpolation. References inside `${...}` omit the leading `$`:
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
- # Dynamic URL using template interpolation
344
- # If base_url = "https://api.example.com/item/" and item = 101:
345
- # → "https://api.example.com/item/101.json"
346
- inputs:
347
- url:
348
- type: string
349
- value: "${inputs.base_url}${item}.json"
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
- ## Iteration Patterns
284
+ **delay**
353
285
 
354
- ### Iterating with `each` on Tool Steps
286
+ Pauses between iterations. Requires `each`. Not applied after the last iteration. Examples: `"2s"`, `"500ms"`.
355
287
 
356
- When you need to call a tool once per item in a list, use `each` on a tool step. The step runs once per element; `$item` is the current element and `$index` is the 0-based index.
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
- **Rate limiting:** The runtime executes iterations sequentially. **Always add `delay` to every `each` loop that calls an external service.** `delay: "1s"` waits 1 second between iterations (not after the last). External APIs rate-limit without warning; a missing `delay` is a latent failure. `delay: "2s"` is a safe default for most APIs. Always prefer a bulk API endpoint that returns all data in one request. When per-item fetching is unavoidable, add `delay`, a preceding filter step to cap the count (see the `slice_items` step in the example below), and include `retry` with `backoff`.
290
+ **on_error**
359
291
 
360
- **Output collection:** Each iteration's output is collected into an array. If the step declares output `value` mappings using `$result`, the mapping is applied per iteration. The step record's `output` is the array of per-iteration mapped results.
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
- ```yaml
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
- After this step, `$steps.fetch_details.output` is an array of `{ title, summary }` objects — one per iteration. Use `$steps.fetch_details.output` (the whole array) in downstream steps or workflow outputs.
296
+ **retry**
392
297
 
393
- **Workflow output for each+tool:**
298
+ Retries a failed tool step. All three fields are required:
394
299
 
395
300
  ```yaml
396
- outputs:
397
- details:
398
- type: array
399
- value: $steps.fetch_details.output # the collected array of per-iteration results
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
- **Pattern: List Slice Fetch Details**
307
+ Only tool errors are retriable expression and validation errors fail immediately.
403
308
 
404
- Full example fetching a listing then fetching each detail via `each`:
309
+ **Expression** a `$`-prefixed reference to a workflow input or earlier step output:
405
310
 
406
311
  ```yaml
407
- inputs:
408
- api_url:
409
- type: string
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
- outputs:
416
- items:
417
- type: array
418
- value: $steps.fetch_details.output
316
+ Expression reference table:
419
317
 
420
- steps:
421
- - id: get_listing
422
- type: tool
423
- tool: api.list_items
424
- inputs:
425
- url: { type: string, value: $inputs.api_url }
426
- outputs:
427
- items: { type: array, value: $result.items }
428
-
429
- - id: slice_items
430
- type: transform
431
- operation: filter
432
- where: $index < $inputs.count # cap iteration count to avoid rate limiting
433
- inputs:
434
- items: { type: array, value: $steps.get_listing.output.items }
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
- ## Authoring Rules
335
+ **Literal** any static value:
454
336
 
455
- 1. **Use tool steps for all external calls.** Every interaction with an API, database, or LLM is a tool step. The runtime dispatches tool steps to whatever tools the host registers — the workflow author should use the exact tool names available in this deployment context. Do not invent tool names.
456
- 2. **Use transforms for pure data operations.** Filtering, reshaping, sorting, and field extraction are structural operations — use `transform` steps. Do not use tool steps to reshape data that can be expressed as a transform.
457
- 3. **Always declare inputs and outputs.** They enable validation and composability.
458
- 4. **Use `value` on workflow outputs** to explicitly map step results to workflow outputs. Use `$steps.<id>.output.<field>` expressions. This is preferred over exit steps for producing output.
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
- ## Output Format
342
+ To use a literal string starting with `$`, escape it: `"$$100"` → `"$100"`.
474
343
 
475
- Write the SKILL.md file using the Write tool. The file structure:
344
+ ## Design Rules
476
345
 
477
- 1. YAML frontmatter (between `---` delimiters) — the very first line. The `name` field must be lowercase-hyphenated (e.g., `fetch-json-from-api`, not `Fetch JSON from API`).
478
- 2. A markdown heading.
479
- 3. A single `workflow` fenced code block containing the YAML.
346
+ ### LLM vs. Deterministic Steps
480
347
 
481
- Example of a complete SKILL.md:
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
- # Example Workflow
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
- ` `` `workflow
492
- inputs:
493
- url:
494
- type: string
495
- default: "https://api.example.com/items"
358
+ **Use an LLM ONLY for:**
496
359
 
497
- outputs:
498
- name:
499
- type: string
500
- value: $steps.fetch.output.name
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
- steps:
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
- ## Validation
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
- After writing the file, always validate it against the runtime. The validation checklist:
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
- - [ ] All step IDs are unique
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
- If validation fails, fix the errors and revalidate.
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.