Test-Suite and Dataset Management
AgenticLens 0.8.0 adds agenticlens.suites and the agenticlens suite
command group (roadmap milestone v0.6). Use them to build regression suites
from hand-written cases, production traces, and user feedback. You can then
review, mask, de-duplicate, vary, and split the cases, and approve the suite
as an immutable version that CI can verify and run.
Suites stay plain agentic-evals TestSuite files the whole way through, so a
draft or an exported version runs with agenticlens evaluate,
evaluate-live, and experiment run unchanged.
Lifecycle
CSV / JSON ──► import ─┐
production traces ─────┼─► draft suite ─► mask ─► dedupe ─► vary ─► split ─► review
user feedback ─────────┘ ▲ │
└──────── edit (add, remove, tag) ◄────────────┘
│
approve (checks) ─► store/<name>/<version>.json
│
CI: verify ─► export ─► evaluate ─► gate ◄───┘
| Stage | CLI | Python |
|---|---|---|
| Import CSV/JSON/JSONL/YAML | suite import |
import_cases, import_suite |
| Convert traces | suite from-traces |
case_from_run, cases_from_runs |
| Convert feedback | suite from-feedback |
case_from_feedback, cases_from_feedback |
| Edit cases | suite add-case, remove-case, tag |
add_case, update_case, remove_cases, tag_cases |
| Review | suite review |
review_cases |
| Mask PII | suite mask [--check] |
PIIMasker, mask_suite, scan_suite |
| Duplicates | suite dedupe [--drop] |
find_duplicates, drop_duplicates |
| Synthetic variations | suite vary |
generate_variations, vary_case |
| Splits | suite split |
assign_splits |
| Filter / summarize / diff | suite filter, summary, diff |
filter_suite, summarize_suite, diff_suites |
| Approve and store | suite approve |
SuiteStore.approve |
| CI | suite verify, suite export, suite versions |
SuiteStore.verify, load_suite, versions |
Editing commands rewrite the suite file in place unless --save names
another file. YAML is written for .yaml/.yml paths and JSON otherwise.
Case metadata
AgenticLens records management state in each case's metadata:
| Key | Meaning |
|---|---|
source |
Lineage: kind (imported, trace, feedback, synthetic; absent means authored), plus file/row, trace_id/run_id, feedback_id, or parent_case_id/variation/seed |
review_status |
needs_review, accepted, or rejected. A case without a status counts as accepted (authored by a person) |
reviewed_by, review_notes |
Who reviewed the case and why |
domain |
Domain category, e.g. billing |
split |
train, validation, or test |
Tags stay in the case's tags list.
Importing cases
agenticlens suite import cases.csv --name support-regression --version 1.0.0 \
--save suite.yaml
CSV columns map onto TestCase fields. List columns (expected_contains,
required_tools, forbidden_tools, required_tool_order, tags) are
|-separated. input is parsed as JSON when it is a JSON object or array.
domain, split, and any metadata.<key> column go to case metadata.
Unknown columns are rejected, so a misspelled expectation fails the import
instead of being dropped silently. JSON imports accept a list, {"cases": [...]},
or JSON Lines. Add --into suite.yaml to append to an existing suite.
PII is masked by default before anything is written (--no-mask to opt out).
From production traces
agenticlens suite from-traces traces/ --name prod-regression --version 1.0.0 --save suite.yaml
A successful run becomes a case that expects what the run did:
required_toolsandrequired_tool_orderfrom its successful tool callsmax_latency_msandmax_cost_usdset to the observed values times--latency-margin/--cost-margin(default 1.5)expected_outputonly with--include-output, because exact model output rarely repeats
The input comes from metadata.input or the first span with input_data, and
the domain from task_type. Each converted case is marked needs_review, and
cases that can't be converted are reported with a reason:
- Failed runs show what went wrong, not what right looks like. Turn them into cases with corrected feedback instead.
- Runs without a recorded input have nothing to replay.
Record the request when tracing, either as run metadata
(trace("support", input=question)) or on a span
(span.record_io(input_data=question)), so there is an input to replay. The
tracer's redactor still applies.
From user feedback
{"feedback": [
{"feedback_id": "fb-101", "input": "Where is my parcel?", "output": "It ships in 2 business days.", "rating": "positive"},
{"feedback_id": "fb-102", "input": "Can I pay by invoice?", "output": "No.", "rating": "negative",
"corrected_output": "Let me connect you with a specialist."}
]}
agenticlens suite from-feedback feedback.json --into suite.yaml --save suite.yaml --traces traces/
If feedback has a correction, the case expects the correction. Positive
feedback (rating: positive, or a score of 0.5 or more) expects the
confirmed output. Negative feedback without a correction is skipped, because it
says what's wrong but not what's right. When a record has no input, the input
comes from the run named by run_id or trace_id in --traces. The rejected
output is kept in metadata.rejected_output for reviewers. The schema is
schemas/suites/feedback-batch.schema.json.
PII masking
agenticlens suite mask suite.yaml # mask in place
agenticlens suite mask suite.yaml --check # scan only; exit 7 if PII found
agenticlens suite mask suite.yaml --strategy hash --salt "$SALT" --report mask.json
What gets masked:
- Patterns in text: emails, phone numbers, Luhn-valid card numbers, US SSNs, IPv4 addresses, bearer tokens, and API keys.
- Values under sensitive keys (
password,token,email,phone,ssn,address,date_of_birth, ...), matched as whole words of the key.customer_emailis masked;input_tokensis not. - Fields covered: a case's input, expected output, expected substrings, expected tool arguments, and metadata. Case lineage is never masked.
redact writes [EMAIL]. hash writes [EMAIL:3f2a9c1e], a salted SHA-256
prefix, so equal values stay equal and you can still spot duplicates. Reports
list the case, the field path, and the PII type, never the value.
Pattern matching is a safeguard, not a guarantee. It doesn't detect names,
street addresses in free text, or locale-specific identifiers. A version
string such as 1.2.3.4 is masked as an IP address. Masking an expected
output also changes what the agent has to produce, so review those cases.
Duplicates, variations, and splits
agenticlens suite dedupe suite.yaml --threshold 0.85 --report dupes.json --drop
agenticlens suite vary suite.yaml --per-case 2 --kind typo --kind prefix --seed 7
agenticlens suite split suite.yaml --train 0.7 --validation 0.15 --test 0.15 --seed 7
Duplicates. Exact duplicates share a normalised input (case,
punctuation, and whitespace ignored). Near duplicates reach the Jaccard
threshold over word unigrams and bigrams. A group is conflicting when
cases with the same normalised input expect different results, which usually
means a labelling error. Near duplicates with distinct inputs never conflict. --drop keeps the first case of each group. Synthetic
variants of the same original are not compared to each other unless you pass
--include-variants. Comparison is pairwise, so it is sized for suites of
thousands of cases.
Variations. lowercase, uppercase, whitespace, punctuation,
typo, and prefix change every string in the input, skipping masked
values. They test robustness to how a request is typed, not what it means.
They are not paraphrases and make no model calls. Each variant:
- keeps its parent's expectations
- inherits its parent's split
- is marked
needs_review - records
parent_case_id,variation, andseed
--per-case caps variants per original, so re-running adds nothing new.
Splits. A case is placed by hashing its lineage with the seed. A case keeps its split when other cases are added or removed, so a later version can't move test cases into training. A variant always lands with its original. The trade-off is that split sizes only approximate the ratios, and the CLI warns when a split is empty.
Review and approval
agenticlens suite review suite.yaml --status accepted --reviewer qa-lead --case trace-run-1
agenticlens suite review suite.yaml --status rejected --reviewer qa-lead --case fb-7 --notes "policy changed"
agenticlens suite approve suite.yaml --store suites/ --approver qa-lead --notes "Q4 regression set"
approve runs three checks, records them, and refuses (exit 6) when any
fails:
| Check | Fails when | Override |
|---|---|---|
review |
any case is rejected (remove it), or needs_review |
--allow-unreviewed (needs_review only) |
pii |
the scan finds unmasked PII | --allow-pii |
conflicting_duplicates |
cases share an input but expect different results | none; fix the labels |
The approved version is written once to <store>/<name>/<version>.json as a
SuiteVersion. It holds the suite exactly as approved, its SHA-256 over
canonical JSON, the case count, the approver, the time, the notes, the
checks, and an optional --parent-version. The schema is
schemas/suites/suite-version.schema.json.
Approved versions are immutable:
- Approving the same name and version again is a no-op if the content is identical, and is refused if it differs. Bump the version instead.
- Every read re-hashes both the suite (
content_sha256) and the whole envelope (record_sha256, covering the approver, time, notes, checks, case count, and parent version). A hand-edited, moved, or corrupted version failsverify,export, andload_suite. - Suite names and versions must be plain path segments (letters, digits,
.,_,-).
Use suite diff support@1.0.0 suite.yaml --store suites/ to review what a
new draft changes before approving it.
Running approved suites in CI
- run: uv run agenticlens suite verify --store suites
- run: uv run agenticlens suite export support-regression@1.0.0 --store suites --save build/suite.json --split test
- run: uv run agenticlens evaluate build/suite.json build/samples.json --save build/evaluation.json --strict-evidence
- run: uv run agenticlens gate build/evaluation.json
verify exits 6 when any stored version is missing or changed. export
writes a plain suite, optionally filtered by --tag, --domain, or
--split. It stamps metadata.agenticlens_suite_version with the name,
version, hash, approver, and approval time, so the evaluation report traces
back to the approved version. For live agents, use evaluate-live in place
of evaluate. This repository's CI runs exactly these steps against
examples/suites/store.
Example
examples/suite_management_demo.py
runs the whole lifecycle offline. Its inputs are a CSV with PII and a
duplicate row, three production traces (one failed), and four feedback
records (two unconvertible). It produces 15 approved cases in
examples/suites/store/support-regression/1.0.0.json and evaluates them
against a deterministic stub agent. tests/test_examples_suites.py checks
that the store and artifacts reproduce byte for byte, apart from the
evaluation report's created_at.
uv run python examples/suite_management_demo.py
Relationship to evaluation datasets
agenticlens dataset ... (since 0.5.0) manages labelled outputs and traces
for judge calibration. agenticlens suite ... manages the test cases
(inputs and expectations) those outputs are scored against. Both are
versioned. Only approved suite versions are immutable and hash-verified.