# AI Evals Authoring Guide This folder contains black-box benchmark cases for: - `flow` - `app` - `script` - `cli` - `global` The goal is to test the current production prompts and guidance with realistic user requests, not to test one exact implementation shape. ## Core rules 1. Write prompts like a real user request. 2. Prefer behavior, inputs, constraints, and outcomes over internal implementation details. 3. Keep deterministic validation narrow and hard. 4. Put semantic expectations in `judgeChecklist`. 5. Use `expected` fixtures only when exact structure really matters. ## Prompt writing Prompts should sound like something a user would naturally ask. Good: - "Create a flow that routes support requests based on customer tier." - "Add a reset button that sets the counter back to 0." - "Create a flow that reuses the existing greeting script instead of duplicating the logic." Bad: - "Use `branchone` with 3 branches and a default branch." - "Create a `rawscript` step with this exact topology." - "This is a benchmark harness." Do not write prompts as if the user knows Windmill internals unless the case is explicitly testing a power-user workflow. ## Flow-specific rules This is the main principle you asked for: - flow prompts should read like requests from a user who does not know the product internals - the user should ask for behavior, not for `branchone`, `branchall`, `rawscript`, `preprocessor_module`, `failure_module`, exact graph topology, or other internal constructs That means: - creation cases should describe the business behavior and expected result - modification cases may mention existing step names, because the user can see the current flow - only mention special Windmill constructs when the case is explicitly about those constructs Examples: - acceptable creation prompt: "Create a purchase approval flow that pauses for approval and asks the approver for a comment." - avoid: "Create a suspend step with one required event and a resume form." For flow cases, do not fail a case just because the model chose a different valid topology. ## App-specific rules App prompts should focus on user-visible behavior: - what the UI should let the user do - what should persist - what backend behavior is needed Avoid prompting in terms of React structure, component names, or implementation unless the case is specifically about editing an existing app. ## CLI-specific rules CLI prompts can be more explicit about paths and file names because real CLI users often do specify them. Still, avoid benchmark phrasing. The prompt should read like a repo task, not a harness instruction. When relevant, ask the assistant to tell the user which `wmill` commands to run next. That is part of the benchmarked behavior. ## Global-specific rules Global prompts should exercise workspace-level drafting behavior: - inspecting existing scripts, flows, apps, schedules, triggers, resources, and variables when relevant - writing AI drafts rather than saving or deploying by default - producing coherent multi-artifact changes when the request crosses artifact boundaries Keep deterministic validation focused on the draft contract: required draft type/path, required content snippets, forbidden draft paths, and forbidden mutating tools such as deploy/delete unless the case explicitly asks for them. Datatable cases should set `skipJudge: true` and validate through tool-use (`requiredToolsUsed` / `forbiddenToolsUsed`) and SQL-argument assertions (`toolCallArgs` with `stringIncludesAnyOf`, e.g. `['select']`, `['create table']`, `['update', 'insert into']`). Two reasons the judge is unreliable here: - `list_datatables`, `get_datatable_table_schema`, and `exec_datatable_sql` produce no drafts, and the global judge only sees the drafts artifact — it scores a no-draft conversational answer as empty (same as the `askUserQuestion` cases). - Even a case that *does* produce a draft (a script reading the data table via `wmill.datatable()` at runtime) is mis-judged: the judge has no datatable SDK reference and penalizes correct `wmill.datatable()` usage as wrong. Verify the SDK call deterministically instead — `requiredDrafts.valueIncludes: ['wmill.datatable(']` plus forbidding `exec_datatable_sql` (keeping chat-time SQL distinct from runtime SDK use). `stringIncludesAnyOf` is existential over calls (at least one matching call), so a mutation case still passes when the model mixes its UPDATE/INSERT with verification SELECTs. The in-memory engine (`datatableSqlEngine.ts`) is stateful within a case — writes persist, so a model that re-queries to verify its CREATE/UPDATE sees the change and does not loop. But the engine is best-effort (SELECT returns all rows of the referenced/first table with no WHERE/projection), so still never assert specific returned row values. Seed data via `workspace.datatables` in the `initial` fixture (see README). ## Deterministic validation Use deterministic validation only for hard failures such as: - missing required files - unexpected extra files when the prompt says not to create them - syntax errors - unresolved flow refs - missing required special modules or suspend config - obvious artifact corruption Do not use deterministic validation to enforce one preferred implementation for broad creation tasks. Examples of bad hard checks: - exact step topology for a creation flow - exact branch structure when the prompt only asked for routing behavior - exact input shape when multiple reasonable shapes are acceptable ## Judge checklist Every non-trivial case should have a `judgeChecklist`. The checklist should capture: - the user-visible behavior that must be present - important constraints - key completion criteria The checklist should not duplicate low-level implementation details unless they are truly required by the task. Good checklist items: - "the flow calculates the order total with 8% tax" - "the app persists recipes appropriately for a raw Windmill app" - "the flow reuses the existing workspace script instead of rewriting the logic" Bad checklist items: - "uses `branchone`" - "contains a `rawscript` node" ## When to use `expected` Use `expected` fixtures when the case is structure-sensitive, for example: - exact file creation - exact script content - modification cases where a specific file must change in a specific way - cases where preserving an existing structure is part of the requirement Do not use a full `expected` artifact as the semantic oracle for broad creation tasks when multiple valid outputs should pass. ## When to use `initial` Use `initial` when the benchmark is about: - editing an existing artifact - reusing existing workspace assets - preserving existing behavior while adding a change If the case is greenfield, prefer no `initial`. ## Case design ladder Prefer suites that get gradually harder: 1. trivial create case 2. realistic create case 3. reuse-existing-assets case 4. modification case 5. refactor case 6. edge-case or niche product behavior The last cases in a suite should cover unusual or product-specific behavior. ## Anti-patterns Avoid these: - benchmark framing in prompts - over-specified internal topology for creation tasks - judge checklists that just restate implementation details - deterministic validation that encodes one preferred solution - fixtures that are so minimal or brittle that they create false negatives ## Before adding a case Ask: 1. Would a real user plausibly write this prompt? 2. If the model solves it in a different valid way, would the case still pass? 3. Are the hard deterministic checks only catching objectively broken output? 4. Does the `judgeChecklist` describe the real success criteria? 5. If this case fails, will the reason be understandable from the saved artifacts?