@eigenpal/cli 0.0.1 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/CHANGELOG.md +441 -0
  2. package/LICENSE +201 -0
  3. package/README.md +60 -2
  4. package/dist/cli.js +162295 -0
  5. package/dist/skill/SKILL.md +366 -0
  6. package/dist/skill/examples/hello-world/dataset/examples/sample/expected/output.json +4 -0
  7. package/dist/skill/examples/hello-world/dataset/examples/sample/input/arguments.json +3 -0
  8. package/dist/skill/examples/hello-world/evaluators.yaml +5 -0
  9. package/dist/skill/examples/hello-world/workflow.yaml +28 -0
  10. package/dist/skill/reference/cli/agent.md +7 -0
  11. package/dist/skill/reference/cli/auth.md +75 -0
  12. package/dist/skill/reference/cli/completion.md +13 -0
  13. package/dist/skill/reference/cli/init.md +63 -0
  14. package/dist/skill/reference/cli/skill.md +79 -0
  15. package/dist/skill/reference/cli/status.md +14 -0
  16. package/dist/skill/reference/cli/workflow.md +788 -0
  17. package/dist/skill/reference/dataset-format.md +251 -0
  18. package/dist/skill/reference/debugging.md +280 -0
  19. package/dist/skill/reference/evaluators.md +210 -0
  20. package/dist/skill/reference/step-exec.md +164 -0
  21. package/dist/skill/reference/step-types.md +519 -0
  22. package/dist/skill/reference/workflow-yaml.md +304 -0
  23. package/dist/templates/blank/README.md +22 -0
  24. package/dist/templates/blank/dataset/examples/sample/expected/output.json +4 -0
  25. package/dist/templates/blank/dataset/examples/sample/input/arguments.json +3 -0
  26. package/dist/templates/blank/evaluators.yaml +9 -0
  27. package/dist/templates/blank/workflow.yaml +39 -0
  28. package/dist/templates/pdf-extraction/README.md +20 -0
  29. package/dist/templates/pdf-extraction/dataset/examples/sample-invoice/README.md +5 -0
  30. package/dist/templates/pdf-extraction/dataset/examples/sample-invoice/expected/output.json +6 -0
  31. package/dist/templates/pdf-extraction/dataset/examples/sample-invoice/input/arguments.json +1 -0
  32. package/dist/templates/pdf-extraction/evaluators.yaml +5 -0
  33. package/dist/templates/pdf-extraction/workflow.yaml +38 -0
  34. package/dist/templates/text-classification/README.md +8 -0
  35. package/dist/templates/text-classification/dataset/examples/negative/expected/output.json +3 -0
  36. package/dist/templates/text-classification/dataset/examples/negative/input/arguments.json +3 -0
  37. package/dist/templates/text-classification/dataset/examples/positive/expected/output.json +3 -0
  38. package/dist/templates/text-classification/dataset/examples/positive/input/arguments.json +3 -0
  39. package/dist/templates/text-classification/evaluators.yaml +6 -0
  40. package/dist/templates/text-classification/workflow.yaml +35 -0
  41. package/package.json +60 -15
  42. package/index.js +0 -4
@@ -0,0 +1,251 @@
1
+ # Dataset folder format
2
+
3
+ A dataset is a folder of named example folders. The folder structure
4
+ itself is the manifest — there is no top-level `manifest.json`. The
5
+ import endpoint **rejects** archives that contain one (legacy format).
6
+
7
+ This is the format the CLI builds and ships to
8
+ `eigenpal workflow dataset push <wf-id> --file dataset/`. After it lands
9
+ on the server, the canonical per-row surface is
10
+ `eigenpal workflow dataset example {get,create,update,delete}` (and
11
+ `dataset list` for a table view) — the local folder is a pre-push
12
+ staging area, not a synced mirror.
13
+
14
+ ## Required layout
15
+
16
+ ```
17
+ dataset/
18
+ └── examples/
19
+ ├── invoice-foo/
20
+ │ ├── input/
21
+ │ │ ├── arguments.json REQUIRED — { language: "en" }
22
+ │ │ └── contract/ file-argument folder
23
+ │ │ ├── Contract_2026.pdf
24
+ │ │ └── Appendix.pdf
25
+ │ ├── expected/
26
+ │ │ ├── output.json OPTIONAL — raw ground truth JSON
27
+ │ │ └── generated_invoice/ OPTIONAL — expected file output
28
+ │ │ └── Invoice.docx
29
+ │ └── meta.json OPTIONAL — { rowOrder?, annotation?, overrides? }
30
+ │
31
+ └── invoice-bar/
32
+ └── input/
33
+ └── arguments.json { amount: 42 }
34
+ ```
35
+
36
+ The `expected/` subfolder mirrors `input/` exactly — `output.json` is
37
+ the scalar/object data ground truth, and any expected file outputs go
38
+ in `expected/<docKey>/<file>` folders (one folder per output path).
39
+
40
+ ## Rules (every one is enforced server-side at import)
41
+
42
+ - **Folder names** (example names, argument names, expected document
43
+ keys) must match `[a-z0-9][a-z0-9-_]*`.
44
+ - **`input/arguments.json`** is REQUIRED for every example, even if it's
45
+ just `{}`. It must be a JSON object (not array, not literal).
46
+ - **File-arguments** live in `input/<arg-name>/<filename>`. The
47
+ materialized shape on `triggerInput` is **driven by the workflow YAML's
48
+ `inputs[].type`**, not by the file count:
49
+
50
+ | workflow.yaml input | dataset folder | `triggerInput.<name>` |
51
+ | ------------------------------------------ | ------------------------- | --------------------- |
52
+ | `type: file` | `input/<name>/<one-file>` | `{ fileId }` |
53
+ | `type: array`, `items: { type: file }` | `input/<name>/<file>...` | `[{ fileId }, ...]` |
54
+
55
+ Reference single-file inputs as `{{ input.<name> }}` (resolves to the
56
+ `{ fileId }` object the worker reads). Reference array-of-file inputs
57
+ as `{{ input.<name>[0] }}` etc. Dropping multiple binaries into an
58
+ `input/<name>/` folder for an input declared `type: file` is rejected
59
+ at import with `code: single_file_input_overpopulated` — either trim
60
+ to one file or change the workflow input to the array form.
61
+ - **Argument-name collisions are rejected.** If `arguments.json` has
62
+ `{"contract": "..."}` AND `input/contract/file.pdf` exists, import
63
+ fails with `code: argument_name_collision`.
64
+ - **`expected/output.json`** is OPTIONAL. When present, it must be a
65
+ JSON object that mirrors your workflow's `output:` shape 1:1 — no
66
+ envelope, no wrapper. A scalar/array at the top level is rejected
67
+ with `code: invalid_expected_output`.
68
+
69
+ > **Note:** the importer is idempotent and accepts both bare objects and
70
+ > pre-wrapped `{ data: { ... } }` forms; bare is preferred.
71
+
72
+ ```json
73
+ // workflow.yaml
74
+ // output:
75
+ // invoiceNumber: '{{ steps.extract.output.invoiceNumber }}'
76
+ // totalAmount: '{{ steps.extract.output.totalAmount }}'
77
+ //
78
+ // → expected/output.json
79
+ {
80
+ "invoiceNumber": "INV-001",
81
+ "totalAmount": 1234.56
82
+ }
83
+ ```
84
+ - **Expected file outputs** live in `expected/<docKey>/<filename>` —
85
+ symmetric with `input/<argName>/<file>`. Each `<docKey>` folder is
86
+ uploaded and becomes an `expectedDocuments[docKey]` entry server-side
87
+ (single file ⇒ `{fileId}`, multiple ⇒ `[{fileId}, …]`). The judge
88
+ compares by file presence + content, not byte-equal.
89
+ - **`meta.json`** is OPTIONAL. Schema:
90
+ ```json
91
+ {
92
+ "rowOrder": 0,
93
+ "annotation": "free-form notes about this example",
94
+ "overrides": {
95
+ "steps": {
96
+ "<stepName>": { ...stepOutputObject }
97
+ }
98
+ }
99
+ }
100
+ ```
101
+ - `rowOrder` — sort order in the dashboard
102
+ - `annotation` — surfaced in the UI; useful for "why this case is
103
+ interesting" notes
104
+ - `overrides.steps.<stepName>` — short-circuits the named step,
105
+ returns the override as the step's output. Use for steps that
106
+ depend on external state (third-party APIs, private connectors) so the rest
107
+ of the example can still run.
108
+ - **Path-traversal patterns** (`..`, leading `/`, backslashes, null
109
+ bytes) reject the whole archive.
110
+
111
+ ## How the row materializes
112
+
113
+ For an example with:
114
+
115
+ - `arguments.json`: `{"language": "en"}`
116
+ - `input/contract/Contract_2026.pdf`
117
+ - `input/contract/Appendix.pdf`
118
+ - `expected/output.json`: `{"invoiceNumber": "INV-001"}`
119
+ - `expected/generated_invoice/Invoice.docx`
120
+
121
+ The server materializes:
122
+
123
+ ```json
124
+ // eval_examples.triggerInput
125
+ {
126
+ "language": "en",
127
+ "contract": [
128
+ { "fileId": "file_abc..." },
129
+ { "fileId": "file_def..." }
130
+ ]
131
+ }
132
+
133
+ // eval_examples.expectedOutput
134
+ {
135
+ "data": { "invoiceNumber": "INV-001" },
136
+ "expectedDocuments": {
137
+ "generated_invoice": { "fileId": "file_ghi..." }
138
+ }
139
+ }
140
+ ```
141
+
142
+ Each file is uploaded to S3 with a fresh `fileId`; the original
143
+ filename is preserved on the `files` row and shows up in the dashboard.
144
+ The `expectedOutput` envelope (`{ data?, expectedDocuments? }`) is
145
+ internal storage shape — your dataset folder stays clean (raw
146
+ `output.json` + per-docKey folders).
147
+
148
+ ## Validate before pushing
149
+
150
+ ```bash
151
+ eigenpal workflow validate dataset ./dataset
152
+ ```
153
+
154
+ Reports per-example issues:
155
+
156
+ ```
157
+ ✗ dataset (./dataset) — 2 issues
158
+ examples/Invoice-Foo Folder name must be lowercase kebab/snake-case.
159
+ examples/foo/input/contract Argument-name collision: "contract" appears in both arguments.json and as an input/ folder.
160
+ ```
161
+
162
+ ## Push
163
+
164
+ ```bash
165
+ # Replace = wipes existing examples for the workflow, uploads the folder fresh.
166
+ eigenpal workflow dataset push <wf-id> --file ./dataset --mode replace
167
+
168
+ # Append = adds to whatever's already on the server.
169
+ eigenpal workflow dataset push <wf-id> --file ./dataset --mode append
170
+ ```
171
+
172
+ The endpoint streams progress as NDJSON. The terminal `done` event
173
+ includes `{ created, expectedSet, … }`:
174
+
175
+ - `created` — examples successfully persisted
176
+ - `expectedSet` — how many of those carried an `expected/output.json`.
177
+ If `expectedSet < created`, your zip didn't include the `expected/`
178
+ folders — the dashboard's "Expected" tab will be blank and evals
179
+ will only run un-graded.
180
+
181
+ ## Editing a dataset on the server
182
+
183
+ To inspect or round-trip what's currently on the server:
184
+
185
+ ```bash
186
+ eigenpal workflow dataset list <wf-id>
187
+ eigenpal workflow dataset pull <wf-id> --out current.zip
188
+ # (or omit --out to stream the zip to stdout for piping)
189
+ ```
190
+
191
+ For bulk changes — many examples at once, schema-level rework, file
192
+ swaps — edit the local folder and re-push with
193
+ `dataset push --mode replace`.
194
+
195
+ For one-row tweaks — capturing a corrected output as the new GT,
196
+ deleting one bad example, adding one example to a known dataset — use
197
+ the per-example CRUD commands instead. They avoid the round-trip of
198
+ re-zipping and re-uploading the whole folder.
199
+
200
+ ### Inspect one example end-to-end
201
+
202
+ ```bash
203
+ eigenpal workflow dataset example get <wf-id> <example-id>
204
+ eigenpal workflow dataset example get <wf-id> <example-id> --json | jq '.expectedOutput'
205
+ ```
206
+
207
+ Pretty mode prints labelled `Inputs` / `Expected` / `Metadata` sections;
208
+ `--json` returns the full row (`triggerInput`, `expectedOutput`,
209
+ `annotation`, `rowOrder`, timestamps) for piping.
210
+
211
+ ### Capture a corrected output as the new ground truth
212
+
213
+ ```bash
214
+ # 1. Look at what the workflow actually returned for the example
215
+ eigenpal workflow execution get exec_… --json | jq '.output.data' > /tmp/correct.json
216
+
217
+ # 2. Patch the example's expected output in place. PATCH semantics:
218
+ # every flag you omit is left alone.
219
+ eigenpal workflow dataset example update <wf-id> <example-id> \
220
+ --expected-file /tmp/correct.json
221
+ ```
222
+
223
+ `--expected-file -` reads JSON from stdin if you'd rather pipe. Pass
224
+ empty string to clear an annotation: `--annotation ""`.
225
+
226
+ ### Add one new example
227
+
228
+ ```bash
229
+ eigenpal workflow dataset example create <wf-id> \
230
+ --name missing-required-field \
231
+ --input-json '{"language":"en"}' \
232
+ --expected-file /tmp/expected.json \
233
+ --annotation "edge case: required field absent from input"
234
+ ```
235
+
236
+ `--input-json` / `--expected-json` accept inline JSON literals; the
237
+ `-file` variants read from disk (or `-` for stdin). They're mutually
238
+ exclusive — pass one or the other, not both.
239
+
240
+ File-arg uploads still go through `dataset push`; CRUD only handles
241
+ scalar args + `expected/output.json`-style outputs.
242
+
243
+ ### Delete one bad example
244
+
245
+ ```bash
246
+ eigenpal workflow dataset example delete <wf-id> <example-id> --yes
247
+ ```
248
+
249
+ `--yes` is required for non-TTY shells (CI). Interactive shells may
250
+ omit it; single-row deletes have a small blast radius compared to
251
+ `dataset push --mode replace`, so no confirmation prompt fires.
@@ -0,0 +1,280 @@
1
+ # Debugging a failing workflow
2
+
3
+ Three tools cover most cases:
4
+
5
+ ```bash
6
+ eigenpal workflow execution get <exec-id> # full payload, step-by-step
7
+ eigenpal workflow execution watch <exec-id> # live status; 2s during transition / 5s steady
8
+ eigenpal workflow execution compare <a> <b> # side-by-side diff of two runs
9
+ ```
10
+
11
+ ## 1. Reproduce the failure
12
+
13
+ ```bash
14
+ eigenpal workflow execution run <workflow-name> <example-name>
15
+ ```
16
+
17
+ Streams a live step list as the run progresses. Bails to the prompt
18
+ the moment a step transitions to `failed`.
19
+
20
+ If the workflow is server-stable and the failure is on a real
21
+ historical execution, skip the rerun and pull the recorded execution
22
+ directly:
23
+
24
+ ```bash
25
+ eigenpal workflow execution list <wf-id> --status failed --limit 5
26
+ eigenpal workflow execution get exec_…
27
+ ```
28
+
29
+ ## 2. Pull the full execution
30
+
31
+ ```bash
32
+ eigenpal workflow execution get <exec-id>
33
+ ```
34
+
35
+ The payload includes:
36
+
37
+ - `status` — `pending` | `running` | `completed` | `failed` | `cancelled` | `skipped`
38
+ - `output` — the workflow's final return value (or partial when failed)
39
+ - `triggerInput` — the input the workflow was called with
40
+ - `stepExecutions` — one entry per step with:
41
+ - `stepName` — handle from `workflow.yaml`
42
+ - `status` — same set as the top-level. `skipped` means the step's
43
+ `if:` evaluated false, OR a `control.foreach` was passed an empty
44
+ array. Check `skippedReason` for the human-readable cause.
45
+ - `skippedReason` — populated when `status: 'skipped'`
46
+ - `input` — fully resolved values (templates already expanded)
47
+ - `resolvedConfig` — the step's `with:` block AFTER `{{template}}`
48
+ substitution. Lets you see what the LLM actually got, what URL
49
+ HTTP actually hit, what items the script actually saw. Populated
50
+ for `ai.*` / `transform.*` / `action.*`. Null for `control.*`
51
+ (orchestration steps don't have a meaningful resolved config).
52
+ - `output` — what the step returned
53
+ - `error` — error message + code when `status: 'failed'`
54
+ - `durationMs`
55
+ - `overrideMode` — `'skipped'` when the example's `meta.json`
56
+ overrode this step; null otherwise
57
+
58
+ Quick triage with `jq`:
59
+
60
+ ```bash
61
+ eigenpal workflow execution get <exec-id> \
62
+ | jq '.stepExecutions[] | { step: .stepName, status, error }'
63
+ ```
64
+
65
+ Narrow to a single step:
66
+
67
+ ```bash
68
+ eigenpal workflow execution get <exec-id> --step extract --include input,output,error
69
+ ```
70
+
71
+ Drill into one field:
72
+
73
+ ```bash
74
+ eigenpal workflow execution get <exec-id> --json | jq '.stepExecutions[2].output.totalAmount'
75
+ ```
76
+
77
+ ## 3. Common failure modes
78
+
79
+ ### `validation_failed` on a step input
80
+
81
+ A step received an input that didn't match its `inputSchema`. Usually
82
+ upstream returned a different shape than expected. Inspect the upstream
83
+ step's `output` and adjust the template expression or schema.
84
+
85
+ ```bash
86
+ # Check the step type's expected input shape
87
+ eigenpal workflow step-type get ai.extract | jq '.configSchema'
88
+ ```
89
+
90
+ ### `template_resolution_failed`
91
+
92
+ A `{{ ... }}` expression referenced a path that didn't exist
93
+ (`steps.foo.output.bar` when `steps.foo.output` is `null`). Either
94
+ guard with `| default:` or fix the upstream step.
95
+
96
+ ### `script_timeout` / `script_memory_limit`
97
+
98
+ `transform.script` tripped its sandbox limits (5 s wall clock, 10 MB
99
+ heap). Optimize the code or split into smaller steps. The sandbox has
100
+ no network or filesystem, so most timeouts are infinite-loop bugs.
101
+
102
+ ### `step_type_unknown`
103
+
104
+ `workflow.yaml` references a `type:` that the deployment doesn't know
105
+ about. Common after a typo or a step type that was renamed.
106
+
107
+ ```bash
108
+ eigenpal workflow step-type list --search <partial-match>
109
+ ```
110
+
111
+ ### Eval flakiness with `llm-judge`
112
+
113
+ LLM-judge scores have variance. Stabilize: pin a specific model id
114
+ (`gpt-4o-2024-08-06`), set `temperature: 0`, narrow the prompt to one
115
+ dimension per judge, run multiple experiments and average.
116
+
117
+ ### Workflow `Validation failed` on push
118
+
119
+ The error envelope tells you exactly where:
120
+
121
+ ```
122
+ ✗ Validation failed
123
+ steps.2.config.passThreshold Required, got undefined
124
+ steps.2.type Invalid step type: 'eval.llm-judg'
125
+
126
+ ℹ Failing step is type `eval.llm-judge`. Run
127
+ `eigenpal workflow step-type get eval.llm-judge` to inspect its
128
+ config schema.
129
+ ```
130
+
131
+ Follow the hint — every field path is dotted into the YAML so you can
132
+ jump straight to the broken line.
133
+
134
+ ## 4. Compare two executions
135
+
136
+ ```bash
137
+ eigenpal workflow execution compare <exec-a> <exec-b>
138
+ ```
139
+
140
+ Output: per-step status / Δ duration / output diff. Useful for spotting
141
+ regressions between revisions of a workflow.
142
+
143
+ For a freer-form diff, dump both and compare with `jq`:
144
+
145
+ ```bash
146
+ eigenpal workflow execution get <exec-a> > a.json
147
+ eigenpal workflow execution get <exec-b> > b.json
148
+ diff <(jq -S . a.json) <(jq -S . b.json)
149
+ ```
150
+
151
+ ## 5. Cancel a long-running execution
152
+
153
+ ```bash
154
+ eigenpal workflow execution cancel <exec-id> # TTY: silent
155
+ eigenpal workflow execution cancel <exec-id> --yes # CI / pipes
156
+ eigenpal workflow execution cancel <exec-id> --json # raw server payload
157
+ ```
158
+
159
+ The worker finishes the current step, then stops — the cancellation
160
+ check happens between every step transition, including inside
161
+ `control.foreach` / `control.parallel` / `control.block` bodies.
162
+ Behavior by current state:
163
+
164
+ - `created` / `pending` — transitions straight to `cancelled`.
165
+ - `running` / `waiting` — stamps `cancellationRequestedAt`; the worker
166
+ observes it on the next step boundary and exits cleanly.
167
+ - already terminal (`completed` / `failed` / `cancelled`) — no-op,
168
+ exits 0 with an info line. Safe to retry.
169
+
170
+ ## 6. Compare two experiment batches
171
+
172
+ ```bash
173
+ eigenpal workflow experiment compare <batch-a> <batch-b>
174
+ eigenpal workflow experiment compare <batch-a> <batch-b> --regression-threshold 0.10
175
+ eigenpal workflow experiment compare <batch-a> <batch-b> --json | jq '.summary.regressions'
176
+ ```
177
+
178
+ Side-by-side score diff per `(example, evaluator)` pair. Highlights
179
+ regressions vs improvements; aggregate stats at the bottom.
180
+
181
+ - `--sort abs-delta-desc` (default — biggest movers first) | `delta-asc`
182
+ (regressions first) | `delta-desc` (improvements first) | `name`
183
+ - `--regression-threshold 0.05` (default) — `Δ < -threshold` is a
184
+ regression with `⚠`.
185
+
186
+ **No `--workflow-id` needed.** The server resolves the owning workflow
187
+ from each batch id. Both batches must belong to the same workflow within
188
+ your tenant. (Sibling `experiment` subcommands still take `<wf-id>` as
189
+ the first positional — only `compare` is workflow-agnostic.)
190
+
191
+ ## 7. Connection vs auth errors
192
+
193
+ `eigenpal status` reports them separately so you can tell which is
194
+ broken. If you see:
195
+
196
+ ```
197
+ ✗ Could not connect to https://… — invalid or unreachable base URL.
198
+ Set EIGENPAL_BASE_URL or pass --base-url <url>.
199
+ ```
200
+
201
+ That's a **connection** error — the URL is wrong, the server is down,
202
+ or your network is blocked. The URL the CLI tried is echoed back so a
203
+ stale `EIGENPAL_BASE_URL` is immediately obvious.
204
+
205
+ If instead you see:
206
+
207
+ ```
208
+ ✗ Not authenticated.
209
+ Run `eigenpal auth login`, or set EIGENPAL_API_KEY in your env.
210
+ ```
211
+
212
+ That's an **auth** error — the URL works but your key is missing /
213
+ expired / scoped to the wrong tenant. Run `auth list` to see configured
214
+ profiles, or `auth use <name>` to switch.
215
+
216
+ ## 8. Override a flaky step in eval mode
217
+
218
+ If a step calls an external system that's unreliable (an external API, a
219
+ private API, a rate-limited connector), short-circuit it for evals via
220
+ the example's `meta.json`:
221
+
222
+ ```json
223
+ {
224
+ "overrides": {
225
+ "steps": {
226
+ "<stepName>": { ...stepOutputObject }
227
+ }
228
+ }
229
+ }
230
+ ```
231
+
232
+ When the workflow runs against this example, the executor returns the
233
+ override as the step's output without running the real step. The
234
+ execution row shows `overrideMode: 'skipped'` so you can audit which
235
+ steps were faked. Re-import the dataset (`workflow dataset push
236
+ --mode replace`) for the change to land.
237
+
238
+ ## 9. Watching a long batch
239
+
240
+ ```bash
241
+ # Kicks off, prints { batchId, executionIds }
242
+ eigenpal workflow experiment run <wf-id>
243
+
244
+ # --watch polls until every execution is terminal, prints a per-example
245
+ # failure summary, and exits non-zero if any failed/cancelled.
246
+ eigenpal workflow experiment status <wf-id> --batch-id <id> --watch
247
+
248
+ # Without --watch: one-shot snapshot, no polling. Prefer --watch in CI / agents.
249
+ eigenpal workflow experiment status <wf-id> --batch-id <id>
250
+ ```
251
+
252
+ The terminal `--watch` output looks like:
253
+
254
+ ```
255
+ 7/7 terminal (failed=7)
256
+
257
+ ✗ 7/7 did not pass
258
+
259
+ invoice-2025-001 [failed] template_resolution_failed at step `parse`: …
260
+ eigenpal workflow execution get exec_…
261
+ …
262
+
263
+ ℹ step-level errors live on `stepExecutions[].error` — see reference/debugging.md.
264
+ ```
265
+
266
+ ## 10. Drilling into a single failed execution
267
+
268
+ The summary surfaces the top-level execution error. For step-by-step
269
+ detail (which step failed, what input it received, what output it
270
+ returned before erroring) follow the suggested command:
271
+
272
+ ```bash
273
+ eigenpal workflow execution get exec_… # full payload
274
+ eigenpal workflow execution get exec_… --step <name> --include input,output,error
275
+ eigenpal workflow execution get exec_… --json | jq -r '.stepExecutions[0].error'
276
+ ```
277
+
278
+ For per-execution live view, `eigenpal workflow execution watch <exec-id>` does
279
+ adaptive polling (2 s while transitioning, 5 s when steady) and prints
280
+ ASCII status badges that work in any terminal.