← All skills
IVYXSTUDIO · SKILL
E

Eval suite authoring

eval-suite-authoring · v1.0.0

ivyx

Use when someone wants to test, evaluate or regression-check their prompt, instruction class or assistant answers (eval, test seti, golden set, değerlendirme) with cases and a pass bar; writes the cases and the suite, binds an engine and runs it once.

Who is asked

Risk medium: you are asked when it loads, and that yes covers reading its files in the same chat.

Tools it may call

While this skill is active, a call to any other tool is refused. These still meet every policy rule; the list narrows, it approves nothing.

evals.enginesfs.listfs.readFilefs.writeFileevals.suite.run

What it tells the agent

The published SKILL.md, as written.

---
name: eval-suite-authoring
description: Use when someone wants to test, evaluate or regression-check their prompt, instruction class or assistant answers (eval, test seti, golden set, değerlendirme) with cases and a pass bar; writes the cases and the suite, binds an engine and runs it once.
allowed-tools: evals.engines fs.list fs.readFile fs.writeFile evals.suite.run
metadata:
  risk: medium
---

Writing an eval suite

A suite is a jsonl file of cases and a block in .punica/evals.yaml that names a target, the dataset, an engine and thresholds. A suite with no engine is silently dropped by the hub, so the engine is the first thing you check, not the last.

1. Which engines exist

evals.engines first, with no arguments. Read engines: for answers that are text (a hint, a subject line, a JSON verdict) the engine is text; for a node or agent manifest it is manifest; for retrieval it is rag. If catalogReadable is false or the engine you need is absent, say so and stop: a suite cannot be bound to an engine that is not installed.

2. Read what exists

fs.readFile on .punica/evals.yaml. If it exists, you add a suite to it and keep every other suite as it is; if it does not, you create it.

Then read the target itself. A workspace instruction class <id> lives at .punica/instruction-classes/<id>.ic.yaml; a prompt lives where the person said. Read its system prompt: it says what the target is given (a policy text, a document, a record) and what it must never do. When it names material the target answers from, find that material in the project: fs.list with path: "." shows the top of the project, and the policy, document or record files are in the folder whose name says so (docs, policies, content); fs.list that folder, then read each file in it with fs.readFile. Never guess a path and never write the material yourself: the cases come from those files, not from what an assistant of this kind usually says, and if the project holds no such file, say so and ask for it before writing a case.

3. Write the cases

Read the exact shape from the file suite-template.md of this skill (ask for it with { "file": "suite-template.md" }), then write .punica/evals/<suite-id>.jsonl with fs.writeFile: one JSON object per line, four to eight cases, each with an id, an input (the whole prompt the target gets, as text: when the target is given a policy or a document and a question, the input holds that text and the question, in the order the system prompt describes) and an expect object holding only checks the engine can run (contains, notContains, matches, maxLines, maxChars, requiredKeys) and a note saying what a person would look for. An expectation is a fact the material states (a number of days, a price, a city, a word the target must not use), so a wrong answer fails and a vague one does not pass; at least one case asks something the material does not answer and expects the target to say so. Cases come from the person's examples when they gave any; otherwise write them from the material and say they are theirs to edit.

4. Write the suite

fs.writeFile .punica/evals.yaml with the block from the template: id, target (kind and id), dataset, samples: 1, engines with the engine id from step 1, thresholds with expect.satisfied at the bar the person wants (1.0 unless they said otherwise), onFailure: stop. Keep version: 1 and baseline at the top level.

5. Run it once

evals.suite.run with suite set to the new id. Read pass, reason and report: how many cases ran, the expect.satisfied mean, which cases failed and on which check. A run that invokes a local model takes minutes; wait for it.

6. Report

  • The files written, with the case count and the engine.
  • The run: pass or fail, the mean against the bar, each failing case by id with its first failing check.
  • What to do with a failing case: fix the target, or change the case when the expectation was wrong; never lower the bar to make the run green.
  • Answer in the language the person wrote in.

Files it reads

Loading the skill lists these to the agent. It reads one when the instructions send it there.

suite-template.md
# Suite and case shapes

## `.punica/evals.yaml`

```yaml
# Eval suites. Each suite runs a target over a jsonl dataset, scores the
# output with the listed engines, and must clear its thresholds.
version: 1

suites:
  - id: <suite-id>                 # lower-case, dashes
    target:
      kind: instructionClass       # or capability
      id: <the instruction class or capability id>
    dataset: .punica/evals/<suite-id>.jsonl
    samples: 1                     # repeats per case; more costs more model calls
    engines:
      - id: text                   # from evals.engines; options are optional
        options:
          json: false              # true when the answer must be JSON
    thresholds:
      expect.satisfied: 1.0        # fraction of declared checks that must hold
    onFailure: stop

baseline: .punica/evals/baseline.json
```

Add a second suite as another `- id:` entry under `suites:`; never remove
one that is there.

## `.punica/evals/<suite-id>.jsonl`

One object per line. `input` is the whole prompt the target receives.
`expect` holds checks and a `note`; a prose `expect` declares nothing and
sends the case to a person.

```json
{"id": "short-answer", "input": "…the prompt text…", "expect": {"maxLines": 3, "notContains": ["```"], "note": "one short answer, no code fence"}}
{"id": "names-the-file", "input": "…", "expect": {"contains": ["README"], "maxChars": 200, "note": "mentions the file it is about"}}
{"id": "json-verdict", "input": "…", "expect": {"requiredKeys": ["verdict", "reason"], "note": "a JSON object with both keys"}}
```

Checks the `text` engine runs: `contains`, `notContains`, `matches`,
`notMatches`, `minChars`, `maxChars`, `maxLines`, `requiredKeys`.
`expect.satisfied` is the share of these that held across the run.