tubeless 0.2.2 → 0.2.3

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.
Files changed (87) hide show
  1. package/README.md +5 -5
  2. package/dist/core/child-execution.js +8 -1
  3. package/dist/core/child-execution.js.map +1 -1
  4. package/dist/core/lifecycle.js +2 -0
  5. package/dist/core/lifecycle.js.map +1 -1
  6. package/dist/core/pipeline-execute.d.ts +2 -0
  7. package/dist/core/pipeline-execute.js +132 -395
  8. package/dist/core/pipeline-execute.js.map +1 -1
  9. package/dist/core/pipeline-plan.js +96 -34
  10. package/dist/core/pipeline-plan.js.map +1 -1
  11. package/dist/core/pipeline-run-state.d.ts +49 -0
  12. package/dist/core/pipeline-run-state.js +284 -0
  13. package/dist/core/pipeline-run-state.js.map +1 -0
  14. package/dist/core/pipeline-step-executor.d.ts +20 -0
  15. package/dist/core/pipeline-step-executor.js +52 -0
  16. package/dist/core/pipeline-step-executor.js.map +1 -0
  17. package/dist/core/pipeline-types.d.ts +11 -2
  18. package/dist/core/pipeline-types.js.map +1 -1
  19. package/dist/core/pipeline-validation.d.ts +6 -0
  20. package/dist/core/pipeline-validation.js +41 -0
  21. package/dist/core/pipeline-validation.js.map +1 -0
  22. package/dist/run-store/run-store-ndjson.js +2 -295
  23. package/dist/run-store/run-store-ndjson.js.map +1 -1
  24. package/dist/run-store/run-store-sqlite.js +91 -42
  25. package/dist/run-store/run-store-sqlite.js.map +1 -1
  26. package/dist/run-store/run-store.d.ts +3 -2
  27. package/dist/run-store/run-store.js +38 -195
  28. package/dist/run-store/run-store.js.map +1 -1
  29. package/dist/studio/run-store-ui-client-controller.d.ts +9 -0
  30. package/dist/studio/run-store-ui-client-controller.js +479 -0
  31. package/dist/studio/run-store-ui-client-controller.js.map +1 -0
  32. package/dist/studio/run-store-ui-client-form.d.ts +9 -0
  33. package/dist/studio/run-store-ui-client-form.js +44 -0
  34. package/dist/studio/run-store-ui-client-form.js.map +1 -0
  35. package/dist/studio/run-store-ui-client-model.d.ts +40 -0
  36. package/dist/studio/run-store-ui-client-model.js +211 -0
  37. package/dist/studio/run-store-ui-client-model.js.map +1 -0
  38. package/dist/studio/run-store-ui-client-source.js +1 -1
  39. package/dist/studio/run-store-ui-client-transport.d.ts +25 -0
  40. package/dist/studio/run-store-ui-client-transport.js +322 -0
  41. package/dist/studio/run-store-ui-client-transport.js.map +1 -0
  42. package/dist/studio/run-store-ui-client-views.d.ts +24 -0
  43. package/dist/studio/run-store-ui-client-views.js +676 -0
  44. package/dist/studio/run-store-ui-client-views.js.map +1 -0
  45. package/dist/studio/run-store-ui-client.d.ts +2 -14
  46. package/dist/studio/run-store-ui-client.js +7 -1360
  47. package/dist/studio/run-store-ui-client.js.map +1 -1
  48. package/dist/studio/run-store-ui-page.d.ts +1 -1
  49. package/dist/studio/run-store-ui.js +44 -25
  50. package/dist/studio/run-store-ui.js.map +1 -1
  51. package/dist/tracing/tracing-codec.d.ts +6 -0
  52. package/dist/tracing/tracing-codec.js +829 -0
  53. package/dist/tracing/tracing-codec.js.map +1 -0
  54. package/dist/tracing/tracing-constants.d.ts +4 -0
  55. package/dist/tracing/tracing-constants.js +5 -0
  56. package/dist/tracing/tracing-constants.js.map +1 -0
  57. package/dist/tracing/tracing-internal.js +116 -81
  58. package/dist/tracing/tracing-internal.js.map +1 -1
  59. package/dist/tracing/tracing-otel.js +19 -3
  60. package/dist/tracing/tracing-otel.js.map +1 -1
  61. package/dist/tracing/tracing.d.ts +149 -10
  62. package/dist/tracing/tracing.js.map +1 -1
  63. package/dist/workbench/workbench-history.js +8 -1
  64. package/dist/workbench/workbench-history.js.map +1 -1
  65. package/dist/workbench/workbench-ui.js +26 -6
  66. package/dist/workbench/workbench-ui.js.map +1 -1
  67. package/docs/README.md +21 -15
  68. package/docs/agent-guide.md +48 -35
  69. package/docs/agent-skills.md +59 -0
  70. package/docs/api-reference.md +28 -12
  71. package/docs/api-report.json +29 -13
  72. package/docs/child-pipeline-composition.md +142 -193
  73. package/docs/cli.md +83 -61
  74. package/docs/comparison.md +63 -62
  75. package/docs/concepts.md +210 -272
  76. package/docs/getting-started.md +59 -48
  77. package/docs/llms.txt +7 -4
  78. package/docs/recipes.md +21 -18
  79. package/docs/remote-step-composition.md +69 -74
  80. package/docs/studio.md +146 -71
  81. package/examples/catalog/pipelines/enrich.ts +2 -2
  82. package/examples/catalog/tubeless.project.ts +4 -2
  83. package/examples/host-embedding.ts +4 -5
  84. package/examples/remote-steps.ts +8 -5
  85. package/package.json +11 -10
  86. package/skills/tubeless/SKILL.md +83 -0
  87. package/skills/tubeless-make-pipeline/SKILL.md +126 -0
@@ -79,7 +79,7 @@
79
79
  "toMappedChildStepProgress",
80
80
  "ToMappedChildStepProgressOptions"
81
81
  ],
82
- "sha256": "4c9eea9c3243f304493c43578af2c64d9406610054db5694519145854393ac15",
82
+ "sha256": "caccbd005663d2c525deb371fc465e132d6aa51071f956e25d7632430d955098",
83
83
  "specifier": "tubeless"
84
84
  },
85
85
  {
@@ -135,7 +135,7 @@
135
135
  "PromptMultiSelectOptions",
136
136
  "TUBELESS_WORKBENCH_EXIT_CODE"
137
137
  ],
138
- "sha256": "afcd70f9ca23dac58af7a5256fb593cf3dd9b4f4718910ba30a6e0db838f3fcc",
138
+ "sha256": "3e188b4fa127274a443d327d9a6ce6c4aa97e88d80c9a1420d25a9696993dbc6",
139
139
  "specifier": "tubeless/cli"
140
140
  },
141
141
  {
@@ -169,7 +169,7 @@
169
169
  "renderPipelineError",
170
170
  "renderPipelinePlan"
171
171
  ],
172
- "sha256": "5b3c6183d1df53eb5474cde672eb2614f6887381d65656544c30a9c5ce8d7b37",
172
+ "sha256": "07fa0a2cf3db19998b426eee827e0902b10235a169fb80d7731f5254b1a80197",
173
173
  "specifier": "tubeless/render"
174
174
  },
175
175
  {
@@ -189,7 +189,7 @@
189
189
  "RunReporterConfig",
190
190
  "RunReporterOptions"
191
191
  ],
192
- "sha256": "b745445b09b8122bd58c7c40eb910b9ccfd916c4664cd2e2d301a2b98961e59e",
192
+ "sha256": "0b101c3f1c4a3a1f581fbd19df2c05fa5de9c4504537db12399a3fb876c725ff",
193
193
  "specifier": "tubeless/reporter"
194
194
  },
195
195
  {
@@ -220,7 +220,7 @@
220
220
  "StoredPipelineStep",
221
221
  "StoredRemote"
222
222
  ],
223
- "sha256": "db7d9e8c4f82bcb22d5ec7eecb47814f9fdfc961a8a11fc805397042a7040b7f",
223
+ "sha256": "06bf5a381752346d51682b74347306387d7d06622ff48fd9f29876b0e7a33c7e",
224
224
  "specifier": "tubeless/run-store"
225
225
  },
226
226
  {
@@ -230,7 +230,7 @@
230
230
  "OpenSqlitePipelineRunStoreOptions",
231
231
  "SqlitePipelineRunStore"
232
232
  ],
233
- "sha256": "a9363f5b1c72ebfd508bd66827e5a931d243ed2a804dbd99070e8abd6d0010d1",
233
+ "sha256": "5a497dfdbac584459c79c303c25e3be95e893ea6b5d2047c01f061eceba25ffc",
234
234
  "specifier": "tubeless/run-store/sqlite"
235
235
  },
236
236
  {
@@ -240,7 +240,7 @@
240
240
  "openNdjsonPipelineRunStore",
241
241
  "OpenNdjsonPipelineRunStoreOptions"
242
242
  ],
243
- "sha256": "011bef12c4c8bc4b1eedfb5dba72e6d1eccf4ebf04a7306837fef978a32fc53b",
243
+ "sha256": "04c65f3e126ed97fe83b427a133450763d4caa996bb188d6c84f366db85fbb9f",
244
244
  "specifier": "tubeless/run-store/ndjson"
245
245
  },
246
246
  {
@@ -282,7 +282,7 @@
282
282
  "PipelineRunStudioServer",
283
283
  "startPipelineRunStudio"
284
284
  ],
285
- "sha256": "69e1f6c6508507b8cd346dc94c6a877b1e8b22d29e2c9f9212366677afc6d91b",
285
+ "sha256": "02dec5c804295fb172f7af226ea2c60a2095685b0ce72cb8ee7902b8913d4a97",
286
286
  "specifier": "tubeless/run-store/ui"
287
287
  },
288
288
  {
@@ -296,13 +296,19 @@
296
296
  "PipelineTestRuntimeOptions",
297
297
  "PipelineTestSleep"
298
298
  ],
299
- "sha256": "150386a43659704068ce0219dbacb8f71dd9d83f35ac0859c197e59721ab96ea",
299
+ "sha256": "f84c8c3b0ae48b8c49d6cabb1a2c5988770059838ffab6f94a8444fd0fff695c",
300
300
  "specifier": "tubeless/testing"
301
301
  },
302
302
  {
303
303
  "declaration": "./dist/tracing/tracing.d.ts",
304
304
  "exports": [
305
305
  "composeTraceExporters",
306
+ "PipelineCompletedTraceEvent",
307
+ "PipelineFinalizeCompletedTraceEvent",
308
+ "PipelineFinalizeFailedTraceEvent",
309
+ "PipelineFinalizeStartedTraceEvent",
310
+ "PipelineLogTraceEvent",
311
+ "PipelineStartedTraceEvent",
306
312
  "PipelineTraceAttributes",
307
313
  "PipelineTraceAttributeValue",
308
314
  "PipelineTraceContext",
@@ -310,15 +316,25 @@
310
316
  "PipelineTraceEvent",
311
317
  "PipelineTraceEventName",
312
318
  "PipelineTraceExporter",
313
- "PipelineTracingOptions"
319
+ "PipelineTraceNestedPipeline",
320
+ "PipelineTraceProgress",
321
+ "PipelineTraceRemote",
322
+ "PipelineTracingOptions",
323
+ "StepAttemptedTraceEvent",
324
+ "StepCancelledTraceEvent",
325
+ "StepCompletedTraceEvent",
326
+ "StepFailedTraceEvent",
327
+ "StepPlannedTraceEvent",
328
+ "StepRunningTraceEvent",
329
+ "StepSkippedTraceEvent"
314
330
  ],
315
- "sha256": "4c9eea9c3243f304493c43578af2c64d9406610054db5694519145854393ac15",
331
+ "sha256": "caccbd005663d2c525deb371fc465e132d6aa51071f956e25d7632430d955098",
316
332
  "specifier": "tubeless/tracing"
317
333
  },
318
334
  {
319
335
  "declaration": "./dist/tracing/tracing-json.d.ts",
320
336
  "exports": ["createJsonTraceExporter", "JsonTraceExporterOptions"],
321
- "sha256": "d19223de841ea24d9c6ab6342bbfdcbeb158f700e54b4072a7d334a309add820",
337
+ "sha256": "040c6496477e3be9618eedd0ee74b317bff12fd390c6cbf0b36c725c5e4a5464",
322
338
  "specifier": "tubeless/tracing/json"
323
339
  },
324
340
  {
@@ -329,7 +345,7 @@
329
345
  "OpenTelemetryLikeTracer",
330
346
  "OpenTelemetryTraceExporterOptions"
331
347
  ],
332
- "sha256": "1ea929831eb01e14e7717bb13e12f1bc6c686411eed41849cbdd493490bfe06a",
348
+ "sha256": "0f86d65848e5dc133265f0e3379c64e6a8490c345a1e7077d2570cc630f669fe",
333
349
  "specifier": "tubeless/tracing/otel"
334
350
  }
335
351
  ],
@@ -1,98 +1,23 @@
1
1
  # Child-pipeline composition
2
2
 
3
- ## What ships
3
+ Use a child pipeline when part of a workflow is useful on its own and should
4
+ also run inside a larger pipeline. Tubeless provides two step builders:
4
5
 
5
- Compose a child pipeline as one typed parent step with
6
- `createSteps<TOptions>().fromPipeline(...)`. Overloads cover identity and
7
- mapped results. An async `fromPipeline` `mapResult` publishes its resolved value;
8
- dependents receive `Awaited<TOut>` and skippable adapters accept that resolved
9
- shape as their skip value. Child lifecycle hooks stay isolated, and child work reports
10
- through parent-step progress. Interactive reporters display selected child steps
11
- as an indented tree, retaining their terminal states and inner work counts.
6
+ | Builder | Use it to | Step output |
7
+ | ---------------------- | ----------------------------------------------- | ---------------------------------------- |
8
+ | `step.fromPipeline` | Run one child pipeline | The child's final result |
9
+ | `step.forEachPipeline` | Run the same child pipeline for a list of items | An array of child results in input order |
12
10
 
13
- Map a runtime item list onto the same child pipeline with
14
- `createSteps<TOptions>().forEachPipeline(...)`. Item keys are stable,
15
- concurrency is bounded, and results keep input order even when children
16
- finish out of order. All running children settle before an aggregate failure
17
- is returned, so the parent cannot finalize while child side effects are still
18
- in flight.
11
+ Both builders infer the child options and result types. They forward the
12
+ parent's runtime context and report child progress through the parent step.
13
+ Start with the [single-child example](../examples/child-pipeline.ts) or the
14
+ [fan-out example](../examples/fan-out-progress.ts).
19
15
 
20
- Opaque parent steps pass outputs through typed dependencies, skip side
21
- effects in dry-run, and convert domain validation errors into failed parent
22
- steps.
16
+ ## Run a child pipeline
23
17
 
24
- ## Why not call the child from an ordinary step
25
-
26
- Invoking a child pipeline from an ordinary parent step and passing the parent
27
- `PipelineStepContext` gives the child the parent's runtime seams, but it also
28
- gives the child the parent's raw hooks and parent-only `reportProgress`
29
- callback.
30
-
31
- The resulting lifecycle stream is flat. A direct nested run emits the child pipeline, step, finalize, and completion events between the parent wrapper step's start and completion events. Most events have no pipeline path or run identity. The interactive reporter also owns one plan, one step map, and one result. A child start replaces its visible plan, and a child completion disposes the reporter before the parent completes.
32
-
33
- The package needs a composition contract that preserves typing and runtime behavior without exposing two pipeline frames through hooks designed for one pipeline.
34
-
35
- ## Goals
36
-
37
- - Model a child pipeline as one typed step in the parent DAG.
38
- - Infer parent dependency inputs, parent options, child options, the child result, and any mapped result.
39
- - Preserve cancellation, logging, timing, working-directory, dry-run, progress, and failure behavior.
40
- - Keep raw child lifecycle hooks inside the adapter while reporting useful progress through the opaque parent step.
41
- - Keep the first slice bounded: one opaque child, or a mapped set of the same child.
42
-
43
- ## Supported: policy skip on opaque steps
44
-
45
- Policy skip (`step.skippable` / `StepSkipDecision`) is part of the shipped API and
46
- applies to ordinary steps, `fromPipeline` adapters, and mapped
47
- `forEachPipeline` adapters (the opaque parent step is a normal step under the
48
- hood):
49
-
50
- - Return a non-empty string or `{ reason, value? }` from `skip` to skip without
51
- calling `run` (or without running the child). Reporters show a yellow skip
52
- whose terminal report has `status: "skipped"` and `reason: "policy"`.
53
- - Policy skips unlock required dependents. A bare string (or `{ reason }`
54
- without `value`) publishes `undefined` as the step output.
55
- - Any step that declares `skip` is typed so dependents see `TOut | undefined`
56
- (including `PipelineResultOf<Child> | undefined` for `fromPipeline` and
57
- `readonly PipelineResultOf<Child>[] | undefined` for `forEachPipeline`, both
58
- without `mapResult`). Prefer `{ reason, value }` on every skip path when
59
- dependents need a real output. With `mapResult`, skip `value` is the
60
- parent-facing mapped output — one `TOut` for `fromPipeline`, or the complete
61
- `readonly TOut[]` for `forEachPipeline`. Policy skip does not call `mapResult`.
62
-
63
- Use `fromPipeline.skippable` or `forEachPipeline.skippable` to policy-skip
64
- optional child work and publish a concrete disabled result via
65
- `{ reason, value }`. The ordinary constructors reject `skip` and retain their
66
- non-optional output types.
67
-
68
- ## Non-goals
69
-
70
- - Flatten child steps into the parent plan.
71
- - Add hierarchical or versioned external lifecycle events.
72
- - Support namespaced selection such as `parent.child-step`.
73
- - Add parallel DAG execution, remote checkpoints, or subcommands.
74
- - Injected runner overrides (substituting a child pipeline implementation at
75
- test time via parent options) — separate from policy skip.
76
-
77
- ## Why this shape
78
-
79
- | Model | Typing | Planning | Hooks and reporting | Selective execution | Failure | Dry run | Cost |
80
- | ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
81
- | Continue manual shared-context calls | Each wrapper can map options and return a typed value, but each wrapper repeats the contract and structural context passing makes unsafe fields easy to forward. | The parent sees only the wrapper step; the child creates a separate plan at runtime. | Raw child events enter the parent's flat hook stream. The current interactive reporter replaces the parent frame and disposes on child completion. | Parent selection can select the wrapper. Child selection depends on ad hoc mapped `stepIds`. | `runOrThrow` can fail the wrapper, but consistent child-identifying messages and progress require repeated wrapper code. | Each wrapper must remember parent/child dry-run precedence and per-step policy. | Low per call, with continuing duplication and reporter breakage. |
82
- | Flatten the child DAG into the parent DAG | The API must translate child option and dependency types into a parent definition without losing inference. | Child ids need stable namespacing, and runtime option mapping can change which child plan is valid. | A flat plan fits current hooks only after defining pipeline identity, paths, and collision rules. | Namespaced filters need semantics for parent dependencies, child dependencies, and finalization. | The parent must define how child failures, partial outputs, and child finalization affect the flattened DAG. | The parent must reconcile its dry-run rules with every flattened child step. | High. It requires event identity and selection semantics before the first useful slice. |
83
- | Opaque typed child-step adapter | A parent-scoped factory can infer dependency inputs and parent options while a typed child pipeline constrains mapped options and results. | The parent plan contains one stable step. The adapter validates the child plan immediately before running it. | Parent hooks see the opaque step only. An internal bridge translates child terminal events into parent-step progress. | Selecting the opaque parent step runs the child according to mapped child options. Nested selection is deferred. | An unusable child result throws a parent-step error that retains the child pipeline and failing step identity. | Framework-owned parent `dryRun` overrides the mapper, then the child applies its own per-step rules. | Medium. It uses existing pipeline and hook primitives without changing the external event model. |
84
-
85
- Shared raw hooks are rejected because they already corrupt the interactive reporter's single frame and cannot distinguish nested runs. Immediate flattening is rejected because it needs stable identity, selection, dependency, failure, and finalization rules that the current event and plan models don't provide. The opaque adapter addresses the existing composition case without deciding those broader contracts.
86
-
87
- ## Contract
88
-
89
- The parent DAG contains one ordinary step. That step maps its typed parent inputs and context into typed child options, validates the child plan, runs the child with an isolated internal hook bridge, and returns either the child result or a mapped parent result.
90
-
91
- The bridge owns child lifecycle events. Parent and custom hooks receive only the parent pipeline and opaque parent-step events. The bridge reports terminal child-step counts through the parent step's `reportProgress`, so existing reporters keep one live frame.
92
-
93
- ## API
94
-
95
- The parent-scoped step factory owns the adapter:
18
+ Use `fromPipeline` with `pipeline` and `mapOptions`. The mapper receives the
19
+ parent step's dependency outputs and context, and returns the inputs the child
20
+ needs. Add `mapResult` if the parent needs a different result shape.
96
21
 
97
22
  ```ts
98
23
  const seedIndexStage = step.fromPipeline("seed-index", {
@@ -107,11 +32,10 @@ const seedIndexStage = step.fromPipeline("seed-index", {
107
32
  });
108
33
  ```
109
34
 
110
- `StepFactory.fromPipeline` and `StepFactory.fromPipeline.skippable` infer the
111
- child result when `mapResult` is absent, and infer the mapped result when it
112
- is present.
35
+ ## Run a child for each item
113
36
 
114
- For runtime-selected sets (any domain — shards, files, jobs, catalog rows):
37
+ Use `items` to return the list to process, `key` for stable item IDs, and
38
+ `concurrency` to limit simultaneous child runs:
115
39
 
116
40
  ```ts
117
41
  const processShards = step.forEachPipeline("process-shards", {
@@ -129,8 +53,7 @@ const processShards = step.forEachPipeline("process-shards", {
129
53
  });
130
54
  ```
131
55
 
132
- When the whole fan-out may be intentionally unnecessary, opt into the widened
133
- output explicitly:
56
+ To skip the whole step when there are no items, use the skippable builder:
134
57
 
135
58
  ```ts
136
59
  const processShards = step.forEachPipeline.skippable("process-shards", {
@@ -144,101 +67,128 @@ const processShards = step.forEachPipeline.skippable("process-shards", {
144
67
  });
145
68
  ```
146
69
 
147
- Only `.skippable` produces `readonly ChildResult[] | undefined` (or
148
- `readonly TOut[] | undefined` with `mapResult`). A valued skip supplies the
149
- complete parent-facing array and bypasses `items`, child execution, and
150
- `mapResult`.
151
-
152
- The parent sees one opaque `process-shards` step. Progress is domain-neutral by
153
- default: a one-line item/concurrency summary plus structured `details` rows for
154
- each item and its selected child steps. Interactive reporters render these as
155
- an indented tree: parent step, item key, child steps, and any deeper composition.
156
- Pending, running, completed, failed, cancelled, and skipped rows retain their
157
- states; filtered steps are omitted. Completed item groups remain in the tree.
158
- `fromPipeline` provides the same child-step breakdown without an item-key level.
159
- Inner `reportProgress` counts and details survive both composition boundaries. The
160
- progress bar advances on terminal child steps so long fan-out work does not look
161
- hung at 0%. Override `progress.itemNoun` or `progress.formatMessage` for domain
162
- labels without changing scheduling. Duplicate keys fail before any child starts.
163
- Parent dry-run overrides the mapped child run object's `dryRun` value.
164
-
165
- Live fan-out snapshots show up to 32 item groups by default, prioritizing active
166
- items and then failures while keeping displayed groups in input order. Descendants
167
- stay with their item; an overflow row reports omitted groups. All groups are
168
- retained and the complete default tree is emitted once after the fan-out settles.
169
- `progress.detailLimit` overrides the live cap and also caps the final snapshot;
170
- larger values increase per-event presentation work. Progress payloads are not
171
- constructed when neither hooks nor tracing observe them. The standalone
172
- `mappedChildProgressDetails` helper continues to format only the active entries
173
- supplied in its snapshot, with no default cap.
174
-
175
- Progress `details` use preorder rows with `depth: 0` (or omitted) for direct
176
- children. Each composition level adds one to descendant depths. IDs are stable
177
- within their containing group; `name` provides optional display text. `completed`
178
- and `total` describe inner work independently of the parent's terminal-step count.
179
- Reporters cap visual indentation at 32 levels. Trace snapshots preserve these
180
- fields within 128-row, 4096-character-per-field, and 256 KiB encoded-payload
181
- bounds, with `detail_count` recording the pre-truncation row count. The byte
182
- budget includes UTF-8 and escaping inside the enclosing JSON event, leaving
183
- room for event metadata below the default 1 MiB NDJSON event limit.
184
-
185
- The interactive CLI keeps completed details after their parent settles. Trees
186
- larger than the terminal use a live window around active work, with omitted-row
187
- counts; the full retained tree prints at completion. Plain/non-TTY reporting
188
- continues to emit aggregate progress messages. Presentation does not change
189
- parent plans, child selection, hook isolation, scheduling, or result types.
190
-
191
- ## Semantics matrix
192
-
193
- | Concern | Contract |
194
- | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
195
- | Parent plan | The child is one opaque parent step. Its `nestedPipeline` metadata identifies the child pipeline, declared child step ids, and single/fan-out mode; child steps are not added to `PipelinePlan.steps` and runtime selection is not predicted. |
196
- | Option mapping | `mapOptions(inputs, parentContext)` returns the child's complete run object: domain fields plus any child-specific controls. The adapter applies parent `dryRun` last so framework semantics win. |
197
- | Other run controls | The adapter does not automatically propagate parent `stepIds`, `targets`, or `continueOnError`. `mapOptions` may choose child-specific values; child `targets` are limited to that child's declared public goals. Mapping parent selection would usually be invalid because parent and child ids occupy different namespaces. Mapping `continueOnError` lets independent child work finish but does not make a failed child usable by the parent. |
198
- | Runtime context | The adapter forwards `cwd`, `log`, `now`, `sleep`, and `signal` field by field. It does not forward parent hooks or the parent-only `reportProgress` field. |
199
- | Hook isolation | Parent and custom hooks see only the parent pipeline and opaque parent step. Raw child lifecycle events go only to an internal bridge. |
200
- | Progress | Single-child `fromPipeline` bridges terminal child steps into the opaque step (`completed` / plan step count, messages prefixed with `childPipelineId/childStepId`). Mapped `forEachPipeline` reports domain-neutral fan-out progress via `toMappedChildStepProgress`: `completed` counts terminal child steps across items, `total` is `items × stepsPerItem`, `message` is a one-line summary, and `details` contains a bounded live item tree and a complete default final tree. Presentation is optional (`progress.itemNoun` / `progress.formatMessage` / `progress.detailLimit`); neither path creates another live reporter frame. |
201
- | Result | The default output is the typed child result. `mapResult` may transform it into a domain-specific parent-step output. A child result is usable only when its status is `"completed"`; best-effort child results must be handled through an explicit ordinary step that calls `run()` and inspects the structured result. |
202
- | Failure | An unusable child result fails the opaque parent step. Its error names the child pipeline and the first failing child step and message, with `code: "TUBELESS_CHILD_FAILED"`, `phase: "execution"`, and `kind: "child"`; its JSON-safe cause chain retains the actionable child failure. When mapped children contain both cancellation and genuine failure, failure takes precedence; the aggregate is cancelled only when every unsuccessful child was cancelled. |
203
- | Cancellation | The child receives the exact parent `AbortSignal`. An already-aborted or later-aborted signal stops the child and transitions the parent step to `cancelled`. |
204
- | Dry run | The child receives `dryRun: parentContext.dryRun` and applies its own per-step policy. Child steps with `dryRun: "skip"` are skipped; ordinary child steps run normally. |
205
- | Selective execution | Selecting or targeting the opaque parent step runs the child according to its mapped controls. Parent selection is never forwarded into the child's ID namespace. |
206
- | Checkpoints | The adapter adds no checkpoint behavior. The child receives mapped options and the normal runtime context. |
207
-
208
- ## Guarantees
209
-
210
- Call sites infer required and optional dependency inputs, parent domain
211
- options, required child options, the default child result, and a transformed
212
- `mapResult` output without explicit generic arguments. Missing required child
213
- options fail at compile time.
214
-
215
- The parent plan contains only the opaque stage. The child receives the
216
- parent's working directory, logger, clock, sleep function, and abort signal.
217
- Parent hooks see no raw child lifecycle events. The internal bridge converts
218
- the child's canonical step statuses into monotonic progress on the opaque
219
- parent step.
220
-
221
- Parent dry-run precedence prevents a child side-effecting step from running.
222
- An unusable child result fails one opaque parent step and names the child
223
- pipeline, step, and error, including when `continueOnError` produced a
224
- best-effort final value. Later cancellation interrupts in-flight child work.
225
- An invalid child plan fails before the child starts.
226
-
227
- `fromPipeline` and `forEachPipeline` identity forms (no `mapResult`) use
228
- overloads so the parent-facing outputs stay the child result and child-result
229
- array, respectively. Their ordinary constructors remain non-optional;
230
- `.skippable` alone adds `undefined`.
231
-
232
- ## Out of scope
233
-
234
- Policy skip plus explicit `undefined` typing cover skip-without-run for opaque
235
- single and mapped child steps. Runner substitution and any richer skipped-output
236
- shape beyond policy skip are not supported.
237
-
238
- Hierarchical or versioned external events, flattened or namespaced child
239
- selection, and parallel DAG scheduling remain out of scope. Mapped children
240
- provide bounded parallelism inside one opaque parent step; they do not make
241
- the pipeline DAG executor parallel.
70
+ ## Results and type inference
71
+
72
+ Without `mapResult`, a `fromPipeline` step returns the child's final result.
73
+ With `mapResult`, it returns the mapped value. An asynchronous mapper is awaited
74
+ before the value becomes available to dependent steps. Its policy-skip value,
75
+ if supplied, must match that resolved result type.
76
+
77
+ `forEachPipeline` returns results in input order, even when child runs finish
78
+ in a different order. `concurrency` limits the number of child runs in flight.
79
+ Keys must be stable and unique; duplicate keys fail before any child starts.
80
+ All running children finish or cancel before the parent step returns a failure.
81
+ The parent therefore cannot finalize while those children are still running.
82
+
83
+ Use `.skippable` when the entire child step may be intentionally omitted.
84
+ Its `skip` callback returns `false` to proceed, a non-empty reason string to
85
+ skip, or `{ reason, value }` to skip with an output. A skip without a value
86
+ publishes `undefined`. Policy skips allow required dependents to run, so those
87
+ dependents must handle `undefined` explicitly.
88
+
89
+ The ordinary builders reject `skip` and keep non-optional output types.
90
+ `fromPipeline.skippable` returns `T | undefined`, and
91
+ `forEachPipeline.skippable` returns `readonly T[] | undefined`. For fan-out,
92
+ the skip value is the complete result array. Skipping does not call `items`,
93
+ run children, or apply `mapResult`.
94
+
95
+ ## Planning and execution controls
96
+
97
+ The parent plan contains one step for the child workflow. This is what
98
+ "opaque" means in the API: child steps are not added to `PipelinePlan.steps`.
99
+ The parent's `nestedPipeline` metadata still identifies the child pipeline,
100
+ its declared step IDs, and whether it runs once or for multiple items.
101
+
102
+ Selecting the parent step runs the child using the values returned by
103
+ `mapOptions`. Parent and child step IDs are separate. A parent target does not
104
+ select an identically named child step, and `parent.child-step` selection is
105
+ not supported.
106
+
107
+ | Control | Child behavior |
108
+ | ----------------- | --------------------------------------------------------------------------------------------- |
109
+ | `dryRun` | Always takes the parent's value, overriding any value in `mapOptions` |
110
+ | `stepIds` | Uses only a child-specific value supplied by `mapOptions` |
111
+ | `targets` | Uses only a child-specific value supplied by `mapOptions`; IDs must be declared child targets |
112
+ | `continueOnError` | Uses only a value supplied by `mapOptions` |
113
+
114
+ Child `mapOptions` returns domain inputs and any child-specific controls in
115
+ one object. The adapter separates them before invoking the child. An invalid
116
+ child plan fails the parent step before child execution begins.
117
+
118
+ In a dry run, each child step still follows its own policy. Mark child writes
119
+ with `dryRun: "skip"` or provide a side-effect-free preview handler. You can
120
+ also set a dry-run policy on the parent wrapper step to skip or preview the
121
+ whole child workflow.
122
+
123
+ Composition adds no checkpoints or crash recovery. Fan-out runs children
124
+ concurrently within one parent step; it does not make the parent graph execute
125
+ its steps in parallel.
126
+
127
+ ## Failures and cancellation
128
+
129
+ A child must finish with status `"completed"` to provide a successful parent
130
+ step output. `continueOnError` can let independent child work finish, but a
131
+ failed child still fails the parent step. Successful siblings in a failed
132
+ fan-out do not produce a partial parent-step output.
133
+
134
+ Child failures use `code: "TUBELESS_CHILD_FAILED"`, `phase: "execution"`, and
135
+ `kind: "child"`. The error identifies the child pipeline and first failing
136
+ step, and includes a JSON-safe cause chain. For multiple child runs, failure
137
+ takes precedence over cancellation: the parent step is cancelled only when
138
+ every unsuccessful child was cancelled.
139
+
140
+ Children receive the parent's exact `AbortSignal`. They must pass it to their
141
+ I/O and other cancellable work. The adapter waits for running children to
142
+ finish or cancel before returning.
143
+
144
+ If partial child results are valid for your application, use an ordinary step
145
+ that calls the child's `run()` and explicitly handles its report. Supply an
146
+ appropriate child context; do not forward the parent's raw hooks or progress
147
+ callback, which belong to the parent run's reporter.
148
+
149
+ ## Progress and hooks
150
+
151
+ The builders forward the parent's working directory, logger, clock, sleep
152
+ function, and abort signal. Parent hooks observe the parent step. Child hooks
153
+ are handled internally and converted into progress updates on that step.
154
+ This keeps parent and child reporting separate.
155
+
156
+ The interactive CLI displays `fromPipeline` child steps beneath the parent.
157
+ For `forEachPipeline`, it adds an item-key row above each child's steps.
158
+ Completed rows remain visible; failed, cancelled, and skipped rows keep their
159
+ own statuses. Filtered steps are omitted. Nested progress counts and details
160
+ are preserved, so a child can report progress inside a long-running step.
161
+
162
+ The parent progress count advances as child steps reach a terminal state.
163
+ For fan-out, `completed` counts finished child steps across items, and `total`
164
+ is the number of items multiplied by the number of child steps. Use
165
+ `progress.itemNoun` or `progress.formatMessage` to change labels and summaries
166
+ without changing execution.
167
+
168
+ ### Display and trace limits
169
+
170
+ Live fan-out updates show up to 32 item groups by default. Active items and
171
+ failures take priority, and displayed groups remain in input order. An overflow
172
+ row shows the number of omitted groups. All groups are retained, and a complete
173
+ tree is emitted when the fan-out finishes. Set `progress.detailLimit` to change
174
+ the live limit; this also limits the final snapshot.
175
+
176
+ The CLI shows a scrolling window when the tree is taller than the terminal and
177
+ prints the retained tree at completion. Non-interactive output uses summary
178
+ messages. Progress data is not built when neither hooks nor tracing observe it.
179
+ The standalone `mappedChildProgressDetails` helper formats the entries provided
180
+ to it and has no default item limit.
181
+
182
+ Custom renderers can read `details` as rows ordered parent before child.
183
+ `depth: 0` (or no depth) identifies a direct child; each nested level adds one.
184
+ IDs are stable within their group, and `name` is an optional label. Row
185
+ `completed` and `total` values describe work inside the child, independently
186
+ of the parent's count of finished steps. CLI indentation is capped at 32 levels.
187
+
188
+ Recorded trace snapshots have separate limits: 128 rows, 4096 characters per
189
+ field, and a 256 KiB encoded payload. `detailCount` records the row count before
190
+ truncation. The byte limit includes UTF-8 encoding and JSON escaping, leaving
191
+ space for event metadata within the default 1 MiB NDJSON event limit.
242
192
 
243
193
  ## Structured fan-out failures
244
194
 
@@ -263,6 +213,5 @@ unstarted work after cancellation. Reruns are ordinary new pipeline runs; the
263
213
  caller owns retry policy and side-effect safety. See the helper in
264
214
  [`fan-out-progress.ts`](../examples/fan-out-progress.ts).
265
215
 
266
- This additive diagnostic does not change parent failure/cancellation precedence,
267
- primary causes, scheduling, downstream dependency skips, or `continueOnError`.
268
- Successful siblings still do not produce a partial parent step output.
216
+ The diagnostics describe which items failed; they do not retry work or change
217
+ the parent step's failure behavior.