@prismatic-io/lux 0.0.1 → 0.0.2-preview.4

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 (142) hide show
  1. package/lib/answerers/terminal/index.js +1 -1
  2. package/lib/answerers/terminal/index.js.map +1 -1
  3. package/lib/assertions/authoring.d.ts +14 -0
  4. package/lib/assertions/authoring.d.ts.map +1 -0
  5. package/lib/assertions/authoring.js +3 -0
  6. package/lib/assertions/authoring.js.map +1 -0
  7. package/lib/assertions/core/helpers.d.ts +2 -7
  8. package/lib/assertions/core/helpers.d.ts.map +1 -1
  9. package/lib/assertions/core/helpers.js +2 -6
  10. package/lib/assertions/core/helpers.js.map +1 -1
  11. package/lib/authoring.d.ts +1 -0
  12. package/lib/authoring.d.ts.map +1 -1
  13. package/lib/authoring.js +1 -0
  14. package/lib/authoring.js.map +1 -1
  15. package/lib/cli/program.d.ts.map +1 -1
  16. package/lib/cli/program.js +6 -2
  17. package/lib/cli/program.js.map +1 -1
  18. package/lib/cli/render/reporter.d.ts.map +1 -1
  19. package/lib/cli/render/reporter.js +16 -3
  20. package/lib/cli/render/reporter.js.map +1 -1
  21. package/lib/cli/run-options.d.ts +1 -0
  22. package/lib/cli/run-options.d.ts.map +1 -1
  23. package/lib/cli/run-options.js.map +1 -1
  24. package/lib/core/artifact-evidence.d.ts +8 -0
  25. package/lib/core/artifact-evidence.d.ts.map +1 -1
  26. package/lib/core/artifact-evidence.js +36 -4
  27. package/lib/core/artifact-evidence.js.map +1 -1
  28. package/lib/core/assertion.d.ts +19 -9
  29. package/lib/core/assertion.d.ts.map +1 -1
  30. package/lib/core/assertion.js +26 -0
  31. package/lib/core/assertion.js.map +1 -1
  32. package/lib/core/case-authoring-state.d.ts +12 -0
  33. package/lib/core/case-authoring-state.d.ts.map +1 -0
  34. package/lib/core/case-authoring-state.js +43 -0
  35. package/lib/core/case-authoring-state.js.map +1 -0
  36. package/lib/core/case.d.ts +29 -1
  37. package/lib/core/case.d.ts.map +1 -1
  38. package/lib/core/case.js +14 -6
  39. package/lib/core/case.js.map +1 -1
  40. package/lib/core/driver.d.ts +3 -0
  41. package/lib/core/driver.d.ts.map +1 -1
  42. package/lib/core/driver.js.map +1 -1
  43. package/lib/core/events.d.ts +2 -0
  44. package/lib/core/events.d.ts.map +1 -1
  45. package/lib/core/events.js +1 -0
  46. package/lib/core/events.js.map +1 -1
  47. package/lib/core/lifecycle-fixtures.d.ts +3 -1
  48. package/lib/core/lifecycle-fixtures.d.ts.map +1 -1
  49. package/lib/core/lifecycle-fixtures.js +4 -2
  50. package/lib/core/lifecycle-fixtures.js.map +1 -1
  51. package/lib/core/lifecycle-hooks.d.ts +9 -0
  52. package/lib/core/lifecycle-hooks.d.ts.map +1 -1
  53. package/lib/core/lifecycle-hooks.js +8 -2
  54. package/lib/core/lifecycle-hooks.js.map +1 -1
  55. package/lib/core/run.d.ts +1 -0
  56. package/lib/core/run.d.ts.map +1 -1
  57. package/lib/core/wire.d.ts +1 -0
  58. package/lib/core/wire.d.ts.map +1 -1
  59. package/lib/core/wire.js +5 -1
  60. package/lib/core/wire.js.map +1 -1
  61. package/lib/drivers/subprocess/index.d.ts.map +1 -1
  62. package/lib/drivers/subprocess/index.js +6 -0
  63. package/lib/drivers/subprocess/index.js.map +1 -1
  64. package/lib/index.d.ts +4 -4
  65. package/lib/index.d.ts.map +1 -1
  66. package/lib/index.js +4 -4
  67. package/lib/index.js.map +1 -1
  68. package/lib/orchestrator/annotation-store.d.ts.map +1 -1
  69. package/lib/orchestrator/annotation-store.js +6 -2
  70. package/lib/orchestrator/annotation-store.js.map +1 -1
  71. package/lib/orchestrator/case-extensions.d.ts +6 -0
  72. package/lib/orchestrator/case-extensions.d.ts.map +1 -0
  73. package/lib/orchestrator/case-extensions.js +28 -0
  74. package/lib/orchestrator/case-extensions.js.map +1 -0
  75. package/lib/orchestrator/config.d.ts.map +1 -1
  76. package/lib/orchestrator/config.js +0 -2
  77. package/lib/orchestrator/config.js.map +1 -1
  78. package/lib/orchestrator/doctor.d.ts.map +1 -1
  79. package/lib/orchestrator/doctor.js +5 -3
  80. package/lib/orchestrator/doctor.js.map +1 -1
  81. package/lib/orchestrator/grader-experiment.d.ts +12 -0
  82. package/lib/orchestrator/grader-experiment.d.ts.map +1 -1
  83. package/lib/orchestrator/grading.d.ts +2 -1
  84. package/lib/orchestrator/grading.d.ts.map +1 -1
  85. package/lib/orchestrator/grading.js +16 -4
  86. package/lib/orchestrator/grading.js.map +1 -1
  87. package/lib/orchestrator/loader.d.ts.map +1 -1
  88. package/lib/orchestrator/loader.js +2 -0
  89. package/lib/orchestrator/loader.js.map +1 -1
  90. package/lib/orchestrator/orchestrator.d.ts +3 -0
  91. package/lib/orchestrator/orchestrator.d.ts.map +1 -1
  92. package/lib/orchestrator/orchestrator.js +42 -14
  93. package/lib/orchestrator/orchestrator.js.map +1 -1
  94. package/lib/orchestrator/owned-lock.d.ts.map +1 -1
  95. package/lib/orchestrator/owned-lock.js +52 -38
  96. package/lib/orchestrator/owned-lock.js.map +1 -1
  97. package/lib/orchestrator/regrade.d.ts +6 -0
  98. package/lib/orchestrator/regrade.d.ts.map +1 -1
  99. package/lib/orchestrator/regrade.js +8 -4
  100. package/lib/orchestrator/regrade.js.map +1 -1
  101. package/lib/orchestrator/run-execution.d.ts +2 -1
  102. package/lib/orchestrator/run-execution.d.ts.map +1 -1
  103. package/lib/orchestrator/run-execution.js +3 -2
  104. package/lib/orchestrator/run-execution.js.map +1 -1
  105. package/package.json +1 -2
  106. package/skills/lux/SKILL.md +7 -3
  107. package/skills/lux/references/cli.md +25 -2
  108. package/skills/lux/references/eval-authoring.md +156 -4
  109. package/skills/lux-answerer/SKILL.md +1 -1
  110. package/src/answerers/terminal/index.ts +1 -1
  111. package/src/assertions/authoring.ts +79 -0
  112. package/src/assertions/core/helpers.ts +3 -6
  113. package/src/authoring.ts +1 -0
  114. package/src/cli/program.ts +9 -2
  115. package/src/cli/render/reporter.ts +18 -3
  116. package/src/cli/run-options.ts +1 -0
  117. package/src/core/artifact-evidence.ts +47 -4
  118. package/src/core/assertion.ts +41 -22
  119. package/src/core/case-authoring-state.ts +65 -0
  120. package/src/core/case.ts +57 -6
  121. package/src/core/driver.ts +3 -0
  122. package/src/core/events.ts +1 -0
  123. package/src/core/lifecycle-fixtures.ts +7 -2
  124. package/src/core/lifecycle-hooks.ts +16 -2
  125. package/src/core/wire.ts +5 -1
  126. package/src/drivers/subprocess/index.ts +6 -0
  127. package/src/index.ts +10 -6
  128. package/src/orchestrator/annotation-store.ts +5 -1
  129. package/src/orchestrator/case-extensions.ts +44 -0
  130. package/src/orchestrator/config.ts +0 -2
  131. package/src/orchestrator/doctor.ts +5 -3
  132. package/src/orchestrator/grading.ts +21 -2
  133. package/src/orchestrator/loader.ts +2 -0
  134. package/src/orchestrator/orchestrator.ts +86 -40
  135. package/src/orchestrator/owned-lock.ts +54 -34
  136. package/src/orchestrator/regrade.ts +8 -7
  137. package/src/orchestrator/run-execution.ts +5 -1
  138. package/lib/answerers/scripted/index.d.ts +0 -8
  139. package/lib/answerers/scripted/index.d.ts.map +0 -1
  140. package/lib/answerers/scripted/index.js +0 -163
  141. package/lib/answerers/scripted/index.js.map +0 -1
  142. package/src/answerers/scripted/index.ts +0 -186
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prismatic-io/lux",
3
- "version": "0.0.1",
3
+ "version": "0.0.2-preview.4",
4
4
  "description": "Coding-agent evaluation and improvement with deterministic assertions, optional LLM judges, and first-class human-in-the-loop runs.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/prismatic-io/lux",
@@ -73,7 +73,6 @@
73
73
  "@modelcontextprotocol/sdk": "^1.30.0",
74
74
  "@vscode/windows-process-tree": "0.8.0",
75
75
  "commander": "^15.0.0",
76
- "js-yaml": "^5.2.2",
77
76
  "oxc-parser": "^0.142.0",
78
77
  "tempy": "^3.2.0",
79
78
  "tinyexec": "1.2.4",
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: lux
3
- description: Author, validate, run, inspect, compare, and improve coding-agent evaluations with the Lux CLI. Use when creating Lux cases or campaigns, choosing assertions/personas/drivers, diagnosing eval failures, comparing runs, or running Lux experiments.
3
+ description: Author, validate, run, inspect, compare, and improve coding-agent evaluations with the Lux CLI. Use when creating or reviewing Lux cases, custom assertions, lifecycle fixtures, driver or harness configuration, campaigns, grader annotations, diagnosing eval failures, comparing runs, or running and promoting Lux experiments.
4
4
  ---
5
5
 
6
6
  # Lux
@@ -16,7 +16,7 @@ only for qualities that artifacts and commands cannot establish.
16
16
  2. If Lux is not initialized, run `npm exec -- lux init` for ordinary evals or
17
17
  `npm exec -- lux init --experiment` for an improvement campaign.
18
18
  3. Read [references/eval-authoring.md](references/eval-authoring.md) before
19
- authoring or reviewing a case.
19
+ authoring or reviewing a case, assertion plugin, or lifecycle fixture.
20
20
  4. Read [references/cli.md](references/cli.md) before running, diagnosing,
21
21
  comparing, or promoting results.
22
22
  5. Run `npm exec -- lux doctor` before model calls. Treat its errors as
@@ -40,6 +40,7 @@ only for qualities that artifacts and commands cannot establish.
40
40
 
41
41
  ```bash
42
42
  npm exec -- lux doctor
43
+ npm exec -- lux run <case-or-filter> --list
43
44
  npm exec -- lux run <case-or-filter> --loop 3
44
45
  npm exec -- lux view <run-or-suite>
45
46
  ```
@@ -50,7 +51,8 @@ before changing the case. Separate subject failure from harness failure:
50
51
  - Subject failure: the agent misunderstood, asked poorly, used the wrong tools,
51
52
  or left incorrect artifacts.
52
53
  - Harness failure: the prompt was underspecified, the persona lacked a needed
53
- fact, an assertion measured a proxy, or the judge criterion was ambiguous.
54
+ fact, a fixture or driver failed, an assertion measured a proxy, or the
55
+ judge criterion was ambiguous.
54
56
 
55
57
  Do not weaken a valid assertion merely to make a run pass.
56
58
 
@@ -78,6 +80,8 @@ Never edit a materialized candidate or managed experiment record by hand.
78
80
  behavior.
79
81
  - Do not use only `run-succeeded` for a case that claims to verify a concrete
80
82
  result.
83
+ - Do not invoke bare `lux`; it displays help. Use the explicit `lux run` verb
84
+ and narrow by case, path, or tag before any model call.
81
85
  - Do not let train examples leak into validation or test splits.
82
86
  - Do not combine evidence across changed models, harnesses, cases, or subjects;
83
87
  Lux identity checks exist to prevent that.
@@ -3,11 +3,19 @@
3
3
  Invoke the project-local CLI with `npm exec -- lux ...` unless the repository
4
4
  uses another package-manager convention.
5
5
 
6
+ Bare `lux` prints command and agent-skill help. Running cases always requires
7
+ the explicit `run` verb; do not assume an unfiltered run is harmless.
8
+ Use `lux <command> --help` before composing an unfamiliar or automation-facing
9
+ invocation; it is the authoritative option list for the installed version.
10
+
6
11
  ## Author and preflight
7
12
 
8
13
  ```bash
9
14
  lux init
10
15
  lux init --experiment
16
+ lux skills list
17
+ lux skills install codex --project
18
+ lux skills install claude --project
11
19
  lux doctor
12
20
  lux doctor experiments/campaign.experiment.ts --json
13
21
  lux run --list
@@ -27,19 +35,27 @@ lux run cases/retry-flag.ts
27
35
  lux run --tag smoke
28
36
  lux run retry --loop 5 --concurrency 2
29
37
  lux run one-case --interactive
38
+ lux run one-case --claude-answerer
39
+ lux run one-case --profile small-low --subject-root . --verbose
40
+ lux run one-case --skip-judge
30
41
  ```
31
42
 
32
43
  Filters match case IDs and paths. Tags come from `meta.tags`. A run invocation
33
44
  also creates a suite record, which is the right unit for repeated or multi-case
34
45
  comparison.
35
46
 
47
+ `--skip-judge` leaves subject execution unchanged but does not call rubric LLM
48
+ judges. Deterministic assertions still determine the verdict; judge-only cases
49
+ remain ungraded, and persisted grading records identify the skipped rubrics.
50
+
36
51
  ## Inspect and compare
37
52
 
38
53
  ```bash
39
54
  lux view
40
55
  lux view <run-or-suite>
56
+ lux view --suites --json
41
57
  lux view --experiments
42
- lux compare <run-or-suite-a> <run-or-suite-b>
58
+ lux compare <run-or-suite-a> <run-or-suite-b> --pass-rate-tolerance 0.05
43
59
  ```
44
60
 
45
61
  Use `view` before opening raw files. When diagnosing, inspect:
@@ -64,7 +80,9 @@ lux annotate <run> \
64
80
  ```
65
81
 
66
82
  Regrading preserves subject evidence and changes only grading. Human annotations
67
- support grader-alignment experiments; keep assertion IDs aligned with the
83
+ support grader-alignment experiments. `annotate` can record overall label or
84
+ score, repeatable assertion outcomes, split/tag selection, feedback, annotator,
85
+ reviewer, and human/synthetic provenance. Keep assertion IDs aligned with the
68
86
  rubric and record reviewer context where available.
69
87
 
70
88
  ## Experiment lifecycle
@@ -72,11 +90,13 @@ rubric and record reviewer context where available.
72
90
  ```bash
73
91
  lux experiment <campaign> --plan
74
92
  lux experiment <campaign>
93
+ lux experiment <campaign> --resume <experiment-dir>
75
94
  lux optimize <campaign> --plan
76
95
  lux optimize <campaign>
77
96
  lux optimize <campaign> --resume <experiment-dir>
78
97
  lux view <experiment-dir>
79
98
  lux report <experiment-dir> --out .lux-runs/reports/decision.md
99
+ lux report <experiment-dir> --json
80
100
  lux apply <experiment-dir>
81
101
  ```
82
102
 
@@ -96,6 +116,9 @@ Use the exact resume, report, and apply commands printed by Lux. Do not edit
96
116
  - Agent exit/error: inspect `events.jsonl` and the driver section of `run.json`.
97
117
  - Assertion failure: inspect the assertion evidence and final artifact; decide
98
118
  whether the subject or measurement is wrong.
119
+ - Tool evidence mismatch: compare paired call/result IDs and inspect unresolved,
120
+ unmatched, malformed, and failed tool outcomes; never infer success from a
121
+ call event alone.
99
122
  - Judge disagreement: narrow the rubric, regrade stored evidence, and collect
100
123
  human annotations before changing the subject.
101
124
  - Interrupted experiment: resume the printed experiment directory; do not start
@@ -51,13 +51,71 @@ Prefer deterministic assertions:
51
51
  - `run-succeeded`: the driver completed; necessary but rarely sufficient.
52
52
  - `file-exists`: a required artifact exists.
53
53
  - `file-contains`: a stable literal is present.
54
- - `json-path-equals`: structured output contains the expected value.
54
+ - `json-pointer-equals`: structured output contains the expected value.
55
55
  - `command-exits-zero`: project-authored validation succeeds.
56
56
 
57
+ Use the more specific bundled evidence checks when they match the contract:
58
+
59
+ - Files: `file-matches`, `file-regex-set`, `file-regex-count`, and `glob-count`.
60
+ - Output/data: `contains`, `not-contains`, `regex`, and `json-pointer-equals`.
61
+ - Interaction: `interrupt-count`, `tool-called`, `tool-called-with`, and
62
+ `tool-call-count`; use `by: "main"` or `by: "subagent"` when ownership matters.
63
+ - Paired tool evidence: `tool-invocation`, `tool-result-contains`,
64
+ `tool-result-json-pointer-equals`, `tool-result-is-error`, and
65
+ `no-error-results`. Prefer these when result status or payload matters; a tool
66
+ call without a paired result is unresolved, not successful.
67
+ - Budgets: `latency-under`, `tokens-under`, `cost-under`, and
68
+ `artifact-bytes-under`.
69
+ - MCP discovery: `server-info-equals`, `tool-listed`,
70
+ `tools-listed-count`, and `capability-absent`.
71
+
72
+ Most file assertions read captured `artifacts`; set `where: "cwd"` only when
73
+ the durable contract intentionally includes the staged run workspace. Use
74
+ stable assertion `id` values for repetitions, comparisons, annotations, and
75
+ experiments. `weight` affects aggregate score and `threshold` sets an optional
76
+ per-assertion score floor.
77
+
57
78
  Use `rubric` for semantic properties such as design quality, completeness, or
58
79
  whether a test meaningfully covers behavior. Keep criteria narrow. Avoid words
59
80
  like “good,” “proper,” or “best practice” without observable conditions.
60
81
 
82
+ Bound rubric evidence to what the criterion needs. Include `tool-result` event
83
+ evidence when status matters, and request tool inputs/results only when their
84
+ payload is necessary; this controls judge context and cost.
85
+
86
+ Persona answering and rubric judging have different difficulty profiles. A
87
+ persona usually chooses from facts already supplied in the case, so prefer a
88
+ cheap model and low effort. Semantic grading should use a stronger model and
89
+ enough effort to distinguish real evidence from plausible prose. Configure
90
+ them independently when that tradeoff matters:
91
+
92
+ ```ts
93
+ // lux.config.ts
94
+ defaultAnswerer: {
95
+ name: "persona",
96
+ config: {
97
+ provider: "codex",
98
+ model: "YOUR_SMALL_PERSONA_MODEL",
99
+ reasoningEffort: "low",
100
+ },
101
+ },
102
+
103
+ // case assertion
104
+ {
105
+ id: "behavior-quality",
106
+ type: "rubric",
107
+ criteria: "The tests exercise retry exhaustion and recovery.",
108
+ provider: "codex",
109
+ model: "YOUR_STRONG_JUDGE_MODEL",
110
+ reasoningEffort: "high",
111
+ }
112
+ ```
113
+
114
+ The project `harness` supplies shared defaults to persona and rubric calls;
115
+ answerer config and rubric fields override it independently. Always pin model
116
+ and effort when comparing runs, and inspect answerer versus judge usage in the
117
+ run summary before increasing either budget.
118
+
61
119
  Assertions should test user-visible behavior and durable contracts. Avoid
62
120
  requiring a specific function name, file layout, or algorithm unless that is
63
121
  the contract under evaluation.
@@ -73,9 +131,51 @@ The persona is simulated user state, not another system prompt. Include:
73
131
  Do not tell the persona how to help the agent solve the task. If no interaction
74
132
  is part of the capability being evaluated, omit the persona or keep it minimal.
75
133
 
76
- Use `interactionMode: "defer-resume"` for automated Claude Code question-tool
77
- handling. Use `textQuestionFallback: true` only when intentionally evaluating
78
- models that ask in prose instead of using the supported tool.
134
+ For Claude Code, use `interactionMode: "defer-resume"` for automated
135
+ question-tool handling. Use `textQuestionFallback: true` only when
136
+ intentionally evaluating models that ask in prose instead of using the
137
+ supported tool. For Codex, keep the default `interactionMode: "app-server"`
138
+ when the case can interrupt; compatibility mode `"exec"` cannot accept
139
+ mid-run responses.
140
+
141
+ Use `--interactive` for a real terminal user or `--claude-answerer` for the
142
+ Claude plugin protocol. Both force sequential execution.
143
+
144
+ ## Project extensions
145
+
146
+ For a reusable project-specific assertion, define it with `defineAssertion`
147
+ and a direct Zod object whose `type` is a matching literal. Import `z` from Lux,
148
+ return an ordinary failed result for missing/invalid product evidence, and
149
+ reserve throws or `errorMessage` for evaluator faults. Increment
150
+ `pluginVersion` whenever schema or evaluation behavior changes. Prefer the
151
+ bounded, traversal-safe `readRunArtifactText` helper over direct filesystem
152
+ reads.
153
+
154
+ Build a typed case authoring function once instead of importing plugins in
155
+ every case:
156
+
157
+ ```ts
158
+ const assertions = defineAssertionRegistry(projectAssertion);
159
+ export const defineEvalCase = defineLuxCase.extend({ assertions });
160
+ ```
161
+
162
+ Case-bound plugins remain plain serialized plans but require the current case
163
+ path for regrading: `lux grade <run> --case <case.ts>`.
164
+
165
+ Use `defineFixtures` for Vitest-style suite/case setup around `await use()` or
166
+ `defineLifecycleHooks` when cleanup-returning callbacks fit better. Setup runs
167
+ in declaration order and teardown in reverse, including failures. Call
168
+ `await use()` exactly once. Fixture `value`/`identity` must be public JSON;
169
+ keep secrets in the prepared resource or environment. Increment
170
+ `pluginVersion` when lifecycle behavior changes. Register globally in
171
+ `lux.config.ts`, or attach selectively with
172
+ `defineEvalCase.extend({ fixtures: [...] })`.
173
+
174
+ Custom drivers that own an in-memory filesystem should persist it with
175
+ `writeArtifactSnapshot` from `@prismatic-io/lux/artifact-snapshot`. Snapshots
176
+ are bounded, traversal-safe, immutable per name, and can use `baseSnapshot`
177
+ for copy-on-write reuse while remaining independently complete. Ordinary file
178
+ assertions then grade the indexed files without a snapshot-specific API.
79
179
 
80
180
  ## Experiments for prompts, skills, and agent source
81
181
 
@@ -100,6 +200,58 @@ Campaigns should:
100
200
  - budget calls, tokens, cost, and wall time;
101
201
  - use repetitions and confidence gates appropriate to stochastic outcomes;
102
202
  - reserve held-out cases for promotion and final confirmation.
203
+ - acknowledge every candidate validation command with
204
+ `unsafeHostExecution: true`; validators must be observational and must not
205
+ mutate the candidate tree.
206
+
207
+ A minimal current agent campaign has this shape (selectors are objects, mutable
208
+ paths are candidate-relative globs, and budgets use call/token/cost limits):
209
+
210
+ ```ts
211
+ export default defineExperiment({
212
+ id: "skill-improvement",
213
+ subject: { root: "..", mutable: ["skills/example/**"] },
214
+ profiles: ["claude-low", "codex-low"],
215
+ splits: {
216
+ train: { tags: ["train"] },
217
+ validation: { tags: ["validation"] },
218
+ test: { tags: ["test"] },
219
+ },
220
+ repetitions: { search: 1, promotion: 3 },
221
+ objectives: {
222
+ constraints: [
223
+ { metric: "quality.caseRegressions", max: 0 },
224
+ { metric: "quality.passRate", min: 0.8, split: "test" },
225
+ ],
226
+ minimize: [{ metric: "agent.total" }, { metric: "subject.bytes" }],
227
+ },
228
+ optimizer: {
229
+ algorithm: "reflective",
230
+ candidateSelection: "pareto",
231
+ promotionTopK: 2,
232
+ config: {
233
+ provider: "codex",
234
+ model: "YOUR_OPTIMIZER_MODEL",
235
+ reasoningEffort: "low",
236
+ },
237
+ },
238
+ budget: {
239
+ maxCandidates: 4,
240
+ patience: 1,
241
+ maxMetricCalls: 60,
242
+ maxJudgeCalls: 120,
243
+ maxProposalCalls: 3,
244
+ maxTokens: 2_000_000,
245
+ maxCostUsd: 30,
246
+ maxUnpricedModelCalls: 0,
247
+ maxWallTimeMs: 3_600_000,
248
+ },
249
+ });
250
+ ```
251
+
252
+ Use the experiment directory printed by the run for `view` and `report`.
253
+ Review its held-out evidence, then pass that same verified directory to
254
+ `apply`; do not guess a directory from the campaign ID.
103
255
 
104
256
  Use `experiment` for authored variants and `optimize` for model-proposed source
105
257
  changes. Always run the corresponding `--plan` command first.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: lux-answerer
3
- version: 0.0.1
3
+ version: 0.0.2-preview.4
4
4
  description: Play the persona for a running Lux orchestrator that uses the claude-code answerer. Read structured events on stdout, decide answers from persona + context, write JSON to its answer channel.
5
5
  user-invocable: false
6
6
  allowed-tools: Bash, Read
@@ -70,7 +70,7 @@ class TerminalAnswererImpl implements Answerer {
70
70
  if (!stdin.isTTY || !stdout.isTTY) {
71
71
  throw new Error(
72
72
  "terminal answerer: --interactive requires an attached terminal; " +
73
- "use a scripted/model answerer in CI or --claude-answerer from the Claude plugin",
73
+ "use a model answerer in CI or --claude-answerer from the Claude plugin",
74
74
  );
75
75
  }
76
76
  this.interface = createInterface({ input: stdin, output: stdout, terminal: true });
@@ -0,0 +1,79 @@
1
+ import type { z } from "zod";
2
+ import { createEvalCaseDefinition } from "../core/case.js";
3
+ import type {
4
+ ArtifactBytesUnderConfigSchema,
5
+ CommandExitsZeroConfigSchema,
6
+ ContainsConfigSchema,
7
+ CostUnderConfigSchema,
8
+ FileContainsConfigSchema,
9
+ FileExistsConfigSchema,
10
+ FileMatchesConfigSchema,
11
+ FileRegexCountConfigSchema,
12
+ FileRegexSetConfigSchema,
13
+ GlobCountConfigSchema,
14
+ InterruptCountConfigSchema,
15
+ JsonPointerEqualsConfigSchema,
16
+ LatencyUnderConfigSchema,
17
+ NoErrorResultsConfigSchema,
18
+ NotContainsConfigSchema,
19
+ PredicateConfigSchema,
20
+ RegexConfigSchema,
21
+ RunSucceededConfigSchema,
22
+ TokensUnderConfigSchema,
23
+ ToolCallCountConfigSchema,
24
+ ToolCalledConfigSchema,
25
+ ToolCalledWithConfigSchema,
26
+ ToolInvocationConfigSchema,
27
+ ToolResultContainsConfigSchema,
28
+ ToolResultIsErrorConfigSchema,
29
+ ToolResultJsonPointerEqualsConfigSchema,
30
+ } from "./core/index.js";
31
+ import type {
32
+ CapabilityAbsentConfigSchema,
33
+ ServerInfoEqualsConfigSchema,
34
+ ToolListedConfigSchema,
35
+ ToolsListedCountConfigSchema,
36
+ } from "./mcp/index.js";
37
+ import type { RubricConfigSchema } from "./rubric/index.js";
38
+
39
+ type BuiltinAssertionSchema =
40
+ | typeof ArtifactBytesUnderConfigSchema
41
+ | typeof CapabilityAbsentConfigSchema
42
+ | typeof CommandExitsZeroConfigSchema
43
+ | typeof ContainsConfigSchema
44
+ | typeof CostUnderConfigSchema
45
+ | typeof FileContainsConfigSchema
46
+ | typeof FileExistsConfigSchema
47
+ | typeof FileMatchesConfigSchema
48
+ | typeof FileRegexCountConfigSchema
49
+ | typeof FileRegexSetConfigSchema
50
+ | typeof GlobCountConfigSchema
51
+ | typeof InterruptCountConfigSchema
52
+ | typeof JsonPointerEqualsConfigSchema
53
+ | typeof LatencyUnderConfigSchema
54
+ | typeof NoErrorResultsConfigSchema
55
+ | typeof NotContainsConfigSchema
56
+ | typeof PredicateConfigSchema
57
+ | typeof RegexConfigSchema
58
+ | typeof RubricConfigSchema
59
+ | typeof RunSucceededConfigSchema
60
+ | typeof ServerInfoEqualsConfigSchema
61
+ | typeof TokensUnderConfigSchema
62
+ | typeof ToolCalledConfigSchema
63
+ | typeof ToolCalledWithConfigSchema
64
+ | typeof ToolCallCountConfigSchema
65
+ | typeof ToolInvocationConfigSchema
66
+ | typeof ToolListedConfigSchema
67
+ | typeof ToolResultContainsConfigSchema
68
+ | typeof ToolResultIsErrorConfigSchema
69
+ | typeof ToolResultJsonPointerEqualsConfigSchema
70
+ | typeof ToolsListedCountConfigSchema;
71
+
72
+ export type BuiltinAssertion = z.input<BuiltinAssertionSchema> & {
73
+ id?: string;
74
+ name?: string;
75
+ weight?: number;
76
+ threshold?: number;
77
+ };
78
+
79
+ export const defineEvalCase = createEvalCaseDefinition<BuiltinAssertion>();
@@ -3,6 +3,7 @@ import { lstat } from "node:fs/promises";
3
3
  import { isAbsolute, join, relative, resolve, sep } from "node:path";
4
4
  import { z } from "zod";
5
5
  import { inspectRunArtifact, runArtifactsRoot } from "../../core/artifact-evidence.js";
6
+ import { baseAssertionFields } from "../../core/assertion.js";
6
7
  import type { Run, RunEvent } from "../../core/index.js";
7
8
 
8
9
  const FsLocationSchema = z.enum(["artifacts", "cwd"]);
@@ -175,12 +176,6 @@ export const deepEquals = (a: unknown, b: unknown): boolean => {
175
176
  * Mirrors {@link Assertion} from the core module while letting each built-in
176
177
  * assertion reject misspelled type-specific fields at its own schema boundary.
177
178
  */
178
- export const baseAssertionFields = {
179
- id: z.string().min(1).optional(),
180
- weight: z.number().positive().optional(),
181
- threshold: z.number().min(0).max(1).optional(),
182
- name: z.string().optional(),
183
- } as const;
184
179
 
185
180
  /**
186
181
  * Where on the run dir an assertion looks for files. Defaults to
@@ -190,6 +185,8 @@ export const LocationSchema = FsLocationSchema.default("artifacts");
190
185
 
191
186
  // `name` is intentionally excluded: for tool assertions the `name` field is
192
187
  // the tool's name, which then doubles as the display label.
188
+ export { baseAssertionFields };
189
+
193
190
  export const weightThresholdFields = {
194
191
  id: baseAssertionFields.id,
195
192
  weight: baseAssertionFields.weight,
package/src/authoring.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export { z } from "zod";
2
+ export { defineEvalCase } from "./assertions/authoring.js";
2
3
  export * from "./core/index.js";
@@ -163,7 +163,7 @@ const registerExperimentCommands = (program: Command): void => {
163
163
  });
164
164
 
165
165
  program
166
- .command("run [filters...]", { isDefault: true })
166
+ .command("run [filters...]")
167
167
  .description("discover and run cases; filter by name/path substring or --tag")
168
168
  .option(
169
169
  "-t, --tag <tag>",
@@ -184,6 +184,7 @@ const registerExperimentCommands = (program: Command): void => {
184
184
  "--claude-answerer",
185
185
  "delegate questions through the Claude plugin's lux-answerer skill",
186
186
  )
187
+ .option("--skip-judge", "skip rubric LLM judge assertions")
187
188
  .option("--runs-root <path>", "run-dir root (overrides lux.config.ts)")
188
189
  .option("--profile <id>", "named driver/answerer profile from lux.config.ts")
189
190
  .option("--subject-root <path>", "root bound into subjectPath() values")
@@ -623,11 +624,16 @@ export const buildProgram = (): Command => {
623
624
  .description(
624
625
  "Coding-agent evaluation and improvement with deterministic assertions and human-in-the-loop runs",
625
626
  )
626
- .version(PKG_VERSION);
627
+ .version(PKG_VERSION)
628
+ .addHelpText(
629
+ "after",
630
+ "\nAgent skills:\n $ lux skills list\n $ lux skills install codex --project\n $ lux skills install claude --project",
631
+ );
627
632
  registerProjectCommands(program);
628
633
  registerExperimentCommands(program);
629
634
  registerCandidateCommands(program);
630
635
  registerReportingCommands(program);
636
+ program.action(() => program.outputHelp());
631
637
  return program;
632
638
  };
633
639
 
@@ -965,6 +971,7 @@ const runCommand = async (filters: string[], options: RunOptions): Promise<void>
965
971
  runsRoot,
966
972
  fixturesRoot: config.fixturesRoot,
967
973
  answererOverride: resolveAnswererOverride(options),
974
+ skipJudge: options.skipJudge,
968
975
  observer: reporter,
969
976
  abortSignal: controller.signal,
970
977
  concurrency,
@@ -4,6 +4,7 @@ import {
4
4
  createToolOutcomeAccumulator,
5
5
  emptyRunUsage,
6
6
  type GradingAssertionEntry,
7
+ type GradingReport,
7
8
  harnessUsage,
8
9
  isUsageEmpty,
9
10
  type LifecycleStage,
@@ -77,6 +78,7 @@ type CaseView = {
77
78
  assertionTotal: number;
78
79
  currentAssertion: string | null;
79
80
  assertions: GradingAssertionEntry[];
81
+ skippedAssertions: NonNullable<GradingReport["skippedAssertions"]>;
80
82
  usage: RunUsage;
81
83
  /** Per-API-call agent usage, in order — the split behind `usage.agent`. */
82
84
  agentTurns: TurnUsage[];
@@ -117,7 +119,10 @@ const statusMark = (status: CaseStatus, theme: Theme): string => {
117
119
 
118
120
  const assertionTally = (view: CaseView): string => {
119
121
  const passed = view.assertions.filter((a) => a.passed).length;
120
- return view.assertions.length === 0 ? "—" : `${passed}/${view.assertions.length}`;
122
+ const evaluated = view.assertions.length === 0 ? "—" : `${passed}/${view.assertions.length}`;
123
+ return view.skippedAssertions.length > 0
124
+ ? `${evaluated} · ${view.skippedAssertions.length} skipped`
125
+ : evaluated;
121
126
  };
122
127
 
123
128
  /**
@@ -233,6 +238,7 @@ export class ConsoleReporter implements SuiteObserver {
233
238
  assertionTotal: 0,
234
239
  currentAssertion: null,
235
240
  assertions: [],
241
+ skippedAssertions: [],
236
242
  usage: emptyRunUsage(),
237
243
  agentTurns: [],
238
244
  phaseDurationMs: null,
@@ -299,6 +305,7 @@ export class ConsoleReporter implements SuiteObserver {
299
305
  if (result.errorMessage) view.errorMessage = result.errorMessage;
300
306
  if (result.result) {
301
307
  view.assertions = result.result.grading.assertions;
308
+ view.skippedAssertions = result.result.grading.skippedAssertions ?? [];
302
309
  view.usage = result.result.metadata.usage;
303
310
  view.phaseDurationMs = result.result.metadata.phaseDurationMs ?? null;
304
311
  const tools = result.result.metadata.toolOutcomes;
@@ -315,6 +322,7 @@ export class ConsoleReporter implements SuiteObserver {
315
322
  // Assertions reported before the run failed to persist are not results
316
323
  // and must not inflate the closing tally of a case that errored.
317
324
  view.assertions = [];
325
+ view.skippedAssertions = [];
318
326
  }
319
327
 
320
328
  this.region.staticLine(this.finishedLine(view));
@@ -452,6 +460,11 @@ export class ConsoleReporter implements SuiteObserver {
452
460
  body: !entry.passed && entry.comment ? [this.theme.dim(entry.comment)] : [],
453
461
  });
454
462
  }
463
+ for (const skipped of view.skippedAssertions) {
464
+ nodes.push({
465
+ head: `${this.theme.dim("⊘")} ${skipped.name} ${this.theme.dim("skipped: judge disabled")}`,
466
+ });
467
+ }
455
468
 
456
469
  if (view.toolCalls > 0 || view.toolUnmatchedResults > 0 || view.toolMalformed > 0) {
457
470
  const parts = [
@@ -550,15 +563,17 @@ export class ConsoleReporter implements SuiteObserver {
550
563
  }
551
564
 
552
565
  const assertions = this.cases.flatMap((c) => c.assertions);
553
- if (assertions.length > 0) {
566
+ const skippedAssertions = this.cases.flatMap((c) => c.skippedAssertions);
567
+ if (assertions.length > 0 || skippedAssertions.length > 0) {
554
568
  const ok = assertions.filter((a) => a.passed).length;
555
569
  const bad = assertions.length - ok;
556
570
  const tally = formatTally(
557
571
  [
558
572
  [ok, this.theme.green("passed")],
559
573
  [bad, this.theme.red("failed")],
574
+ [skippedAssertions.length, this.theme.dim("skipped")],
560
575
  ],
561
- assertions.length,
576
+ assertions.length + skippedAssertions.length,
562
577
  );
563
578
  lines.push(` ${label("Assertions")}${tally}`);
564
579
  }
@@ -8,6 +8,7 @@ export type RunOptions = {
8
8
  listTags?: boolean;
9
9
  interactive?: boolean;
10
10
  claudeAnswerer?: boolean;
11
+ skipJudge?: boolean;
11
12
  runsRoot?: string;
12
13
  profile?: string;
13
14
  subjectRoot?: string;