@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.
- package/CHANGELOG.md +441 -0
- package/LICENSE +201 -0
- package/README.md +60 -2
- package/dist/cli.js +162295 -0
- package/dist/skill/SKILL.md +366 -0
- package/dist/skill/examples/hello-world/dataset/examples/sample/expected/output.json +4 -0
- package/dist/skill/examples/hello-world/dataset/examples/sample/input/arguments.json +3 -0
- package/dist/skill/examples/hello-world/evaluators.yaml +5 -0
- package/dist/skill/examples/hello-world/workflow.yaml +28 -0
- package/dist/skill/reference/cli/agent.md +7 -0
- package/dist/skill/reference/cli/auth.md +75 -0
- package/dist/skill/reference/cli/completion.md +13 -0
- package/dist/skill/reference/cli/init.md +63 -0
- package/dist/skill/reference/cli/skill.md +79 -0
- package/dist/skill/reference/cli/status.md +14 -0
- package/dist/skill/reference/cli/workflow.md +788 -0
- package/dist/skill/reference/dataset-format.md +251 -0
- package/dist/skill/reference/debugging.md +280 -0
- package/dist/skill/reference/evaluators.md +210 -0
- package/dist/skill/reference/step-exec.md +164 -0
- package/dist/skill/reference/step-types.md +519 -0
- package/dist/skill/reference/workflow-yaml.md +304 -0
- package/dist/templates/blank/README.md +22 -0
- package/dist/templates/blank/dataset/examples/sample/expected/output.json +4 -0
- package/dist/templates/blank/dataset/examples/sample/input/arguments.json +3 -0
- package/dist/templates/blank/evaluators.yaml +9 -0
- package/dist/templates/blank/workflow.yaml +39 -0
- package/dist/templates/pdf-extraction/README.md +20 -0
- package/dist/templates/pdf-extraction/dataset/examples/sample-invoice/README.md +5 -0
- package/dist/templates/pdf-extraction/dataset/examples/sample-invoice/expected/output.json +6 -0
- package/dist/templates/pdf-extraction/dataset/examples/sample-invoice/input/arguments.json +1 -0
- package/dist/templates/pdf-extraction/evaluators.yaml +5 -0
- package/dist/templates/pdf-extraction/workflow.yaml +38 -0
- package/dist/templates/text-classification/README.md +8 -0
- package/dist/templates/text-classification/dataset/examples/negative/expected/output.json +3 -0
- package/dist/templates/text-classification/dataset/examples/negative/input/arguments.json +3 -0
- package/dist/templates/text-classification/dataset/examples/positive/expected/output.json +3 -0
- package/dist/templates/text-classification/dataset/examples/positive/input/arguments.json +3 -0
- package/dist/templates/text-classification/evaluators.yaml +6 -0
- package/dist/templates/text-classification/workflow.yaml +35 -0
- package/package.json +60 -15
- 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.
|