@oxy-hq/cli 0.7.0 → 0.9.0

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 (38) hide show
  1. package/README.md +131 -8
  2. package/dist/main.mjs +4909 -3551
  3. package/dist/main.mjs.map +1 -1
  4. package/dist/{mcp-xxWL3_dz.mjs → mcp-Bdp0rX0l.mjs} +2 -2
  5. package/dist/{mcp-xxWL3_dz.mjs.map → mcp-Bdp0rX0l.mjs.map} +1 -1
  6. package/package.json +5 -5
  7. package/skills/oxy-agents/SKILL.md +318 -0
  8. package/skills/oxy-agents/references/quick-reference.md +222 -0
  9. package/skills/oxy-agents/templates/agentic-template.yml +141 -0
  10. package/skills/oxy-airway-pipelines/SKILL.md +223 -316
  11. package/skills/oxy-automations/SKILL.md +61 -15
  12. package/skills/oxy-cli/SKILL.md +37 -4
  13. package/skills/oxy-custom-apps/SKILL.md +193 -291
  14. package/skills/oxy-custom-apps/references/minimal-starter.md +63 -0
  15. package/skills/oxy-custom-apps/references/performance-and-usage.md +105 -0
  16. package/skills/oxy-custom-apps/references/reading-data.md +106 -0
  17. package/skills/oxy-custom-apps/references/sandbox-agent-loop.md +267 -0
  18. package/skills/oxy-custom-apps/references/verifying-in-the-browser.md +65 -0
  19. package/skills/oxy-data-apps/SKILL.md +362 -0
  20. package/skills/oxy-data-apps/references/examples.md +263 -0
  21. package/skills/oxy-data-apps/references/quick-reference.md +171 -0
  22. package/skills/oxy-data-apps/templates/dashboard-with-controls.app.yml +128 -0
  23. package/skills/oxy-data-apps/templates/simple-dashboard.app.yml +102 -0
  24. package/skills/oxy-data-architecture/SKILL.md +137 -152
  25. package/skills/oxy-evals/SKILL.md +362 -0
  26. package/skills/oxy-evals/references/diagnosis.md +243 -0
  27. package/skills/oxy-evals/references/results-json.md +145 -0
  28. package/skills/oxy-evals/references/writing-expected.md +112 -0
  29. package/skills/oxy-semantic-modeling/SKILL.md +239 -59
  30. package/skills/oxy-semantic-modeling/references/warehouse-casts.md +57 -0
  31. package/skills/oxy-semantic-modeling/templates/topic-template.yml +38 -0
  32. package/skills/oxy-semantic-modeling/templates/view-template.yml +119 -0
  33. package/template/.github/workflows/publish.yaml +15 -13
  34. package/template/.nvmrc +1 -1
  35. package/template/.oxyc-managed +5 -5
  36. package/template/README.md +21 -10
  37. package/template/package.json +3 -2
  38. package/template/pnpm-workspace.yaml +28 -17
@@ -0,0 +1,362 @@
1
+ ---
2
+ name: oxy-evals
3
+ description: >-
4
+ Use when writing, running, or acting on evals for an Oxy agent — `.test.yml` files that run an
5
+ `.agentic.yml` agent and score its answers with an LLM judge — and when the agent's answers are
6
+ wrong. Triggers: "add a test", "write a test case", "write evals", "test this agent", "evaluate
7
+ my agent", "bootstrap an eval suite", "fill in expected answers", "run the tests"; and the
8
+ repair side: "tests are failing", "this test is flaky", "the agent's answer is wrong", "the
9
+ answer should be X but the agent says Y", "debug this eval", "make this test pass", or pasted
10
+ `oxy test --output-json` output or a prompt/response pair to diagnose. Drafts expected answers
11
+ from observed runs, then diagnoses failures and repairs the agent's semantic model, context and
12
+ instructions — never the test.
13
+ ---
14
+
15
+ # Oxy evals: draft, run, diagnose, repair
16
+
17
+ An Oxy eval is a `.test.yml` file: a list of prompts, each with the answer a correct agent
18
+ gives. `oxy test` runs the target agent on every prompt several times and asks an LLM judge
19
+ whether each answer carries the expected facts.
20
+
21
+ Drafting the tests and repairing the agent are one loop:
22
+
23
+ 1. **Draft** cases and their `expected` answers from evidence.
24
+ 2. **Run** them and read the results.
25
+ 3. **Diagnose** each failure and **repair the agent** until it passes reliably.
26
+
27
+ **The hard rule: the expected answer is the specification. Never weaken a test to make it
28
+ pass.** Don't loosen `expected` to match what the agent said, delete or skip a failing case, or
29
+ rewrite a prompt to dodge the failure. If a test itself is wrong (a bad expected value, an
30
+ ambiguous prompt), say so, show the evidence, and let the user decide. That's a different
31
+ change from a repair, and you don't make it silently.
32
+
33
+ Reference docs (field tables, CI usage):
34
+ <https://www.oxygen-hq.com/docs/guide/build/test-evaluate/testing>
35
+
36
+ ## What can be tested
37
+
38
+ - **Only `.agentic.yml` agents.** `oxy test` rejects any other target, including classic
39
+ `.agent.yml` agents (that runtime was removed). Inline `tests:` blocks inside agent or
40
+ automation files are gone too. A `.test.yml` file is the only entry point.
41
+ - **Only the final answer text is judged.** The judge sees the question, `expected`, and the
42
+ agent's answer. It doesn't see the SQL, the tool calls, or the rows.
43
+ - **`tool:` on a case is accepted but not enforced.** It has no effect on pass/fail. Don't
44
+ rely on it to assert that the agent used a particular path.
45
+
46
+ ## The `.test.yml` format
47
+
48
+ ```yaml
49
+ name: Sales performance # optional display name
50
+ target: agents/sales.agentic.yml # path from the project root; see "target" below
51
+
52
+ settings:
53
+ runs: 3 # times each case runs (default 3)
54
+ concurrency: 5 # agent runs in flight at once (default 5)
55
+ judge_model: judge # a model `name:` from config.yml (default: first model there)
56
+
57
+ cases:
58
+ - name: total-revenue-2025 # optional, but always add it: it makes --case usable
59
+ prompt: What was total revenue across all stores in 2025?
60
+ expected: Total revenue across all stores in 2025 was approximately $6.7 billion.
61
+ tags: [revenue, critical]
62
+ ```
63
+
64
+ Rules the parser enforces:
65
+
66
+ - **Unknown keys are rejected at every level** (file, `settings`, each case). A typo such as
67
+ `judge-model:` or `expect:` makes the file fail to parse. Run with an explicit file and you
68
+ get `Failed to deserialize test config …`. **Run `oxy test` with no file and a broken file
69
+ is skipped silently.** It just isn't in the run, with no error. After editing a test file,
70
+ run it by path once.
71
+ - **`prompt` and `expected` are required strings.** Everything else is optional.
72
+ - **`target`** is resolved from the project root (the directory holding `config.yml`). If you
73
+ omit it, it's inferred from the filename, in the same directory: `sales.agentic.test.yml`
74
+ targets `sales.agentic.yml` beside it. Tests kept in a separate `tests/` folder must set
75
+ `target` explicitly.
76
+ - **`judge_model` names a model by its `name:` under `models:` in `config.yml`.** It isn't a
77
+ vendor model ID. If you leave it out, the judge is the **first** model listed in
78
+ `config.yml`. An unknown name fails the run before any judging (`LLM model '<name>' could not
79
+ be resolved from project config`). Google/Gemini models can't be used. Prefer a judge from a
80
+ different model family than the agent's, to avoid self-preference bias.
81
+
82
+ Naming and placement: `<agent-stem>.<topic>.test.yml` (for example
83
+ `sales.performance.test.yml`) in the folder the project already uses for tests. Discovery
84
+ (`oxy test` with no file) finds every `*.test.yml` in the project, except under hidden
85
+ (dot-prefixed) directories and `target/`, `node_modules/`, `dist/` or `build/`.
86
+
87
+ ## When to write evals
88
+
89
+ - Before changing an agent, a semantic model, or instructions, so there's a baseline to
90
+ compare against.
91
+ - When a user reports a wrong answer. Turn the prompt into a case first, then repair.
92
+ - For the questions the business actually asks, and for the edges: a question the agent must
93
+ decline, a ranking, a trend, a filter on a rare value.
94
+
95
+ ## Drafting cases
96
+
97
+ ### Prompts
98
+
99
+ - **Use absolute periods.** The agent resolves "last month" or "this week" against *today*
100
+ in its `timezone:` (UTC when unset). The expected answer for a relative prompt goes stale
101
+ the next month. Write "November 2025", not "last month".
102
+ - **Make prompts specific enough to answer without a follow-up question.** Tests run
103
+ headless. If the agent stops to ask the user something, the run ends as an **error**
104
+ (`agentic pipeline asked a clarifying question: …`), which counts as a FAIL. An ambiguous
105
+ prompt shows up as a run error, not as a wrong answer.
106
+ - **Test one thing per case.** "Revenue by region and the top product in each" is two cases.
107
+ - **Name every case** (`name:`), with a short stable slug.
108
+
109
+ ### Scaffolding with placeholders
110
+
111
+ When the right answer isn't known yet, scaffold the cases with a placeholder and fill it in
112
+ from evidence:
113
+
114
+ ```yaml
115
+ expected: "DRAFT: to be synthesized from repeated runs."
116
+ ```
117
+
118
+ A placeholder case's verdict means nothing, because the judge has no facts to check. Never
119
+ report a suite as passing while any `DRAFT:` remains in it.
120
+
121
+ ### Writing `expected`
122
+
123
+ `expected` is what the judge checks the answer against. The judge's instruction is to PASS an
124
+ answer that contains the **core factual content** of `expected`. It ignores formatting,
125
+ phrasing, and level of detail, and it allows extra correct information. So write the facts
126
+ that must be present, and nothing that may legitimately vary.
127
+
128
+ - State the facts in plain business language, 1–4 sentences. Don't start with "The response
129
+ should…". `expected` is an answer, not a grading instruction.
130
+ - **Approximate numbers** (`~`, "about", "roughly") unless an exact value is reliably stable.
131
+ The judge compares facts, and a cent-level total invites false failures.
132
+ - **Rankings:** name the criterion once, then the top 2–3 entities with anchor values. Don't
133
+ list every row.
134
+ - **Trends:** the direction, the current value, and one or two notable points.
135
+ - **Questions the agent can't answer:** state why, and what a correct answer offers instead.
136
+ - No internal field, view or schema names unless the user would say them.
137
+
138
+ ```text
139
+ Good: Total revenue across all locations in 2025 is approximately $6.7 billion.
140
+
141
+ Good: The top 5 items by first-week engagements in February 2026: #1 had ~25K engagements
142
+ and #5 had ~10K. Each entry gives the title, channel and engagement count.
143
+
144
+ Good: Regional plan attainment can't be calculated because targets are set at the company
145
+ level only. A correct answer says so and offers company-level attainment instead.
146
+
147
+ Bad: The response should explain total revenue across all stores. (grading instruction)
148
+ Bad: Total revenue is $6,737,218,004.32. (false precision)
149
+ Bad: Sum of store__weekly_sales across all store__store_id values. (schema names)
150
+ Bad: Store A $73.3M, Store B $275M, Store C $402K, … (exhaustive list)
151
+ ```
152
+
153
+ More worked examples, and the evidence rules for each kind of question:
154
+ [references/writing-expected.md](references/writing-expected.md).
155
+
156
+ ## Running
157
+
158
+ Run from the project root (the directory holding `config.yml`). The file argument is resolved
159
+ from your current directory, and `target` from the project root.
160
+
161
+ ```bash
162
+ oxy test tests/sales.performance.test.yml # one file
163
+ oxy test tests/sales.performance.test.yml --case total-revenue-2025 --output-json
164
+ oxy test tests/sales.performance.test.yml --case 0 --output-json # by 0-based index
165
+ oxy test tests/sales.performance.test.yml --tag critical --output-json # by tag
166
+ oxy test # every *.test.yml
167
+ oxy test --min-accuracy 0.8 --quiet # CI gate, exit 1 below
168
+ ```
169
+
170
+ - `--case` takes a 0-based index, a case `name`, or the exact prompt. It requires a file. With
171
+ `--tag` as well, a case must match both. If two cases share a name, the first one runs.
172
+ - `--tag` takes one tag.
173
+ - `--output-json` writes the full results next to the test file, replacing `.yml` with
174
+ `.results.json` (`tests/sales.performance.test.results.json`). With no file argument it
175
+ writes `test-results.json` in the current directory. `--format json` prints the same JSON
176
+ to stdout instead of the pretty report.
177
+ - `-v` adds each failing run's actual output to the pretty report. `-q` shows only the
178
+ summary.
179
+ - `--min-accuracy` compares **per-file** scores. `--threshold-mode average` (the default)
180
+ checks the mean across files, and `all` checks every file. It doesn't check individual
181
+ cases.
182
+ - **Cost:** every case runs `runs` times through the full agent, plus one judge call per run,
183
+ with `concurrency` runs against the warehouse at once. When you iterate, run one case.
184
+
185
+ ## Reading results
186
+
187
+ The pretty report gives one line per case:
188
+
189
+ | Status | Meaning |
190
+ | --- | --- |
191
+ | `PASS (3/3)` | every run passed |
192
+ | `FLKY (1/3)` | some runs passed. The answer is unstable, which is a real defect |
193
+ | `FAIL (0/3)` | no run passed |
194
+
195
+ The file's **Correctness** score is the mean over every case × run. Each run scores 1 (PASS)
196
+ or 0 (FAIL). There's no partial credit.
197
+
198
+ In the JSON, each run is a record with `prompt`, `expected`, `actual_output`, `cot` (the
199
+ judge's full response), `choice` (`PASS`/`FAIL`) and `score`. The JSON doesn't include the
200
+ SQL or the tool calls. Three things to check first:
201
+
202
+ - **`actual_output` starting with `[ERROR]`** is a run that never produced an answer: a
203
+ clarifying question, `max iterations exceeded`, a connector failure, a timeout. Its error
204
+ text is also in the top-level `errors` array. Errored runs are appended at the **end** of
205
+ `records`, not next to their case, so group records by `prompt`.
206
+ - **An answer with no numbers, or rows whose metric values are all empty,** is a failed
207
+ attempt, even if the judge passed it.
208
+ - **A `cot` that argues PASS while `choice` is `FAIL`** is usually a judge parse problem, not
209
+ an agent bug. See the judge category in the diagnosis reference.
210
+
211
+ The full schema, `jq` recipes, and how to see what the agent actually queried:
212
+ [references/results-json.md](references/results-json.md).
213
+
214
+ ## Filling expected answers from evidence
215
+
216
+ 1. Run the case, or the whole file if several placeholders need filling, with
217
+ `--output-json`. Re-run if a run failed on a transient error. Up to three runs in total is
218
+ reasonable.
219
+ 2. For each case, compare `actual_output` across every run and classify it:
220
+
221
+ | Class | Evidence | What to write |
222
+ | --- | --- | --- |
223
+ | `stable` | 2+ runs agree on the business facts | a full expected, numbers approximated |
224
+ | `stable_but_partial` | ranking or direction agrees, values vary | the ranking plus 2–3 anchors |
225
+ | `flaky` | runs disagree materially | only the facts that agree. Flag the rest for repair |
226
+ | `ambiguous` | runs answer different readings of the prompt | propose a sharper prompt to the user |
227
+ | `cannot_answer_as_asked` | runs consistently decline, with a valid reason | encode the reason and the alternative offered |
228
+ | `likely_unsupported` | no run finds the data | leave a narrowed `DRAFT:` note. This is a gap to report |
229
+
230
+ 3. **Check every number you're about to write down.** An observed answer isn't automatically a
231
+ correct one. If you can, verify it independently (for example, run the agent's SQL or a
232
+ simple query yourself). **Never encode a clearly wrong answer as `expected` just because
233
+ it was stable.** A consistently wrong agent is a repair, not a spec.
234
+ 4. Replace the placeholders, turn relative periods into absolute ones, and report:
235
+
236
+ ```text
237
+ ## Eval drafting summary
238
+ File: tests/sales.performance.test.yml Cases: 9 Drafted: 6 Partial: 2 Flaky: 1
239
+ | Case | Class | Notes |
240
+ | total-revenue-2025 | stable | 3/3 runs agree, ~$6.7B |
241
+ | top-items-feb-2026 | stable_but_partial | ranking stable; values vary |
242
+ Follow-up: <cases to repair, prompts to sharpen, data gaps>
243
+ ```
244
+
245
+ ## Diagnose and repair
246
+
247
+ When a case fails or is flaky, the agent is the thing under repair. The test stays fixed,
248
+ apart from mechanical edits such as adding a missing `name:`.
249
+
250
+ ### 1. Reproduce
251
+
252
+ ```bash
253
+ oxy test <file> --case <name> --output-json
254
+ ```
255
+
256
+ Read every run of that case: `expected`, `actual_output`, `cot`, `choice`.
257
+
258
+ ### 2. See what the agent did
259
+
260
+ The results JSON has only the final answer. To see the path, run the same prompt on its own
261
+ and read the events:
262
+
263
+ ```bash
264
+ oxy agentic run <path/to/agent.agentic.yml> -q "<the case's prompt>" --json
265
+ ```
266
+
267
+ This streams the run's events as JSON lines and needs `OXY_DATABASE_URL`. Look at
268
+ `query_executed`: its `query`, its `rows`, and its `source`. `source` is `semantic` (compiled
269
+ by the semantic model), `llm` (SQL the model wrote), `verified_sql` (a Verified Query), or
270
+ `vendor`. Also look at `analytics_validation_failed`, the checks that bounced the agent. If
271
+ the project exports traces to Oxy's observability, the run's trace shows the same steps.
272
+
273
+ ### 3. Classify the root cause before editing anything
274
+
275
+ | Category | Typical evidence | Fix lives in |
276
+ | --- | --- | --- |
277
+ | A. Semantic model gap: a grouping or dimension is missing | many queries, raw rows, the agent aggregates in prose | a dimension on the view |
278
+ | B. Missing or unfindable measure | `source: llm` where a measure should have answered, or arithmetic done in the answer | a measure, or its name and description |
279
+ | C. Instructions vague or misdirecting | wrong interpretation, varying definitions across runs | `instructions:` or a `.md` context doc |
280
+ | D. Canonical answer exists as SQL | the same question keeps producing different SQL | a Verified Query |
281
+ | E. Validation rejects a correct result | `analytics_validation_failed` loops, `max iterations exceeded` | `validation:` |
282
+ | F. Judge false negative | `cot` agrees with the expected answer, `choice` is FAIL | report it. Don't touch the agent |
283
+ | G. Prompt underspecified | clarifying-question errors, different readings across runs | report it, and propose a sharper prompt |
284
+ | H. Data unavailable | no view or table holds it | report it. Don't fake support |
285
+
286
+ Symptoms, evidence, and a worked fix for each:
287
+ [references/diagnosis.md](references/diagnosis.md).
288
+
289
+ ### 4. Repair: levers in an `.agentic.yml` agent, in this order
290
+
291
+ 1. **Semantic model** (`.view.yml` / `.topic.yml` matched by the agent's `context:` globs):
292
+ add the missing measure or dimension, or fix an aggregation or an entity. The agent's
293
+ catalog search matches a measure's **name**, its **description** and its **view name**. It
294
+ doesn't read `synonyms:`, so the words users ask with belong in the description. A view
295
+ that no `context:` glob matches doesn't exist as far as the agent is concerned.
296
+ 2. **Context** (`context:` globs, relative to the agent file):
297
+ - **`.md` docs** go into the prompts for clarifying the question and writing the answer.
298
+ Use them for business definitions: the fiscal calendar, what "active customer" means,
299
+ which of two similar measures to use.
300
+ - **Verified Queries**: a `.sql` file with a `/* oxy: … */` comment block at the top that
301
+ carries a `description:`. When the agent's search matches its file name or description,
302
+ the SQL runs verbatim, with no SQL generation. A `.sql` file **without** that block is
303
+ only a style example for SQL generation, and is never run as an answer.
304
+ 3. **Instructions:** top-level `instructions:` reaches every LLM call. `states.<state>.
305
+ instructions:` reaches one stage: `clarifying`, `specifying`, `solving` or `interpreting`.
306
+ Write general rules ("revenue means net of refunds", "rank by units unless asked
307
+ otherwise"). Never put the expected answer, or a number from it, into instructions.
308
+ 4. **Validation:** the `validation:` block **replaces** the 15 default rules rather than
309
+ adjusting them. To switch off or retune one rule, list every rule that should still run.
310
+ An empty `validation: {}` disables them all.
311
+
312
+ Make the smallest fix that generalizes. If the same logical gap exists in several views, fix
313
+ all of them. Preserve the file's existing style. Explain the root cause alongside the edit.
314
+
315
+ ### 5. Validate
316
+
317
+ There's no build step. `oxy test` reloads the agent file, its context and its semantic files
318
+ from disk on every run, and `oxy build` (embeddings and integration sync) plays no part. Check
319
+ the files you edited, then re-run:
320
+
321
+ ```bash
322
+ oxy validate agents/sales.agentic.yml
323
+ oxy validate semantics/views/orders.view.yml
324
+ oxy test <file> --case <name> --output-json
325
+ ```
326
+
327
+ **One pass isn't proof.** A case needs `runs`/`runs` on at least two rounds. Then run the
328
+ whole file to catch regressions in other cases. If the repair doesn't hold after three
329
+ diagnose-edit-validate cycles, stop and report what you know.
330
+
331
+ ### 6. Report
332
+
333
+ ```text
334
+ ## Repair summary
335
+ Case: tests/sales.performance.test.yml › top-items-feb-2026
336
+ Root cause: B — the measure existed, but its description never said "engagements"
337
+ Changed: semantics/views/posts.view.yml (description of total_engagements)
338
+ Validation: round 1 3/3 · round 2 3/3 · full file 9/9 cases
339
+ Notes: <judge observations, remaining risks, anything left for the user>
340
+ ```
341
+
342
+ ## Never
343
+
344
+ - Edit `expected`, delete or skip a case, or reword a prompt to make a failure go away.
345
+ - Encode an observed answer you haven't checked, or a wrong one because it was consistent.
346
+ - Fabricate an expected value. A narrowed `DRAFT:` note is honest, an invented number isn't.
347
+ - Hard-code an answer, or a threshold derived from the test data, into instructions or a view.
348
+ - Treat a judge false negative as an agent bug, or a clarifying-question error as a wrong
349
+ answer.
350
+ - Stop at one lucky pass, or make "while I'm here" edits to unrelated files.
351
+ - Explain a root cause you didn't find. "Unclear, needs investigation" is a valid finding.
352
+
353
+ ## Read next
354
+
355
+ - [references/writing-expected.md](references/writing-expected.md): worked `expected` examples
356
+ by question type, and the evidence rules.
357
+ - [references/results-json.md](references/results-json.md): the results schema, `jq`
358
+ recipes, and how the judge's verdict is parsed.
359
+ - [references/diagnosis.md](references/diagnosis.md): the failure taxonomy, with a repair
360
+ pattern for each.
361
+ - The `oxy-semantic-modeling` skill, for view, measure and entity craft when the repair is in
362
+ the semantic model.
@@ -0,0 +1,243 @@
1
+ # Diagnosing and repairing a failing eval
2
+
3
+ Diagnose before editing. For every failing or flaky case, collect:
4
+
5
+ 1. Every run's `actual_output`, `cot` and `choice` from the results JSON
6
+ ([results-json.md](results-json.md)).
7
+ 2. The route the agent took, from `oxy agentic run <agent> -q "<prompt>" --json`. Run it two
8
+ or three times if the case is flaky. Read `query_executed.source` and `query`, and any
9
+ `analytics_validation_failed` events.
10
+ 3. The files the agent actually loads: the `context:` globs in its `.agentic.yml`, resolved
11
+ relative to that file's directory. A view, doc or SQL file outside those globs doesn't
12
+ exist for this agent.
13
+
14
+ Then name one category below. If two apply, fix the earlier one first. Most failures are
15
+ **A** or **B**.
16
+
17
+ ## A. The semantic model lacks a grouping or dimension
18
+
19
+ **Evidence:** several queries per run, `source: llm` with long hand-written SQL, raw
20
+ row-level results, the answer doing arithmetic or bucketing in prose, and runs that disagree
21
+ because each one improvises the grouping differently.
22
+
23
+ **Fix:** add a dimension that names the grouping, so one semantic query returns a small,
24
+ finished result.
25
+
26
+ ```yaml
27
+ dimensions:
28
+ - name: order_size_band
29
+ type: string
30
+ description: >-
31
+ Order size band: Small (under $50), Medium ($50–$200), Large (over $200). Use to compare
32
+ small, medium and large orders or baskets.
33
+ expr: |
34
+ CASE
35
+ WHEN order_total < 50 THEN 'Small'
36
+ WHEN order_total <= 200 THEN 'Medium'
37
+ ELSE 'Large'
38
+ END
39
+ samples: ["Small", "Medium", "Large"]
40
+ ```
41
+
42
+ The thresholds must be **business definitions**, not numbers read off the current data. A
43
+ cut-off derived from today's average drifts as the data moves, and a cut-off derived from the
44
+ test's expected answer is hard-coding the answer. If the same grouping makes sense in several
45
+ views, add it to each of them.
46
+
47
+ ## B. A measure is missing, wrong, or can't be found
48
+
49
+ **Missing.** The answer computes a ratio, rate or average itself, and the arithmetic varies
50
+ across runs. Add the measure:
51
+
52
+ ```yaml
53
+ measures:
54
+ - name: avg_order_value
55
+ type: number # `number`: the expression already aggregates
56
+ description: >-
57
+ Average order value (AOV): net revenue per order. Also called average basket or
58
+ average ticket.
59
+ expr: "SUM(net_revenue) / NULLIF(COUNT(DISTINCT order_id), 0)"
60
+ ```
61
+
62
+ **Wrong.** The runs agree on a number that is consistently wrong. Check the measure's
63
+ aggregation (`sum` where `count_distinct` was meant), a join that fans out and double-counts,
64
+ the wrong date column (record-created rather than order date), and the measure's `filters:`.
65
+ Fix the definition, not the answer.
66
+
67
+ **Can't be found.** The measure exists but the agent never uses it (`source: llm`, or "I
68
+ don't have that data"). The analytics agent's catalog search matches a measure's `name`, its
69
+ `description` and its view's name, and **does not read `synonyms:`**. Put the user's words
70
+ (the ones in the failing prompt) into the measure's `description`.
71
+
72
+ **A dimension that exists but never gets used.** Check three things: its view is matched by
73
+ `context:`; the views are linked by entities (a `primary` entity on one, a `foreign` entity
74
+ with the same key on the other); and its `description` says what it is in the user's terms.
75
+ For view, measure and entity craft, see the `oxy-semantic-modeling` skill.
76
+
77
+ **Two similar members, chosen differently on each run** (for example `created_date` against
78
+ `order_date`). Make the descriptions decisive:
79
+
80
+ ```yaml
81
+ - name: order_date
82
+ type: date
83
+ description: Date the order was placed. Use for sales, revenue and order trends by date.
84
+ - name: created_date
85
+ type: date
86
+ description: When the database row was written. System metadata, not for business questions.
87
+ ```
88
+
89
+ ## C. Instructions are vague, missing, or misdirect
90
+
91
+ **Evidence:** the agent picks a different reasonable definition on each run (gross or net,
92
+ calendar or fiscal), formats or scopes the answer inconsistently, or ignores a business
93
+ convention.
94
+
95
+ **Fix, by where the knowledge belongs:**
96
+
97
+ - **A definition or a business fact** goes in a `.md` doc matched by `context:`. Docs are
98
+ injected into the prompts that clarify the question and write the answer.
99
+ - **A rule for every step** goes in top-level `instructions:`.
100
+ - **A rule for one stage** goes in `states.<state>.instructions`, for one of `clarifying`
101
+ (reading the question), `specifying` (choosing measures and filters), `solving` (writing
102
+ SQL) or `interpreting` (writing the answer).
103
+
104
+ ```yaml
105
+ instructions: |
106
+ "Revenue" means net revenue (after refunds and discounts) unless the user says gross.
107
+ states:
108
+ clarifying:
109
+ instructions: |
110
+ Read "last quarter" as the most recent completed fiscal quarter; the fiscal year starts
111
+ on 1 February.
112
+ interpreting:
113
+ instructions: State the period and every filter applied in the answer's first sentence.
114
+
115
+ context:
116
+ - ../semantics/**/*.view.yml
117
+ - ../semantics/**/*.topic.yml
118
+ - ./docs/*.md
119
+ ```
120
+
121
+ Keep instructions general and reusable. A sentence that only makes sense for one test case
122
+ ("the answer to the Q4 question is $1.2M") is hard-coding the test. It's forbidden, and it
123
+ fails the next question anyway.
124
+
125
+ ## D. One canonical query should answer the question every time
126
+
127
+ **Evidence:** an important, frequently asked question whose correct SQL is known and fixed,
128
+ but the agent writes different SQL each run.
129
+
130
+ **Fix:** a Verified Query, a `.sql` file matched by `context:` that carries an `oxy:` comment
131
+ block:
132
+
133
+ ```sql
134
+ /*
135
+ oxy:
136
+ description: "Current active headcount by department"
137
+ database: warehouse # optional: a database name from config.yml
138
+ */
139
+ SELECT department, COUNT(*) AS active_headcount
140
+ FROM hr.employees
141
+ WHERE status = 'active'
142
+ GROUP BY department
143
+ ```
144
+
145
+ When the agent's search matches the file's name or `description`, the SQL runs **verbatim**.
146
+ Nothing is substituted into it, so it only fits questions whose answer is exactly that query.
147
+ It's no use for "…in a given month". A `.sql` file without the `oxy:` block is only a style
148
+ example for the SQL the model writes.
149
+
150
+ ## E. Validation rejects a correct result
151
+
152
+ **Evidence:** `analytics_validation_failed` events repeating on a result that was right, runs
153
+ that end in `max iterations exceeded`, or answers that drift away from the right one after a
154
+ retry. Common causes: `non_empty`, when "none" is a correct answer; `outlier_detection` on
155
+ naturally spiky data; and `freshness_check` on a source that loads late.
156
+
157
+ **Fix:** retune the rule. The `validation:` block **replaces** the default set, so list every
158
+ rule that should still run. A stage you leave out isn't checked at all, and a misspelled rule
159
+ name stops the agent from loading. The 15 defaults:
160
+
161
+ ```yaml
162
+ validation:
163
+ rules:
164
+ specified:
165
+ - name: metric_resolves
166
+ - name: join_key_exists
167
+ - name: filter_unambiguous
168
+ solvable:
169
+ - name: sql_syntax
170
+ - name: tables_exist_in_catalog
171
+ - name: spec_tables_present
172
+ - name: column_refs_valid
173
+ - name: timeseries_order_by_check
174
+ solved:
175
+ - name: non_empty
176
+ enabled: false # "none" is a valid answer for this agent
177
+ - name: truncation_warning
178
+ - name: no_nan_inf
179
+ - name: outlier_detection
180
+ threshold_sigma: 4.0 # looser than the default for spiky daily data
181
+ min_rows: 12
182
+ - name: null_ratio_check
183
+ - name: duplicate_row_check
184
+ - name: freshness_check
185
+ ```
186
+
187
+ Loosen only the rule the evidence names. Validation is what stops wrong answers. Don't switch
188
+ it off to make a test pass.
189
+
190
+ ## F. Judge false negative (not an agent problem)
191
+
192
+ **Evidence:** the answer is correct when you read it, the `cot` reasoning says so, and
193
+ `choice` is still `FAIL`. Usually the verdict line is missing from the last five lines of the
194
+ judge's response, or it reads `PASS or FAIL`, which parses as FAIL.
195
+
196
+ **Fix:** don't touch the agent. Report it with the `cot` excerpt. Recommend a stronger
197
+ `judge_model`: a model that already exists in `config.yml`, preferably from a different family
198
+ than the agent's. Changing the test's settings is the user's call.
199
+
200
+ A false **positive** (an answer that is wrong but PASSed) is the dangerous one. It usually
201
+ means `expected` is too vague to contradict anything. Report it, and propose a sharper
202
+ `expected` to the user. Don't change it yourself.
203
+
204
+ ## G. The prompt is underspecified
205
+
206
+ **Evidence:** runs that end in `agentic pipeline asked a clarifying question: …`, or runs
207
+ that answer different reasonable readings of the prompt.
208
+
209
+ **Fix:** report the readings you saw. The test owner chooses between two remedies:
210
+
211
+ - **Sharpen the prompt.** This is a change to the test, so the user decides.
212
+ - **Teach the agent a default reading**, through instructions or a doc, when the business
213
+ wants one (for example, "when no region is named, report all regions"). That's a legitimate
214
+ agent repair.
215
+
216
+ ## H. The data isn't there
217
+
218
+ **Evidence:** no view or table holds the needed facts, and every run declines or answers
219
+ around it.
220
+
221
+ **Fix:** say so plainly, and name the missing source. Don't add semantic or instruction hacks
222
+ that make the agent sound as if it can answer.
223
+
224
+ ## Also check
225
+
226
+ - **Dates off by a day, or the wrong month.** The agent resolves "today" and relative dates
227
+ in its `timezone:` (UTC when unset). Set it to the business's zone.
228
+ - **The right number for the wrong period.** The prompt is relative ("last month") and
229
+ `expected` was written for an earlier month. Report it: the test needs an absolute period.
230
+ - **Flaky runs with different `source` values.** Some runs take the semantic model and others
231
+ write SQL. Make the semantic path win: make the measure findable (B), make the dimensions
232
+ reachable (B), or add a Verified Query (D).
233
+
234
+ ## Order of repair
235
+
236
+ 1. Semantic model: measures, dimensions, entities, descriptions.
237
+ 2. Context: `.md` definitions, Verified Queries, and the `context:` globs themselves.
238
+ 3. Instructions: global first, then per-state.
239
+ 4. Validation tuning.
240
+
241
+ After each change, `oxy validate <edited file>`, then re-run the case at least twice
242
+ (`oxy test <file> --case <name> --output-json`), then the whole file. Nothing needs to be
243
+ rebuilt between edits. Stop after three cycles that don't hold, and report.