workflowskill 0.2.1 → 0.3.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/README.md +147 -106
- package/dist/index.d.mts +26 -84
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1779 -16
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -10
- package/skill/SKILL.md +77 -307
- package/dist/cli/index.d.mts +0 -1
- package/dist/cli/index.mjs +0 -187
- package/dist/cli/index.mjs.map +0 -1
- package/dist/runtime-CD81H1bx.mjs +0 -1977
- package/dist/runtime-CD81H1bx.mjs.map +0 -1
- package/dist/web-scrape-GeEM_JNl.mjs +0 -142
- package/dist/web-scrape-GeEM_JNl.mjs.map +0 -1
package/skill/SKILL.md
CHANGED
|
@@ -13,7 +13,7 @@ tags:
|
|
|
13
13
|
|
|
14
14
|
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.
|
|
15
15
|
|
|
16
|
-
**Sections:** Authoring Process | YAML Structure | Step Type Reference | Output Resolution | Expression Language | Iteration Patterns |
|
|
16
|
+
**Sections:** Authoring Process | YAML Structure | Step Type Reference | Output Resolution | Expression Language | Iteration Patterns | Authoring Rules | Output Format | Validation
|
|
17
17
|
|
|
18
18
|
## How WorkflowSkill Works
|
|
19
19
|
|
|
@@ -23,15 +23,16 @@ A WorkflowSkill is a declarative workflow definition embedded in a SKILL.md file
|
|
|
23
23
|
- **Outputs**: Typed results the workflow produces
|
|
24
24
|
- **Steps**: An ordered sequence of operations
|
|
25
25
|
|
|
26
|
-
Each step is one of
|
|
26
|
+
Each step is one of four types:
|
|
27
27
|
|
|
28
|
-
| Type | Purpose |
|
|
29
|
-
|
|
30
|
-
| `tool` | Invoke a registered tool (
|
|
31
|
-
| `
|
|
32
|
-
| `
|
|
33
|
-
| `
|
|
34
|
-
|
|
28
|
+
| Type | Purpose |
|
|
29
|
+
|------|---------|
|
|
30
|
+
| `tool` | Invoke a registered tool via the host's ToolAdapter (APIs, functions, LLM calls) |
|
|
31
|
+
| `transform` | Filter, map, or sort data |
|
|
32
|
+
| `conditional` | Branch execution based on a condition |
|
|
33
|
+
| `exit` | Terminate the workflow early with a status |
|
|
34
|
+
|
|
35
|
+
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.
|
|
35
36
|
|
|
36
37
|
## Authoring Process
|
|
37
38
|
|
|
@@ -44,7 +45,7 @@ The user should never have to think about workflow internals. They describe what
|
|
|
44
45
|
- If the request is clear, skip directly to Research.
|
|
45
46
|
|
|
46
47
|
### Phase 2: Research
|
|
47
|
-
- **Confirm available tools first.**
|
|
48
|
+
- **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.
|
|
48
49
|
- If the workflow involves APIs, web services, or web scraping, investigate before generating:
|
|
49
50
|
1. **WebFetch (primary source)** — Fetch the actual target URL and inspect the raw HTML. This is the ground truth. Look for:
|
|
50
51
|
- The repeating container element (e.g., `li.result-row`, `div.job-card`)
|
|
@@ -59,13 +60,12 @@ The user should never have to think about workflow internals. They describe what
|
|
|
59
60
|
### Phase 3: Generate
|
|
60
61
|
Design the workflow internally following this checklist, then write the `.md` file:
|
|
61
62
|
|
|
62
|
-
1. **Identify data sources** — What tools or APIs are needed? These become `tool` steps.
|
|
63
|
-
2. **Identify
|
|
64
|
-
3. **Identify
|
|
65
|
-
4. **Identify
|
|
66
|
-
5. **
|
|
67
|
-
6. **
|
|
68
|
-
7. **Add error handling** — Mark non-critical steps with `on_error: ignore`. Add `retry` policies for flaky APIs.
|
|
63
|
+
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.
|
|
64
|
+
2. **Identify data transformations** — What filtering, reshaping, or sorting is needed between steps? These become `transform` steps.
|
|
65
|
+
3. **Identify decision points** — Where does execution branch? These become `conditional` steps.
|
|
66
|
+
4. **Identify exit conditions** — When should the workflow stop early? These become `exit` steps with `condition` guards.
|
|
67
|
+
5. **Wire steps together** — Use `$steps.<id>.output` references to connect outputs to inputs.
|
|
68
|
+
6. **Add error handling** — Mark non-critical steps with `on_error: ignore`. Add `retry` policies for flaky APIs.
|
|
69
69
|
|
|
70
70
|
Write the workflow `.md` file using the Write tool.
|
|
71
71
|
|
|
@@ -90,7 +90,7 @@ outputs: # object keyed by name — NOT an array
|
|
|
90
90
|
|
|
91
91
|
steps:
|
|
92
92
|
- id: <unique_identifier>
|
|
93
|
-
type: tool |
|
|
93
|
+
type: tool | transform | conditional | exit
|
|
94
94
|
description: <what this step does>
|
|
95
95
|
# Type-specific fields (see below)
|
|
96
96
|
inputs: # object keyed by name (the field is "inputs", not "params")
|
|
@@ -127,35 +127,18 @@ steps:
|
|
|
127
127
|
```yaml
|
|
128
128
|
- id: fetch_data
|
|
129
129
|
type: tool
|
|
130
|
-
tool: api.
|
|
130
|
+
tool: api.get_items
|
|
131
131
|
inputs:
|
|
132
|
-
|
|
132
|
+
url:
|
|
133
133
|
type: string
|
|
134
|
-
value: $inputs.
|
|
134
|
+
value: $inputs.url
|
|
135
|
+
limit:
|
|
136
|
+
type: int
|
|
137
|
+
value: 50
|
|
135
138
|
outputs:
|
|
136
|
-
|
|
137
|
-
type:
|
|
138
|
-
value: $result.
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
### LLM Step
|
|
142
|
-
```yaml
|
|
143
|
-
- id: summarize
|
|
144
|
-
type: llm
|
|
145
|
-
model: haiku # optional: haiku, sonnet, opus
|
|
146
|
-
prompt: |
|
|
147
|
-
Summarize this data in 2-3 sentences.
|
|
148
|
-
Data: $steps.fetch_data.output.result
|
|
149
|
-
|
|
150
|
-
Write only the summary — no formatting, no preamble.
|
|
151
|
-
inputs:
|
|
152
|
-
data:
|
|
153
|
-
type: object
|
|
154
|
-
value: $steps.fetch_data.output.result
|
|
155
|
-
outputs:
|
|
156
|
-
summary:
|
|
157
|
-
type: string
|
|
158
|
-
value: $result
|
|
139
|
+
results:
|
|
140
|
+
type: array
|
|
141
|
+
value: $result.items # map from raw executor result
|
|
159
142
|
```
|
|
160
143
|
|
|
161
144
|
### Transform Step
|
|
@@ -215,7 +198,7 @@ When you have parallel arrays from different steps that need to be combined into
|
|
|
215
198
|
type: array
|
|
216
199
|
```
|
|
217
200
|
|
|
218
|
-
This is a pure data operation — never use
|
|
201
|
+
This is a pure data operation — never use a tool step for merging or zipping arrays when a transform step suffices.
|
|
219
202
|
|
|
220
203
|
### Transform Step (sort)
|
|
221
204
|
```yaml
|
|
@@ -309,38 +292,13 @@ outputs:
|
|
|
309
292
|
|
|
310
293
|
```yaml
|
|
311
294
|
outputs:
|
|
312
|
-
|
|
313
|
-
type:
|
|
314
|
-
value: $result.
|
|
295
|
+
results:
|
|
296
|
+
type: array
|
|
297
|
+
value: $result.items # maps from the tool's raw response
|
|
315
298
|
```
|
|
316
299
|
|
|
317
300
|
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.
|
|
318
301
|
|
|
319
|
-
**LLM step outputs require `value`.** LLM steps return the model's raw text (parsed as JSON when valid). Without `value`, downstream `$steps.<id>.output.<key>` references fail for plain text responses.
|
|
320
|
-
|
|
321
|
-
**Default: plain text with `value: $result`.** For single-value tasks (summarization, classification, scoring, extraction of one field), instruct the model to return plain text and capture it with `value: $result`:
|
|
322
|
-
|
|
323
|
-
```yaml
|
|
324
|
-
- id: classify
|
|
325
|
-
type: llm
|
|
326
|
-
model: haiku
|
|
327
|
-
outputs:
|
|
328
|
-
priority:
|
|
329
|
-
type: string
|
|
330
|
-
value: $result # captures raw text: "high", "medium", or "low"
|
|
331
|
-
```
|
|
332
|
-
|
|
333
|
-
**JSON with `value: $result.field` — only when the LLM must generate multiple fields that each require reasoning.** If the output has multiple fields but only one requires LLM judgment, use plain text for the LLM and a `map` transform to zip the LLM output with structural data:
|
|
334
|
-
|
|
335
|
-
```yaml
|
|
336
|
-
- id: analyze
|
|
337
|
-
type: llm
|
|
338
|
-
outputs:
|
|
339
|
-
analysis:
|
|
340
|
-
type: object
|
|
341
|
-
value: $result # parsed JSON object
|
|
342
|
-
```
|
|
343
|
-
|
|
344
302
|
## Expression Language
|
|
345
303
|
|
|
346
304
|
Use `$`-prefixed references to wire data between steps:
|
|
@@ -389,9 +347,9 @@ inputs:
|
|
|
389
347
|
|
|
390
348
|
### Iterating with `each` on Tool Steps
|
|
391
349
|
|
|
392
|
-
When you need to call
|
|
350
|
+
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.
|
|
393
351
|
|
|
394
|
-
**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.
|
|
352
|
+
**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`.
|
|
395
353
|
|
|
396
354
|
**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.
|
|
397
355
|
|
|
@@ -400,29 +358,31 @@ steps:
|
|
|
400
358
|
- id: get_ids
|
|
401
359
|
type: tool
|
|
402
360
|
tool: api.list_items
|
|
361
|
+
inputs:
|
|
362
|
+
url: { type: string, value: $inputs.api_url }
|
|
403
363
|
outputs:
|
|
404
|
-
|
|
405
|
-
type: array
|
|
364
|
+
items: { type: array, value: $result.items }
|
|
406
365
|
|
|
407
366
|
- id: fetch_details
|
|
408
367
|
type: tool
|
|
409
|
-
tool: api.get_item
|
|
410
|
-
each: $steps.get_ids.output.
|
|
411
|
-
|
|
368
|
+
tool: api.get_item
|
|
369
|
+
each: $steps.get_ids.output.items # iterate over items array
|
|
370
|
+
delay: "2s" # required: rate limit between calls
|
|
371
|
+
on_error: ignore # skip failed fetches, continue
|
|
412
372
|
inputs:
|
|
413
|
-
|
|
373
|
+
id:
|
|
414
374
|
type: string
|
|
415
|
-
value:
|
|
375
|
+
value: $item.id # each item's ID from the listing
|
|
416
376
|
outputs:
|
|
417
377
|
title:
|
|
418
378
|
type: string
|
|
419
|
-
value: $result.
|
|
420
|
-
|
|
421
|
-
type:
|
|
422
|
-
value: $result.
|
|
379
|
+
value: $result.title # mapped per iteration via $result
|
|
380
|
+
summary:
|
|
381
|
+
type: string
|
|
382
|
+
value: $result.summary
|
|
423
383
|
```
|
|
424
384
|
|
|
425
|
-
After this step, `$steps.fetch_details.output` is an array of `{ title,
|
|
385
|
+
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.
|
|
426
386
|
|
|
427
387
|
**Workflow output for each+tool:**
|
|
428
388
|
```yaml
|
|
@@ -434,16 +394,16 @@ outputs:
|
|
|
434
394
|
|
|
435
395
|
**Pattern: List → Slice → Fetch Details**
|
|
436
396
|
|
|
437
|
-
Full example
|
|
397
|
+
Full example fetching a listing then fetching each detail via `each`:
|
|
438
398
|
|
|
439
399
|
```yaml
|
|
440
400
|
inputs:
|
|
401
|
+
api_url:
|
|
402
|
+
type: string
|
|
403
|
+
default: "https://api.example.com/items"
|
|
441
404
|
count:
|
|
442
405
|
type: int
|
|
443
406
|
default: 10
|
|
444
|
-
base_url:
|
|
445
|
-
type: string
|
|
446
|
-
default: "https://api.example.com/item/"
|
|
447
407
|
|
|
448
408
|
outputs:
|
|
449
409
|
items:
|
|
@@ -451,231 +411,46 @@ outputs:
|
|
|
451
411
|
value: $steps.fetch_details.output
|
|
452
412
|
|
|
453
413
|
steps:
|
|
454
|
-
- id:
|
|
414
|
+
- id: get_listing
|
|
455
415
|
type: tool
|
|
456
416
|
tool: api.list_items
|
|
457
417
|
inputs:
|
|
458
|
-
url: { type: string, value:
|
|
418
|
+
url: { type: string, value: $inputs.api_url }
|
|
459
419
|
outputs:
|
|
460
|
-
|
|
420
|
+
items: { type: array, value: $result.items }
|
|
461
421
|
|
|
462
|
-
- id:
|
|
422
|
+
- id: slice_items
|
|
463
423
|
type: transform
|
|
464
424
|
operation: filter
|
|
465
425
|
where: $index < $inputs.count # cap iteration count to avoid rate limiting
|
|
466
426
|
inputs:
|
|
467
|
-
items: { type: array, value: $steps.
|
|
427
|
+
items: { type: array, value: $steps.get_listing.output.items }
|
|
468
428
|
outputs:
|
|
469
|
-
|
|
429
|
+
items: { type: array }
|
|
470
430
|
|
|
471
431
|
- id: fetch_details
|
|
472
432
|
type: tool
|
|
473
433
|
tool: api.get_item
|
|
474
|
-
each: $steps.
|
|
475
|
-
delay: "2s"
|
|
476
|
-
retry: { max: 3, delay: "2s", backoff: 1.5 }
|
|
477
|
-
on_error: ignore
|
|
478
|
-
inputs:
|
|
479
|
-
url:
|
|
480
|
-
type: string
|
|
481
|
-
value: "${inputs.base_url}${item}.json"
|
|
482
|
-
outputs:
|
|
483
|
-
title: { type: string, value: $result.body.title }
|
|
484
|
-
score: { type: int, value: $result.body.score }
|
|
485
|
-
url: { type: string, value: $result.body.url }
|
|
486
|
-
```
|
|
487
|
-
|
|
488
|
-
### Iterating with `each` on LLM Steps
|
|
489
|
-
|
|
490
|
-
When you have an array of items that each need LLM reasoning (summarization, classification, extraction), use `each` on the LLM step — just like tool steps. **Do not dump the entire array into a single prompt.** Always add `delay` to LLM `each` loops — LLM APIs enforce token-per-minute limits, and `delay: "1s"` is the minimum safe default.
|
|
491
|
-
|
|
492
|
-
**Why iterate instead of batch:**
|
|
493
|
-
- **Token bounds** — Each call processes one item, so prompt size is predictable and bounded. Batching N items risks hitting context limits when items are large (e.g., HTML content).
|
|
494
|
-
- **Error isolation** — If one item produces malformed output, only that item fails. With `on_error: ignore`, the rest succeed. Batching loses *all* results if the model returns one malformed JSON array.
|
|
495
|
-
- **Prompt simplicity** — "Summarize this one item" is a trivial prompt. "Parse N items and return an N-element array with exact positional correspondence" is fragile and error-prone.
|
|
496
|
-
|
|
497
|
-
**Pattern: `each` + LLM with plain text output + map transform**
|
|
498
|
-
|
|
499
|
-
Use plain text output (`value: $result`) for the LLM step, then zip the LLM results with structural data from the source array using a `map` transform:
|
|
500
|
-
|
|
501
|
-
```yaml
|
|
502
|
-
steps:
|
|
503
|
-
- id: fetch_items
|
|
504
|
-
type: tool
|
|
505
|
-
tool: api.get_item # platform-specific; use web.scrape for HTML pages
|
|
506
|
-
each: $steps.get_ids.output.ids
|
|
507
|
-
delay: "2s"
|
|
508
|
-
on_error: ignore
|
|
509
|
-
inputs:
|
|
510
|
-
url:
|
|
511
|
-
type: string
|
|
512
|
-
value: "${inputs.base_url}${item}.json"
|
|
513
|
-
outputs:
|
|
514
|
-
title: { type: string, value: $result.body.title }
|
|
515
|
-
content: { type: string, value: $result.body.content }
|
|
516
|
-
|
|
517
|
-
- id: summarize
|
|
518
|
-
type: llm
|
|
519
|
-
model: haiku
|
|
520
|
-
each: $steps.fetch_items.output # iterate over the collected array
|
|
521
|
-
delay: "1s" # rate-limit: 1s pause between iterations
|
|
522
|
-
on_error: ignore # skip items that fail
|
|
523
|
-
description: Summarize each item individually
|
|
524
|
-
prompt: |
|
|
525
|
-
Summarize this item in 1-2 sentences.
|
|
526
|
-
|
|
527
|
-
Title: $item.title
|
|
528
|
-
Content: $item.content
|
|
529
|
-
|
|
530
|
-
Write only the summary — no formatting, no preamble.
|
|
531
|
-
inputs:
|
|
532
|
-
item:
|
|
533
|
-
type: object
|
|
534
|
-
value: $item
|
|
535
|
-
outputs:
|
|
536
|
-
summary:
|
|
537
|
-
type: string
|
|
538
|
-
value: $result # plain text — one summary per iteration
|
|
539
|
-
|
|
540
|
-
- id: combine_results
|
|
541
|
-
type: transform
|
|
542
|
-
operation: map
|
|
543
|
-
description: Zip summaries with source data
|
|
544
|
-
expression:
|
|
545
|
-
title: $item.title
|
|
546
|
-
description: $steps.summarize.output[$index].summary
|
|
547
|
-
inputs:
|
|
548
|
-
items: { type: array, value: $steps.fetch_items.output }
|
|
549
|
-
outputs:
|
|
550
|
-
items: { type: array }
|
|
551
|
-
```
|
|
552
|
-
|
|
553
|
-
After this, `$steps.combine_results.output.items` is an array of `{ title, description }` objects — structural data from the tool step, LLM-generated text from the summarize step.
|
|
554
|
-
|
|
555
|
-
**Why plain text over JSON for `each` + LLM:**
|
|
556
|
-
- **Fence risk** — Models frequently wrap JSON in markdown fences (`` ```json...``` ``) despite explicit instructions. The runtime parses with `JSON.parse`, which rejects fenced output. Plain text has no parsing step — what the model writes is what you get.
|
|
557
|
-
- **Silent failures** — When JSON parsing fails, the output stays as a raw string. `$result.field` on a string returns `undefined`, which propagates as `{}` downstream. No error is thrown — the workflow "succeeds" with empty objects.
|
|
558
|
-
- **Structural data doesn't need LLM generation** — Fields like `title`, `id`, `score` already exist in the source data. Only the LLM-generated field (summary, classification, score) needs to come from the model. Use a `map` transform to zip them together.
|
|
559
|
-
|
|
560
|
-
**Workflow output for each+LLM:**
|
|
561
|
-
```yaml
|
|
562
|
-
outputs:
|
|
563
|
-
summaries:
|
|
564
|
-
type: array
|
|
565
|
-
value: $steps.combine_results.output.items # the zipped array
|
|
566
|
-
```
|
|
567
|
-
|
|
568
|
-
**Anti-pattern — JSON output in `each` + LLM:**
|
|
569
|
-
```yaml
|
|
570
|
-
# BAD: model may return fenced JSON → parse fails → $result.field returns undefined → silent {}
|
|
571
|
-
- id: summarize
|
|
572
|
-
type: llm
|
|
573
|
-
each: $steps.fetch_items.output
|
|
574
|
-
prompt: |
|
|
575
|
-
Return a JSON object with "title" and "description" fields.
|
|
576
|
-
Respond with raw JSON only — no markdown fences.
|
|
577
|
-
outputs:
|
|
578
|
-
title: { type: string, value: $result.title } # undefined if fenced
|
|
579
|
-
description: { type: string, value: $result.description } # undefined if fenced
|
|
580
|
-
```
|
|
581
|
-
|
|
582
|
-
**Anti-pattern — batching all items into one prompt:**
|
|
583
|
-
```yaml
|
|
584
|
-
# BAD: unbounded prompt size, all-or-nothing failure, complex output format
|
|
585
|
-
- id: summarize
|
|
586
|
-
type: llm
|
|
587
|
-
prompt: |
|
|
588
|
-
Summarize each job below...
|
|
589
|
-
Jobs: $steps.fetch_items.output # dumps entire array into prompt
|
|
590
|
-
outputs:
|
|
591
|
-
roles: { type: array, value: $result } # one malformed response loses everything
|
|
592
|
-
```
|
|
593
|
-
|
|
594
|
-
**When bulk IS acceptable:** Use a single LLM call with the full array only when the task requires cross-item reasoning — ranking, deduplication, holistic comparison, or generating a unified summary across all items. If each item can be processed independently, always use `each`.
|
|
595
|
-
|
|
596
|
-
## Web Scraping Pattern
|
|
597
|
-
|
|
598
|
-
When a workflow fetches HTML and extracts structured data, follow this recipe:
|
|
599
|
-
|
|
600
|
-
### Step pattern: scrape → guard
|
|
601
|
-
|
|
602
|
-
`web.scrape` fetches the URL and applies CSS selectors in one step:
|
|
603
|
-
|
|
604
|
-
```yaml
|
|
605
|
-
steps:
|
|
606
|
-
- id: scrape_data
|
|
607
|
-
type: tool
|
|
608
|
-
tool: web.scrape
|
|
609
|
-
retry: { max: 3, delay: "2s", backoff: 1.5 }
|
|
610
|
-
inputs:
|
|
611
|
-
url: { type: string, value: "https://example.com/search" }
|
|
612
|
-
headers:
|
|
613
|
-
type: object
|
|
614
|
-
value: { "User-Agent": "Mozilla/5.0", "Accept": "text/html" }
|
|
615
|
-
selector: { type: string, value: "li.result-item" }
|
|
616
|
-
fields:
|
|
617
|
-
type: object
|
|
618
|
-
value:
|
|
619
|
-
title: "h3.title"
|
|
620
|
-
url: "a.link @href"
|
|
621
|
-
id: "@data-pid"
|
|
622
|
-
limit: { type: int, value: 50 }
|
|
623
|
-
outputs:
|
|
624
|
-
items: { type: array, value: $result.results }
|
|
625
|
-
|
|
626
|
-
- id: guard_empty
|
|
627
|
-
type: exit
|
|
628
|
-
condition: $steps.scrape_data.output.items.length == 0
|
|
629
|
-
status: success
|
|
630
|
-
output: { results: [] }
|
|
631
|
-
inputs: {}
|
|
632
|
-
outputs: {}
|
|
633
|
-
```
|
|
634
|
-
|
|
635
|
-
### Multi-page scraping with `each`
|
|
636
|
-
|
|
637
|
-
When scraping multiple pages, combine `web.scrape` with `each`:
|
|
638
|
-
|
|
639
|
-
```yaml
|
|
640
|
-
steps:
|
|
641
|
-
- id: get_page_list
|
|
642
|
-
type: tool
|
|
643
|
-
tool: api.get_sitemap # returns list of page URLs to scrape
|
|
644
|
-
outputs:
|
|
645
|
-
pages:
|
|
646
|
-
type: array
|
|
647
|
-
|
|
648
|
-
- id: scrape_pages
|
|
649
|
-
type: tool
|
|
650
|
-
tool: web.scrape
|
|
651
|
-
each: $steps.get_page_list.output.pages
|
|
434
|
+
each: $steps.slice_items.output.items
|
|
652
435
|
delay: "2s"
|
|
653
436
|
retry: { max: 3, delay: "2s", backoff: 1.5 }
|
|
654
437
|
on_error: ignore
|
|
655
438
|
inputs:
|
|
656
|
-
|
|
657
|
-
selector: { type: string, value: "article.post" }
|
|
658
|
-
fields:
|
|
659
|
-
type: object
|
|
660
|
-
value:
|
|
661
|
-
title: "h1.title"
|
|
662
|
-
body: "div.content"
|
|
439
|
+
id: { type: string, value: $item.id }
|
|
663
440
|
outputs:
|
|
664
|
-
|
|
441
|
+
title: { type: string, value: $result.title }
|
|
442
|
+
summary: { type: string, value: $result.summary }
|
|
443
|
+
score: { type: string, value: $result.score }
|
|
665
444
|
```
|
|
666
445
|
|
|
667
|
-
### Selector research
|
|
668
|
-
|
|
669
|
-
Follow the Research protocol (Authoring Process, Phase 2) before writing selectors. Every selector must be verified against actual fetched HTML.
|
|
670
|
-
|
|
671
446
|
## Authoring Rules
|
|
672
447
|
|
|
673
|
-
1. **
|
|
674
|
-
2. **Use
|
|
448
|
+
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.
|
|
449
|
+
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.
|
|
675
450
|
3. **Always declare inputs and outputs.** They enable validation and composability.
|
|
676
451
|
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.
|
|
677
|
-
5. **Use `value` on step outputs** to map fields from the raw executor result using `$result`.
|
|
678
|
-
6. **Use `each` for per-item processing** on
|
|
452
|
+
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.
|
|
453
|
+
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*.
|
|
679
454
|
7. **Add `on_error: ignore` for non-critical steps** like notifications.
|
|
680
455
|
8. **Add `retry` for external API calls** (tool steps that might fail transiently).
|
|
681
456
|
9. **Use `condition` guards for early exits** rather than letting empty data flow through.
|
|
@@ -684,11 +459,9 @@ Follow the Research protocol (Authoring Process, Phase 2) before writing selecto
|
|
|
684
459
|
12. **`condition` on a `conditional` step is the branch condition**, not a guard.
|
|
685
460
|
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.
|
|
686
461
|
14. **Transform steps are for arrays only.** Never use a transform to extract fields from a single object.
|
|
687
|
-
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.
|
|
688
|
-
16. **
|
|
689
|
-
17. **
|
|
690
|
-
18. **Prefer bulk endpoints over per-item iteration.** When per-item `each` + tool calls (including `web.scrape`) are unavoidable, always add `delay: "2s"` (minimum), cap iteration count, and add `retry` with `backoff`. `delay` is not optional — external sites and APIs rate-limit without warning and delays are free. Same applies to `each` + `llm` steps: always add `delay: "1s"`. See *Iteration Patterns*.
|
|
691
|
-
19. **Prefer plain text LLM output over JSON.** For single-value tasks (summarization, classification, scoring), use `value: $result` and instruct the model to return plain text. Reserve JSON (`value: $result.field`) for multi-field output where every field requires LLM reasoning. In `each` + LLM patterns, always use plain text + a `map` transform to zip LLM output with structural data from the source array. See *Iteration Patterns*.
|
|
462
|
+
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.
|
|
463
|
+
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*.
|
|
464
|
+
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*.
|
|
692
465
|
|
|
693
466
|
## Output Format
|
|
694
467
|
|
|
@@ -710,9 +483,9 @@ description: Fetches data and outputs a specific field
|
|
|
710
483
|
|
|
711
484
|
` `` `workflow
|
|
712
485
|
inputs:
|
|
713
|
-
|
|
486
|
+
url:
|
|
714
487
|
type: string
|
|
715
|
-
default: "
|
|
488
|
+
default: "https://api.example.com/items"
|
|
716
489
|
|
|
717
490
|
outputs:
|
|
718
491
|
name:
|
|
@@ -722,15 +495,15 @@ outputs:
|
|
|
722
495
|
steps:
|
|
723
496
|
- id: fetch
|
|
724
497
|
type: tool
|
|
725
|
-
tool:
|
|
498
|
+
tool: api.get_item
|
|
726
499
|
inputs:
|
|
727
|
-
|
|
500
|
+
url:
|
|
728
501
|
type: string
|
|
729
|
-
value: $inputs.
|
|
502
|
+
value: $inputs.url
|
|
730
503
|
outputs:
|
|
731
504
|
name:
|
|
732
505
|
type: string
|
|
733
|
-
value: $result.
|
|
506
|
+
value: $result.name
|
|
734
507
|
` `` `
|
|
735
508
|
```
|
|
736
509
|
|
|
@@ -745,11 +518,8 @@ After writing the file, always validate it against the runtime. The validation c
|
|
|
745
518
|
- [ ] `each` not used on exit or conditional steps
|
|
746
519
|
- [ ] Workflow outputs have `value` mapping to `$steps` references
|
|
747
520
|
- [ ] Step output `value` uses `$result` (not `$steps`)
|
|
748
|
-
- [ ] LLM step outputs have `value` using `$result`
|
|
749
521
|
- [ ] All `${}` template references resolve to declared inputs/steps
|
|
750
|
-
- [ ]
|
|
751
|
-
- [ ]
|
|
752
|
-
- [ ] Every `each` loop that calls an external service has `delay` (tool steps: `"2s"` minimum; LLM steps: `"1s"` minimum)
|
|
753
|
-
- [ ] `each` + `web.scrape` steps are bounded (preceded by a cap) and have `retry` with `backoff`
|
|
522
|
+
- [ ] Every `each` loop that calls an external service has `delay` (`"2s"` minimum)
|
|
523
|
+
- [ ] `each` + tool steps with per-item fetching are bounded (preceded by a cap) and have `retry` with `backoff`
|
|
754
524
|
|
|
755
525
|
If validation fails, fix the errors and revalidate.
|
package/dist/cli/index.d.mts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { };
|