@eigenpal/cli 0.4.7 → 0.4.9
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 +10 -0
- package/dist/cli.js +229 -57
- package/dist/skill/SKILL.md +70 -6
- package/dist/skill/reference/cli/workflow.md +43 -17
- package/dist/skill/reference/step-types.md +0 -1
- package/dist/skill/reference/workflow-yaml.md +1 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -141,6 +141,16 @@
|
|
|
141
141
|
|
|
142
142
|
Non-overlapping examples are listed in dedicated `only in A` / `only in B` blocks before the table so |Δ| sort stays meaningful.
|
|
143
143
|
|
|
144
|
+
- 9dc84b9: CLI: agent-friendly experiment polling, watching, and comparison.
|
|
145
|
+
|
|
146
|
+
Four small additions so agents (and humans writing monitoring scripts) don't have to roll their own bash pollers, JSON parsers, or aggregators on top of `experiment status`.
|
|
147
|
+
- **`experiment status --json` now includes a top-level `summary` rollup** — `{ total, terminal, complete, completedCount, failedCount, cancelledCount, rejectedCount, runningCount, pendingCount }`. Polling scripts can read `.summary.complete` / `.summary.failedCount` directly instead of folding `executions[].status` themselves. `executions[]` stays as-is for backwards compat.
|
|
148
|
+
- **`experiment status --short`** — single-line, awk-friendly stdout summary (e.g. `6/6 done failed=0 cancelled=0 rejected=0`). Mutually exclusive with `--watch`. Drops the failure detail block + closing tip so monitoring loops stay clean.
|
|
149
|
+
- **New `experiment watch <workflow-id> <batchId>`** — polls until terminal, then auto-pulls eval results to `./results-<batchId>.<format>` in one command. Replaces the old "monitor + status + pull + score" recipe. Flags: `--interval`, `--max-wait`, `--format csv|json`, `--pull-on-complete <path>` to override destination, `--no-pull` to opt out. Exit codes match `experiment status --watch` (0 clean, 1 any failure, 2 deadline).
|
|
150
|
+
- **`experiment compare` adds a per-evaluator aggregate** — a "Per-evaluator deltas" table renders above the existing per-row table with rows, mean Δ, regressions, and improvements per evaluator (sorted by |meanΔ| desc). `--json` exposes it at `summary.byEvaluator[]`.
|
|
151
|
+
|
|
152
|
+
Updated SKILL.md so the canonical "run an experiment + collect results" recipe uses `experiment watch` and adds a "monitoring scripts" section that calls out `--short` and `summary.complete`.
|
|
153
|
+
|
|
144
154
|
- 3da0612: CLI: pretty default rendering for `list`, `get`, and mutating commands; `--json` toggle for piping.
|
|
145
155
|
- `workflow definition/dataset/experiment/version/step-type list` and `workflow execution list` render aligned ASCII tables. `--json` restores the legacy raw payload.
|
|
146
156
|
- `workflow execution get` (no other flags) prints the vertical step list via `renderFrame` instead of dumping 1000+ lines of JSON. `--json` for the full payload; `--output-path` / `--step` / `--include` continue to print filtered JSON.
|
package/dist/cli.js
CHANGED
|
@@ -21310,7 +21310,6 @@ var init_extract = __esm(() => {
|
|
|
21310
21310
|
prompt: exports_external.string().optional().describe("Custom prompt template for extraction"),
|
|
21311
21311
|
provider: exports_external.string().optional().describe('Provider ID to use (e.g., "openai-gpt4o", "anthropic-claude")'),
|
|
21312
21312
|
model: exports_external.string().optional().describe("Model to use (overrides provider default)"),
|
|
21313
|
-
temperature: exports_external.number().min(0).max(2).default(0).describe("Model temperature (0 = deterministic)"),
|
|
21314
21313
|
maxInputTokens: exports_external.number().int().optional().describe("Max input tokens. Truncates input text and logs a warning when exceeded. Omit for no limit.")
|
|
21315
21314
|
});
|
|
21316
21315
|
});
|
|
@@ -21844,6 +21843,9 @@ var init_execution_row = __esm(() => {
|
|
|
21844
21843
|
];
|
|
21845
21844
|
});
|
|
21846
21845
|
|
|
21846
|
+
// ../types/src/llm-determinism.ts
|
|
21847
|
+
var DETERMINISTIC_TEMPERATURE = 0;
|
|
21848
|
+
|
|
21847
21849
|
// ../types/src/workflow/steps.ts
|
|
21848
21850
|
function parseStepType(type) {
|
|
21849
21851
|
const [category, operation] = type.split(".");
|
|
@@ -22110,7 +22112,6 @@ var init_step_configs = __esm(() => {
|
|
|
22110
22112
|
prompt: exports_external.string().optional().describe("Custom prompt template for extraction"),
|
|
22111
22113
|
provider: exports_external.string().optional().describe('Provider ID (e.g., "openai-gpt4o")'),
|
|
22112
22114
|
model: exports_external.string().optional().describe("Model override"),
|
|
22113
|
-
temperature: exports_external.number().min(0).max(2).default(0).optional().describe("Model temperature (0 = deterministic)"),
|
|
22114
22115
|
maxInputTokens: exports_external.number().int().optional().describe("Max input tokens. Truncates input text and logs a warning when exceeded. Omit for no limit.")
|
|
22115
22116
|
});
|
|
22116
22117
|
AiExtractOutputSchema = exports_external.record(exports_external.string(), exports_external.unknown()).describe("Extracted structured data matching the provided schema");
|
|
@@ -22695,7 +22696,8 @@ var init_workflow = __esm(() => {
|
|
|
22695
22696
|
inputs: exports_external.array(WorkflowInputDefSchema).optional(),
|
|
22696
22697
|
steps: exports_external.array(StepSchema),
|
|
22697
22698
|
output: exports_external.record(exports_external.string(), exports_external.string()).optional(),
|
|
22698
|
-
settings: WorkflowSettingsSchema.optional()
|
|
22699
|
+
settings: WorkflowSettingsSchema.optional(),
|
|
22700
|
+
defaultModel: exports_external.string().optional()
|
|
22699
22701
|
});
|
|
22700
22702
|
StoredWorkflowDefinitionSchema = exports_external.object({
|
|
22701
22703
|
id: exports_external.string(),
|
|
@@ -23463,8 +23465,7 @@ async function judgeScoreJson(params) {
|
|
|
23463
23465
|
const response = await client.extract(userContent, {
|
|
23464
23466
|
schema: SCORE_SCHEMA,
|
|
23465
23467
|
prompt: systemPrompt,
|
|
23466
|
-
model
|
|
23467
|
-
temperature: 0
|
|
23468
|
+
model
|
|
23468
23469
|
});
|
|
23469
23470
|
const rawScore = response.data.score;
|
|
23470
23471
|
const score = typeof rawScore === "number" ? rawScore : typeof rawScore === "string" ? parseFloat(rawScore) : NaN;
|
|
@@ -23484,8 +23485,7 @@ async function judgeLabelJson(params) {
|
|
|
23484
23485
|
const response = await client.extract(userContent, {
|
|
23485
23486
|
schema: buildLabelSchema(labels),
|
|
23486
23487
|
prompt: systemPrompt,
|
|
23487
|
-
model
|
|
23488
|
-
temperature: 0
|
|
23488
|
+
model
|
|
23489
23489
|
});
|
|
23490
23490
|
const label = typeof response.data.label === "string" ? response.data.label.trim() : "";
|
|
23491
23491
|
if (!labels.includes(label)) {
|
|
@@ -23870,6 +23870,7 @@ __export(exports_src, {
|
|
|
23870
23870
|
DatasetImportRequestSchema: () => DatasetImportRequestSchema,
|
|
23871
23871
|
DatasetImportModeSchema: () => DatasetImportModeSchema,
|
|
23872
23872
|
DURABLE_EXECUTION_TYPES: () => DURABLE_EXECUTION_TYPES,
|
|
23873
|
+
DETERMINISTIC_TEMPERATURE: () => DETERMINISTIC_TEMPERATURE,
|
|
23873
23874
|
DEFAULT_PAGE_SIZE: () => DEFAULT_PAGE_SIZE,
|
|
23874
23875
|
DEFAULT_CONCURRENCY: () => DEFAULT_CONCURRENCY,
|
|
23875
23876
|
DATASET_NAME_PATTERN: () => DATASET_NAME_PATTERN,
|
|
@@ -143405,7 +143406,7 @@ import { fileURLToPath as fileURLToPath6 } from "url";
|
|
|
143405
143406
|
// package.json
|
|
143406
143407
|
var package_default = {
|
|
143407
143408
|
name: "@eigenpal/cli",
|
|
143408
|
-
version: "0.4.
|
|
143409
|
+
version: "0.4.9",
|
|
143409
143410
|
description: "Eigenpal CLI — author and run document workflows from your terminal. Agent-ready.",
|
|
143410
143411
|
keywords: [
|
|
143411
143412
|
"eigenpal",
|
|
@@ -147595,10 +147596,47 @@ function buildBatchDiff(args) {
|
|
|
147595
147596
|
sharedExamples: sharedExampleSet.size,
|
|
147596
147597
|
onlyInA,
|
|
147597
147598
|
onlyInB,
|
|
147598
|
-
regressionThreshold
|
|
147599
|
+
regressionThreshold,
|
|
147600
|
+
byEvaluator: aggregateByEvaluator(rows)
|
|
147599
147601
|
}
|
|
147600
147602
|
};
|
|
147601
147603
|
}
|
|
147604
|
+
function aggregateByEvaluator(rows) {
|
|
147605
|
+
const buckets = new Map;
|
|
147606
|
+
for (const r5 of rows) {
|
|
147607
|
+
const list = buckets.get(r5.evaluator);
|
|
147608
|
+
if (list)
|
|
147609
|
+
list.push(r5);
|
|
147610
|
+
else
|
|
147611
|
+
buckets.set(r5.evaluator, [r5]);
|
|
147612
|
+
}
|
|
147613
|
+
const aggregates = [];
|
|
147614
|
+
for (const [evaluator, group2] of buckets) {
|
|
147615
|
+
const comparable = group2.filter((r5) => r5.delta != null);
|
|
147616
|
+
const meanDelta = comparable.length === 0 ? null : comparable.reduce((sum, r5) => sum + r5.delta, 0) / comparable.length;
|
|
147617
|
+
aggregates.push({
|
|
147618
|
+
evaluator,
|
|
147619
|
+
comparable: comparable.length,
|
|
147620
|
+
meanDelta,
|
|
147621
|
+
regressions: group2.filter((r5) => r5.status === "regression").length,
|
|
147622
|
+
improvements: group2.filter((r5) => r5.status === "improvement").length,
|
|
147623
|
+
unchanged: group2.filter((r5) => r5.status === "unchanged").length
|
|
147624
|
+
});
|
|
147625
|
+
}
|
|
147626
|
+
aggregates.sort((a5, b8) => {
|
|
147627
|
+
if (a5.meanDelta == null && b8.meanDelta == null)
|
|
147628
|
+
return a5.evaluator.localeCompare(b8.evaluator);
|
|
147629
|
+
if (a5.meanDelta == null)
|
|
147630
|
+
return 1;
|
|
147631
|
+
if (b8.meanDelta == null)
|
|
147632
|
+
return -1;
|
|
147633
|
+
const diff = Math.abs(b8.meanDelta) - Math.abs(a5.meanDelta);
|
|
147634
|
+
if (diff !== 0)
|
|
147635
|
+
return diff;
|
|
147636
|
+
return a5.evaluator.localeCompare(b8.evaluator);
|
|
147637
|
+
});
|
|
147638
|
+
return aggregates;
|
|
147639
|
+
}
|
|
147602
147640
|
function sortDiffRows(rows, sort) {
|
|
147603
147641
|
const tiebreak = (a5, b8) => {
|
|
147604
147642
|
const exCmp = a5.example.localeCompare(b8.example);
|
|
@@ -147684,10 +147722,30 @@ function renderBatchDiffHuman(diff) {
|
|
|
147684
147722
|
}
|
|
147685
147723
|
process.stderr.write(`
|
|
147686
147724
|
`);
|
|
147725
|
+
if (diff.summary.byEvaluator.length > 0) {
|
|
147726
|
+
process.stderr.write(`${ui.bold("Per-evaluator deltas")} ${ui.dim("(B − A):")}
|
|
147727
|
+
`);
|
|
147728
|
+
console.log(table(diff.summary.byEvaluator, [
|
|
147729
|
+
{ key: "evaluator", header: "evaluator" },
|
|
147730
|
+
{ key: "comparable", header: "rows", align: "right" },
|
|
147731
|
+
{
|
|
147732
|
+
key: "meanDelta",
|
|
147733
|
+
header: "mean Δ",
|
|
147734
|
+
align: "right",
|
|
147735
|
+
format: (v11) => formatDelta(v11)
|
|
147736
|
+
},
|
|
147737
|
+
{ key: "regressions", header: "regressions", align: "right" },
|
|
147738
|
+
{ key: "improvements", header: "improvements", align: "right" }
|
|
147739
|
+
]));
|
|
147740
|
+
process.stderr.write(`
|
|
147741
|
+
`);
|
|
147742
|
+
}
|
|
147687
147743
|
if (diff.rows.length === 0) {
|
|
147688
147744
|
process.stderr.write(`${ui.dim("No shared (example, evaluator) pairs to compare.")}
|
|
147689
147745
|
`);
|
|
147690
147746
|
} else {
|
|
147747
|
+
process.stderr.write(`${ui.bold("Per-row deltas:")}
|
|
147748
|
+
`);
|
|
147691
147749
|
console.log(table(diff.rows, [
|
|
147692
147750
|
{ key: "example", header: "example" },
|
|
147693
147751
|
{ key: "evaluator", header: "evaluator" },
|
|
@@ -148444,7 +148502,7 @@ returns immediately with \`{ batchId, total }\`; poll
|
|
|
148444
148502
|
return;
|
|
148445
148503
|
}
|
|
148446
148504
|
success2(`Started experiment ${ui.bold(start.batchId)} ${ui.dim(`(${start.total} execution${start.total === 1 ? "" : "s"} queued)`)}`);
|
|
148447
|
-
process.stderr.write(` ${ui.dim(`Watch: eigenpal workflow experiment
|
|
148505
|
+
process.stderr.write(` ${ui.dim(`Watch + auto-pull: eigenpal workflow experiment watch ${workflowId} ${start.batchId}`)}
|
|
148448
148506
|
`);
|
|
148449
148507
|
return;
|
|
148450
148508
|
}
|
|
@@ -148461,20 +148519,32 @@ returns immediately with \`{ batchId, total }\`; poll
|
|
|
148461
148519
|
await new Promise((r5) => setTimeout(r5, intervalMs));
|
|
148462
148520
|
}
|
|
148463
148521
|
}));
|
|
148464
|
-
const statusCmd = experiment.command("status <workflow-id> <batchId>").description(descriptionFor("workflow_get_experiment_status")).option("--watch", "Poll until every execution reaches a terminal state (completed/failed/cancelled/rejected), then exit", false).option("--interval <seconds>", "Poll interval in seconds when --watch is set (default 5)", intArg, 5).option("--max-wait <seconds>", "Hard ceiling for --watch in seconds (default 1800 = 30 min)", intArg, 30 * 60).option("--include <kinds>", "Comma-separated extras to attach when --watch terminates: payload (full per-execution snapshot, can be hundreds of KB)", "");
|
|
148522
|
+
const statusCmd = experiment.command("status <workflow-id> <batchId>").description(descriptionFor("workflow_get_experiment_status")).option("--watch", "Poll until every execution reaches a terminal state (completed/failed/cancelled/rejected), then exit", false).option("--short", "Single-line plain-text summary on stdout (e.g. `6/6 done failed=0 cancelled=0 rejected=0`). Pipe-friendly for monitoring loops; mutually exclusive with --watch.", false).option("--interval <seconds>", "Poll interval in seconds when --watch is set (default 5)", intArg, 5).option("--max-wait <seconds>", "Hard ceiling for --watch in seconds (default 1800 = 30 min)", intArg, 30 * 60).option("--include <kinds>", "Comma-separated extras to attach when --watch terminates: payload (full per-execution snapshot, can be hundreds of KB)", "");
|
|
148465
148523
|
addJsonFlag(withBaseUrl(statusCmd)).action(action(async (workflow2, batchId, opts) => {
|
|
148524
|
+
if (opts.short && opts.watch) {
|
|
148525
|
+
error51("--short and --watch are mutually exclusive (short is for spot-check polling).");
|
|
148526
|
+
process.exit(2);
|
|
148527
|
+
}
|
|
148466
148528
|
const includes = new Set((opts.include ?? "").split(",").map((s5) => s5.trim()).filter(Boolean));
|
|
148467
148529
|
const includePayload = includes.has("payload");
|
|
148468
148530
|
const { client, workflowId } = await buildClientForWorkflow(workflow2, opts);
|
|
148469
148531
|
const url3 = `/api/v1/workflows/${workflowId}/experiments/${batchId}`;
|
|
148470
148532
|
if (!opts.watch) {
|
|
148471
148533
|
const result = await client.get(url3);
|
|
148534
|
+
const execs = result.executions ?? [];
|
|
148535
|
+
const summary = summarizeExperimentExecutions(execs);
|
|
148472
148536
|
if (opts.json) {
|
|
148473
|
-
printJson(result);
|
|
148537
|
+
printJson({ ...result, summary: rollupForJson(summary) });
|
|
148538
|
+
return;
|
|
148539
|
+
}
|
|
148540
|
+
if (opts.short) {
|
|
148541
|
+
console.log(formatShortStatus(summary));
|
|
148542
|
+
const failed2 = summary.counts.failed ?? 0;
|
|
148543
|
+
const cancelled2 = summary.counts.cancelled ?? 0;
|
|
148544
|
+
if (summary.done && failed2 + cancelled2 > 0)
|
|
148545
|
+
process.exit(1);
|
|
148474
148546
|
return;
|
|
148475
148547
|
}
|
|
148476
|
-
const execs = result.executions ?? [];
|
|
148477
|
-
const summary = summarizeExperimentExecutions(execs);
|
|
148478
148548
|
const counts = Object.entries(summary.counts).map(([k9, v11]) => `${k9}=${v11}`).sort().join(" ");
|
|
148479
148549
|
console.log(`${ui.bold(batchId)} ${summary.terminal}/${summary.total} terminal ${ui.dim(`(${counts || "no executions"})`)}`);
|
|
148480
148550
|
const failed = summary.counts.failed ?? 0;
|
|
@@ -148488,56 +148558,25 @@ returns immediately with \`{ batchId, total }\`; poll
|
|
|
148488
148558
|
process.exit(1);
|
|
148489
148559
|
return;
|
|
148490
148560
|
}
|
|
148491
|
-
const
|
|
148492
|
-
|
|
148493
|
-
|
|
148494
|
-
|
|
148495
|
-
|
|
148496
|
-
|
|
148497
|
-
|
|
148498
|
-
const line3 = `${summary.terminal}/${summary.total} terminal ${ui.dim(`(${counts || "no executions"})`)}`;
|
|
148499
|
-
if (line3 !== lastSummary) {
|
|
148500
|
-
if (process.stderr.isTTY) {
|
|
148501
|
-
process.stderr.write(`\r\x1B[K${line3}`);
|
|
148502
|
-
} else {
|
|
148503
|
-
process.stderr.write(`${line3}
|
|
148504
|
-
`);
|
|
148505
|
-
}
|
|
148506
|
-
lastSummary = line3;
|
|
148507
|
-
}
|
|
148508
|
-
const wantPayload = opts.json || includePayload;
|
|
148509
|
-
if (summary.done) {
|
|
148510
|
-
if (process.stderr.isTTY)
|
|
148511
|
-
process.stderr.write(`
|
|
148512
|
-
`);
|
|
148513
|
-
const failed = summary.counts.failed ?? 0;
|
|
148514
|
-
const cancelled = summary.counts.cancelled ?? 0;
|
|
148515
|
-
const rejected = summary.counts.rejected ?? 0;
|
|
148516
|
-
const badCount = failed + cancelled + rejected;
|
|
148517
|
-
if (badCount > 0) {
|
|
148518
|
-
renderExperimentFailures(execs, summary.total);
|
|
148519
|
-
}
|
|
148561
|
+
const wantPayload = opts.json || includePayload;
|
|
148562
|
+
await pollExperimentUntilTerminal({
|
|
148563
|
+
client,
|
|
148564
|
+
url: url3,
|
|
148565
|
+
interval: opts.interval,
|
|
148566
|
+
maxWait: opts.maxWait,
|
|
148567
|
+
onTerminal: (result, summary) => {
|
|
148520
148568
|
if (wantPayload) {
|
|
148521
|
-
printJson(result);
|
|
148569
|
+
printJson({ ...result, summary: rollupForJson(summary) });
|
|
148522
148570
|
} else {
|
|
148523
148571
|
process.stderr.write(`${ui.dim(`use --include payload (or --json) for the full per-execution snapshot`)}
|
|
148524
148572
|
`);
|
|
148525
148573
|
}
|
|
148526
|
-
|
|
148527
|
-
|
|
148528
|
-
return;
|
|
148529
|
-
}
|
|
148530
|
-
if (Date.now() >= deadline) {
|
|
148531
|
-
if (process.stderr.isTTY)
|
|
148532
|
-
process.stderr.write(`
|
|
148533
|
-
`);
|
|
148534
|
-
error51(`Timed out after ${opts.maxWait}s with ${summary.total - summary.terminal} non-terminal execution(s). Re-run to keep watching.`);
|
|
148574
|
+
},
|
|
148575
|
+
onDeadline: (result, summary) => {
|
|
148535
148576
|
if (wantPayload)
|
|
148536
|
-
printJson(result);
|
|
148537
|
-
process.exit(2);
|
|
148577
|
+
printJson({ ...result, summary: rollupForJson(summary) });
|
|
148538
148578
|
}
|
|
148539
|
-
|
|
148540
|
-
}
|
|
148579
|
+
});
|
|
148541
148580
|
}));
|
|
148542
148581
|
const cancelCmd = experiment.command("cancel <workflow-id> <batchId>").description("Cancel every execution in a batch. Idempotent.").option("--yes", "Required for non-TTY shells (CI, pipes). Acts immediately, no prompt.").addHelpText("after", `
|
|
148543
148582
|
Examples:
|
|
@@ -148626,6 +148665,62 @@ batches must belong to the same workflow within the caller's tenant.
|
|
|
148626
148665
|
}
|
|
148627
148666
|
renderBatchDiffHuman(diff);
|
|
148628
148667
|
}));
|
|
148668
|
+
const watchCmd = experiment.command("watch <workflow-id> <batchId>").description("Poll until terminal, then auto-pull results — replaces `status --watch` + `results --out`.").option("--interval <seconds>", "Poll interval in seconds (default 5)", intArg, 5).option("--max-wait <seconds>", "Hard ceiling in seconds (default 1800 = 30 min)", intArg, 30 * 60).option("--pull-on-complete <path>", "Destination for the results file. Default: ./results-<batchId>.<format>").option("--format <csv|json>", "Results export format (default json)", parseExportFormat, "json").option("--no-pull", "Skip auto-pulling results on terminal (just watch)").addHelpText("after", `
|
|
148669
|
+
Examples:
|
|
148670
|
+
$ eigenpal workflow experiment watch wf_abc123 evb_xyz789
|
|
148671
|
+
$ eigenpal workflow experiment watch wf_abc123 evb_xyz789 --format csv
|
|
148672
|
+
$ eigenpal workflow experiment watch wf_abc123 evb_xyz789 --pull-on-complete ./out/r.json
|
|
148673
|
+
$ eigenpal workflow experiment watch wf_abc123 evb_xyz789 --no-pull
|
|
148674
|
+
|
|
148675
|
+
Behavior:
|
|
148676
|
+
- Polls every --interval seconds; redraws a one-line tick on a TTY,
|
|
148677
|
+
appends a new line per tick when piped.
|
|
148678
|
+
- On terminal: renders the failed-execution detail block (if any), then
|
|
148679
|
+
downloads results to ./results-<batchId>.<format> unless --no-pull
|
|
148680
|
+
or --pull-on-complete <path> says otherwise.
|
|
148681
|
+
- 4xx errors (e.g. unknown batch id) abort immediately rather than spin.
|
|
148682
|
+
- Pre-flight check: the destination directory must exist before the
|
|
148683
|
+
poll loop starts, so a typo doesn't waste a 30-min wait.
|
|
148684
|
+
- Exit codes (matching \`experiment status --watch\`):
|
|
148685
|
+
0 every execution succeeded (or pull skipped via --no-pull on a clean batch)
|
|
148686
|
+
1 at least one execution ended in failed/cancelled/rejected,
|
|
148687
|
+
OR the export download failed after a clean batch
|
|
148688
|
+
2 --max-wait deadline reached — re-run to keep watching;
|
|
148689
|
+
OR misuse (e.g. --no-pull combined with --pull-on-complete)
|
|
148690
|
+
`);
|
|
148691
|
+
withBaseUrl(watchCmd).action(action(async (workflow2, batchId, opts) => {
|
|
148692
|
+
if (!opts.pull && opts.pullOnComplete) {
|
|
148693
|
+
error51("--no-pull and --pull-on-complete are mutually exclusive — pass one or the other.");
|
|
148694
|
+
process.exit(2);
|
|
148695
|
+
}
|
|
148696
|
+
const { client, workflowId } = await buildClientForWorkflow(workflow2, opts);
|
|
148697
|
+
const url3 = `/api/v1/workflows/${workflowId}/experiments/${batchId}`;
|
|
148698
|
+
const dest = opts.pull ? opts.pullOnComplete ?? `./results-${batchId}.${opts.format}` : null;
|
|
148699
|
+
if (dest) {
|
|
148700
|
+
const dir = path15.dirname(dest);
|
|
148701
|
+
if (!existsSync9(dir)) {
|
|
148702
|
+
error51(`Destination directory does not exist: ${dir}. Create it first or pass a path under an existing directory.`);
|
|
148703
|
+
process.exit(2);
|
|
148704
|
+
}
|
|
148705
|
+
}
|
|
148706
|
+
await pollExperimentUntilTerminal({
|
|
148707
|
+
client,
|
|
148708
|
+
url: url3,
|
|
148709
|
+
interval: opts.interval,
|
|
148710
|
+
maxWait: opts.maxWait,
|
|
148711
|
+
onTerminal: async () => {
|
|
148712
|
+
if (!dest)
|
|
148713
|
+
return;
|
|
148714
|
+
const params = new URLSearchParams({ format: opts.format, batchId });
|
|
148715
|
+
try {
|
|
148716
|
+
await downloadStreamToFile(client, `/api/v1/workflows/${workflowId}/eval-results/export?${params.toString()}`, dest);
|
|
148717
|
+
} catch (err2) {
|
|
148718
|
+
error51(`Watch finished but pulling results failed: ${err2 instanceof Error ? err2.message : String(err2)}. Re-run \`experiment results <wf> <batch>\` to retry.`);
|
|
148719
|
+
process.exit(1);
|
|
148720
|
+
}
|
|
148721
|
+
}
|
|
148722
|
+
});
|
|
148723
|
+
}));
|
|
148629
148724
|
}
|
|
148630
148725
|
function renderExperimentFailures(execs, totalCount) {
|
|
148631
148726
|
const failedExecs = execs.filter((e8) => e8.status === "failed" || e8.status === "cancelled" || e8.status === "rejected");
|
|
@@ -148664,6 +148759,83 @@ function summarizeExperimentExecutions(execs) {
|
|
|
148664
148759
|
done: execs.length > 0 && terminal === execs.length
|
|
148665
148760
|
};
|
|
148666
148761
|
}
|
|
148762
|
+
function rollupForJson(summary) {
|
|
148763
|
+
return {
|
|
148764
|
+
total: summary.total,
|
|
148765
|
+
terminal: summary.terminal,
|
|
148766
|
+
complete: summary.done,
|
|
148767
|
+
completedCount: summary.counts.completed ?? 0,
|
|
148768
|
+
failedCount: summary.counts.failed ?? 0,
|
|
148769
|
+
cancelledCount: summary.counts.cancelled ?? 0,
|
|
148770
|
+
rejectedCount: summary.counts.rejected ?? 0,
|
|
148771
|
+
runningCount: summary.counts.running ?? 0,
|
|
148772
|
+
pendingCount: summary.counts.pending ?? 0
|
|
148773
|
+
};
|
|
148774
|
+
}
|
|
148775
|
+
function formatShortStatus(summary) {
|
|
148776
|
+
const state = summary.done ? "done" : "in-progress";
|
|
148777
|
+
return [
|
|
148778
|
+
`${summary.terminal}/${summary.total}`,
|
|
148779
|
+
state,
|
|
148780
|
+
`failed=${summary.counts.failed ?? 0}`,
|
|
148781
|
+
`cancelled=${summary.counts.cancelled ?? 0}`,
|
|
148782
|
+
`rejected=${summary.counts.rejected ?? 0}`
|
|
148783
|
+
].join(" ");
|
|
148784
|
+
}
|
|
148785
|
+
function renderTickLine(summary) {
|
|
148786
|
+
const counts = Object.entries(summary.counts).map(([k9, v11]) => `${k9}=${v11}`).sort().join(" ");
|
|
148787
|
+
return `${summary.terminal}/${summary.total} terminal ${ui.dim(`(${counts || "no executions"})`)}`;
|
|
148788
|
+
}
|
|
148789
|
+
async function pollExperimentUntilTerminal({
|
|
148790
|
+
client,
|
|
148791
|
+
url: url3,
|
|
148792
|
+
interval,
|
|
148793
|
+
maxWait,
|
|
148794
|
+
onTerminal,
|
|
148795
|
+
onDeadline
|
|
148796
|
+
}) {
|
|
148797
|
+
const deadline = Date.now() + maxWait * 1000;
|
|
148798
|
+
let lastSummary = "";
|
|
148799
|
+
while (true) {
|
|
148800
|
+
const result = await client.get(url3);
|
|
148801
|
+
const execs = result.executions ?? [];
|
|
148802
|
+
const summary = summarizeExperimentExecutions(execs);
|
|
148803
|
+
const line3 = renderTickLine(summary);
|
|
148804
|
+
if (line3 !== lastSummary) {
|
|
148805
|
+
if (process.stderr.isTTY) {
|
|
148806
|
+
process.stderr.write(`\r\x1B[K${line3}`);
|
|
148807
|
+
} else {
|
|
148808
|
+
process.stderr.write(`${line3}
|
|
148809
|
+
`);
|
|
148810
|
+
}
|
|
148811
|
+
lastSummary = line3;
|
|
148812
|
+
}
|
|
148813
|
+
if (summary.done) {
|
|
148814
|
+
if (process.stderr.isTTY)
|
|
148815
|
+
process.stderr.write(`
|
|
148816
|
+
`);
|
|
148817
|
+
const failed = summary.counts.failed ?? 0;
|
|
148818
|
+
const cancelled = summary.counts.cancelled ?? 0;
|
|
148819
|
+
const rejected = summary.counts.rejected ?? 0;
|
|
148820
|
+
const badCount = failed + cancelled + rejected;
|
|
148821
|
+
if (badCount > 0)
|
|
148822
|
+
renderExperimentFailures(execs, summary.total);
|
|
148823
|
+
await onTerminal(result, summary);
|
|
148824
|
+
if (badCount > 0)
|
|
148825
|
+
process.exit(1);
|
|
148826
|
+
return;
|
|
148827
|
+
}
|
|
148828
|
+
if (Date.now() >= deadline) {
|
|
148829
|
+
if (process.stderr.isTTY)
|
|
148830
|
+
process.stderr.write(`
|
|
148831
|
+
`);
|
|
148832
|
+
error51(`Timed out after ${maxWait}s with ${summary.total - summary.terminal} non-terminal execution(s). Re-run to keep watching.`);
|
|
148833
|
+
onDeadline?.(result, summary);
|
|
148834
|
+
process.exit(2);
|
|
148835
|
+
}
|
|
148836
|
+
await new Promise((r5) => setTimeout(r5, interval * 1000));
|
|
148837
|
+
}
|
|
148838
|
+
}
|
|
148667
148839
|
function registerVersionsCommands(parent) {
|
|
148668
148840
|
const versions2 = parent.command("versions").description("Inspect and restore historical workflow versions.");
|
|
148669
148841
|
const listCmd = versions2.command("list <workflow-id>").description(descriptionFor("workflow_list_versions"));
|
package/dist/skill/SKILL.md
CHANGED
|
@@ -145,6 +145,17 @@ to retry.
|
|
|
145
145
|
eigenpal workflow experiment compare evb_old evb_new --regression-threshold 0.05
|
|
146
146
|
```
|
|
147
147
|
|
|
148
|
+
Output is two stacked tables: a per-evaluator aggregate (rows per evaluator
|
|
149
|
+
with mean Δ, regressions, improvements — sorted biggest mover first) followed
|
|
150
|
+
by a per-(example, evaluator) detail table. No need to write a Python
|
|
151
|
+
aggregator on top.
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
# JSON exposes both layers — `summary.byEvaluator` for the rollup,
|
|
155
|
+
# `rows` for the full per-example detail.
|
|
156
|
+
eigenpal workflow experiment compare evb_old evb_new --json | jq '.summary.byEvaluator'
|
|
157
|
+
```
|
|
158
|
+
|
|
148
159
|
Unlike its siblings, `compare` takes no `<workflow-id>` — the server resolves
|
|
149
160
|
the owning workflow from each batch id. Both batches must live in the
|
|
150
161
|
same workflow within your tenant. See [`reference/debugging.md`](reference/debugging.md#6-compare-two-experiment-batches)
|
|
@@ -218,12 +229,64 @@ eigenpal workflow experiment run <workflow-id> # → { bat
|
|
|
218
229
|
|
|
219
230
|
# Or restrict to one example:
|
|
220
231
|
eigenpal workflow experiment run <workflow-id> --example-id evx_…
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
**Recommended:** use `experiment watch` — it polls until terminal AND auto-pulls
|
|
235
|
+
results to disk in one command. Replaces the old "run → status (poll) → results"
|
|
236
|
+
chain so agents don't have to write a custom poller, parse `executions[].status`,
|
|
237
|
+
or chain `monitor + status + pull + score` shell logic.
|
|
238
|
+
|
|
239
|
+
```bash
|
|
240
|
+
# One command: watch until terminal, then write ./results-<batchId>.json on completion.
|
|
241
|
+
eigenpal workflow experiment watch <workflow-id> <batch-id>
|
|
242
|
+
|
|
243
|
+
# CSV output:
|
|
244
|
+
eigenpal workflow experiment watch <workflow-id> <batch-id> --format csv
|
|
245
|
+
|
|
246
|
+
# Custom destination (overrides the default ./results-<batchId>.<format>):
|
|
247
|
+
eigenpal workflow experiment watch <workflow-id> <batch-id> --pull-on-complete ./out/r.json
|
|
248
|
+
|
|
249
|
+
# Just watch, skip the pull (e.g. you only need the live tick + exit code):
|
|
250
|
+
eigenpal workflow experiment watch <workflow-id> <batch-id> --no-pull
|
|
251
|
+
```
|
|
221
252
|
|
|
222
|
-
|
|
223
|
-
|
|
253
|
+
Exit codes match `experiment status --watch`: `0` clean, `1` any
|
|
254
|
+
failed/cancelled/rejected execution, `2` `--max-wait` deadline reached
|
|
255
|
+
(30 min default — re-run to keep watching).
|
|
224
256
|
|
|
225
|
-
|
|
226
|
-
|
|
257
|
+
### Monitoring scripts — use `--short` and the JSON rollup
|
|
258
|
+
|
|
259
|
+
When a script needs to spot-check a batch (no waiting), prefer one of the two
|
|
260
|
+
machine-readable paths instead of folding `executions[].status` yourself.
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
# Single line on stdout, awk-friendly:
|
|
264
|
+
eigenpal workflow experiment status <wf> <batch> --short
|
|
265
|
+
# → 6/6 done failed=0 cancelled=0 rejected=0 (or "in-progress" while pending)
|
|
266
|
+
|
|
267
|
+
# Parse with awk:
|
|
268
|
+
DONE_STATE=$(eigenpal workflow experiment status <wf> <batch> --short | awk '{print $2}')
|
|
269
|
+
[ "$DONE_STATE" = "done" ] || exit 0 # not terminal yet, try again later
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
```bash
|
|
273
|
+
# JSON includes a top-level `summary` rollup so you don't count statuses by hand:
|
|
274
|
+
eigenpal workflow experiment status <wf> <batch> --json | jq '.summary'
|
|
275
|
+
# → { total: 6, terminal: 6, complete: true,
|
|
276
|
+
# completedCount: 5, failedCount: 1,
|
|
277
|
+
# cancelledCount: 0, rejectedCount: 0,
|
|
278
|
+
# runningCount: 0, pendingCount: 0 }
|
|
279
|
+
|
|
280
|
+
# `summary.complete` is the canonical poll-completion flag.
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
`--short` and `--json` are mutually exclusive with `--watch` — for live
|
|
284
|
+
streaming use `experiment watch` instead.
|
|
285
|
+
|
|
286
|
+
If you need the raw eval-results export after watching with `--no-pull`, or
|
|
287
|
+
to refetch results outside a watch loop, fall back to:
|
|
288
|
+
|
|
289
|
+
```bash
|
|
227
290
|
eigenpal workflow experiment results <workflow-id> <batch-id> --format csv --out r.csv
|
|
228
291
|
```
|
|
229
292
|
|
|
@@ -433,10 +496,11 @@ eigenpal workflow clear-local # delete local execution artifacts
|
|
|
433
496
|
|
|
434
497
|
# Experiments (batch eval runs)
|
|
435
498
|
eigenpal workflow experiment run <workflow-id> [--example-id <id> ...]
|
|
436
|
-
eigenpal workflow experiment status <workflow-id> <batch-id> [--watch] # exit 1 on terminal w/ failures, 2 on --max-wait
|
|
499
|
+
eigenpal workflow experiment status <workflow-id> <batch-id> [--watch] [--short] [--json] # exit 1 on terminal w/ failures, 2 on --max-wait. --json includes top-level `summary` rollup.
|
|
500
|
+
eigenpal workflow experiment watch <workflow-id> <batch-id> [--format csv|json] [--pull-on-complete <path>] [--no-pull] # poll + auto-pull results in one shot
|
|
437
501
|
eigenpal workflow experiment results <workflow-id> [batch-id] --format {csv|json} --out r.csv
|
|
438
502
|
eigenpal workflow experiment list <workflow-id>
|
|
439
|
-
eigenpal workflow experiment compare <batch-a> <batch-b> [--regression-threshold] [--sort] # no --workflow-id
|
|
503
|
+
eigenpal workflow experiment compare <batch-a> <batch-b> [--regression-threshold] [--sort] # no --workflow-id; prints per-evaluator aggregate + per-row table
|
|
440
504
|
|
|
441
505
|
# Step-type / evaluator-type introspection
|
|
442
506
|
eigenpal workflow step-type list
|
|
@@ -38,6 +38,7 @@ Manage workflows: push, pull, run, evaluate.
|
|
|
38
38
|
- [`eigenpal workflow experiment cancel [options] <workflow-id> <batchId>`](#eigenpal-workflow-experiment-cancel-options-workflow-id-batchid)
|
|
39
39
|
- [`eigenpal workflow experiment results [options] <workflow-id> [batchId]`](#eigenpal-workflow-experiment-results-options-workflow-id-batchid)
|
|
40
40
|
- [`eigenpal workflow experiment compare [options] <batchIdA> <batchIdB>`](#eigenpal-workflow-experiment-compare-options-batchida-batchidb)
|
|
41
|
+
- [`eigenpal workflow experiment watch [options] <workflow-id> <batchId>`](#eigenpal-workflow-experiment-watch-options-workflow-id-batchid)
|
|
41
42
|
- [`eigenpal workflow execution run [options] <workflow-id> [examples...]`](#eigenpal-workflow-execution-run-options-workflow-id-examples)
|
|
42
43
|
- [`eigenpal workflow execution get [options] <executionId>`](#eigenpal-workflow-execution-get-options-executionid)
|
|
43
44
|
- [`eigenpal workflow execution list [options] <workflow-id>`](#eigenpal-workflow-execution-list-options-workflow-id)
|
|
@@ -79,7 +80,8 @@ workflow
|
|
|
79
80
|
│ ├── status <workflow-id> <batchId>
|
|
80
81
|
│ ├── cancel <workflow-id> <batchId>
|
|
81
82
|
│ ├── results <workflow-id> [batchId]
|
|
82
|
-
│
|
|
83
|
+
│ ├── compare <batchIdA> <batchIdB>
|
|
84
|
+
│ └── watch <workflow-id> <batchId>
|
|
83
85
|
├── execution
|
|
84
86
|
│ ├── run <workflow-id> [examples...]
|
|
85
87
|
│ ├── get <executionId>
|
|
@@ -137,14 +139,15 @@ workflow
|
|
|
137
139
|
|
|
138
140
|
### Experiment
|
|
139
141
|
|
|
140
|
-
| Command | Description
|
|
141
|
-
| ------------------------------------------------------------------------ |
|
|
142
|
-
| `eigenpal workflow experiment list [options] <workflow-id>` | List executions for the workflow, newest first.
|
|
143
|
-
| `eigenpal workflow experiment run [options] <workflow-id>` | Start a batch eval against the workflow's dataset.
|
|
144
|
-
| `eigenpal workflow experiment status [options] <workflow-id> <batchId>` | Aggregate progress for a batch by `batchId`.
|
|
145
|
-
| `eigenpal workflow experiment cancel [options] <workflow-id> <batchId>` | Cancel every execution in a batch. Idempotent.
|
|
146
|
-
| `eigenpal workflow experiment results [options] <workflow-id> [batchId]` | Download eval results in CSV or JSON.
|
|
147
|
-
| `eigenpal workflow experiment compare [options] <batchIdA> <batchIdB>` | Diff eval scores between two experiment batches.
|
|
142
|
+
| Command | Description |
|
|
143
|
+
| ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------ |
|
|
144
|
+
| `eigenpal workflow experiment list [options] <workflow-id>` | List executions for the workflow, newest first. |
|
|
145
|
+
| `eigenpal workflow experiment run [options] <workflow-id>` | Start a batch eval against the workflow's dataset. |
|
|
146
|
+
| `eigenpal workflow experiment status [options] <workflow-id> <batchId>` | Aggregate progress for a batch by `batchId`. |
|
|
147
|
+
| `eigenpal workflow experiment cancel [options] <workflow-id> <batchId>` | Cancel every execution in a batch. Idempotent. |
|
|
148
|
+
| `eigenpal workflow experiment results [options] <workflow-id> [batchId]` | Download eval results in CSV or JSON. |
|
|
149
|
+
| `eigenpal workflow experiment compare [options] <batchIdA> <batchIdB>` | Diff eval scores between two experiment batches. |
|
|
150
|
+
| `eigenpal workflow experiment watch [options] <workflow-id> <batchId>` | Poll until terminal, then auto-pull results — replaces `status --watch` + `results --out`. |
|
|
148
151
|
|
|
149
152
|
### Execution
|
|
150
153
|
|
|
@@ -508,14 +511,15 @@ Aggregate progress for a batch by `batchId`.
|
|
|
508
511
|
|
|
509
512
|
### Options
|
|
510
513
|
|
|
511
|
-
| Flag | Required | Default | Description
|
|
512
|
-
| ---------------------- | -------- | ------- |
|
|
513
|
-
| `--watch` | no | `false` | Poll until every execution reaches a terminal state (completed/failed/cancelled/rejected), then exit
|
|
514
|
-
| `--
|
|
515
|
-
| `--
|
|
516
|
-
| `--
|
|
517
|
-
| `--
|
|
518
|
-
| `--
|
|
514
|
+
| Flag | Required | Default | Description |
|
|
515
|
+
| ---------------------- | -------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
516
|
+
| `--watch` | no | `false` | Poll until every execution reaches a terminal state (completed/failed/cancelled/rejected), then exit |
|
|
517
|
+
| `--short` | no | `false` | Single-line plain-text summary on stdout (e.g. `6/6 done failed=0 cancelled=0 rejected=0`). Pipe-friendly for monitoring loops; mutually exclusive with --watch. |
|
|
518
|
+
| `--interval <seconds>` | no | `5` | Poll interval in seconds when --watch is set (default 5) |
|
|
519
|
+
| `--max-wait <seconds>` | no | `1800` | Hard ceiling for --watch in seconds (default 1800 = 30 min) |
|
|
520
|
+
| `--include <kinds>` | no | `""` | Comma-separated extras to attach when --watch terminates: payload (full per-execution snapshot, can be hundreds of KB) |
|
|
521
|
+
| `--base-url <url>` | no | | Server base URL |
|
|
522
|
+
| `--json` | no | | Output the raw server response as JSON |
|
|
519
523
|
|
|
520
524
|
### `eigenpal workflow experiment cancel [options] <workflow-id> <batchId>`
|
|
521
525
|
|
|
@@ -575,6 +579,28 @@ Diff eval scores between two experiment batches.
|
|
|
575
579
|
| `--base-url <url>` | no | | Server base URL |
|
|
576
580
|
| `--json` | no | | Output the raw server response as JSON |
|
|
577
581
|
|
|
582
|
+
### `eigenpal workflow experiment watch [options] <workflow-id> <batchId>`
|
|
583
|
+
|
|
584
|
+
Poll until terminal, then auto-pull results — replaces `status --watch` + `results --out`.
|
|
585
|
+
|
|
586
|
+
### Arguments
|
|
587
|
+
|
|
588
|
+
| Name | Required | Variadic | Description |
|
|
589
|
+
| ------------- | -------- | -------- | ----------- |
|
|
590
|
+
| `workflow-id` | yes | no | |
|
|
591
|
+
| `batchId` | yes | no | |
|
|
592
|
+
|
|
593
|
+
### Options
|
|
594
|
+
|
|
595
|
+
| Flag | Required | Default | Description |
|
|
596
|
+
| --------------------------- | -------- | -------- | ----------------------------------------------------------------------- |
|
|
597
|
+
| `--interval <seconds>` | no | `5` | Poll interval in seconds (default 5) |
|
|
598
|
+
| `--max-wait <seconds>` | no | `1800` | Hard ceiling in seconds (default 1800 = 30 min) |
|
|
599
|
+
| `--pull-on-complete <path>` | no | | Destination for the results file. Default: ./results-<batchId>.<format> |
|
|
600
|
+
| `--format <csv\|json>` | no | `"json"` | Results export format (default json) |
|
|
601
|
+
| `--no-pull` | no | | Skip auto-pulling results on terminal (just watch) |
|
|
602
|
+
| `--base-url <url>` | no | | Server base URL |
|
|
603
|
+
|
|
578
604
|
### `eigenpal workflow execution run [options] <workflow-id> [examples...]`
|
|
579
605
|
|
|
580
606
|
Run a saved workflow against local dataset examples.
|
|
@@ -153,7 +153,6 @@ Extract structured data from text using AI with a JSON schema
|
|
|
153
153
|
| `prompt` | string | no | | Custom prompt template for extraction |
|
|
154
154
|
| `provider` | string | no | | Provider ID (e.g., "openai-gpt4o") |
|
|
155
155
|
| `model` | string | no | | Model override |
|
|
156
|
-
| `temperature` | number | no | `0` | Model temperature (0 = deterministic) |
|
|
157
156
|
| `maxInputTokens` | integer | no | | Max input tokens. Truncates input text and logs a warning when exceeded. Omit for no limit. |
|
|
158
157
|
|
|
159
158
|
**Output:** `record<string, unknown>`
|
|
@@ -387,6 +387,7 @@ _Generated from `WorkflowDefinitionSchema` in `@eigenpal/types/src/workflow/work
|
|
|
387
387
|
| `steps` | array<unknown> | yes | | |
|
|
388
388
|
| `output` | record<string, string> | no | | |
|
|
389
389
|
| `settings` | object | no | | |
|
|
390
|
+
| `defaultModel` | string | no | | |
|
|
390
391
|
|
|
391
392
|
|
|
392
393
|
## Per-input fields
|