merit_* cases, injects dependencies by parameter name (like pytest fixtures), runs them, and reports results.
This page focuses on how to write merits and (importantly) where the behavior lives in the codebase, so you can trust what’s happening.
TLDR - If you know pytest, you already know 80%
Merits
Merit follows pytest-style discovery patterns to find merit functions in your codebase:Related docs: Merit (Concept), Testing (API)
merit_:
merit_chatbot.py✓merit_agent.py✓helpers.py✗
merit_:
Merit are discovered, and their merit_* methods become merit cases:
Modifiers
Modifiers are decorators that change how a merit is collected and/or executed. Some modifiers expand a single merit into many cases (like parametrization or dataset iteration), while others change execution semantics (like repeating a case, or marking it as skipped/xfail). Apply them tomerit_* functions or Merit* classes.
Related docs: Testing (API), Case (Concept)
@merit.parametrize(names, values) - Run the same merit with different inputs
@merit.iter_cases(*cases, min_passes=len(cases)) - Iterate over Case objects from external sources, optionally allowing pass thresholds
@merit.iter_case_groups(*groups) - Iterate over CaseGroup objects with per-group thresholds and group-level references
@merit.tag(*tags) - Organize and filter merits by tags
@merit.tag.skip(reason=...) - Skip merits unconditionally
@merit.tag.xfail(reason=..., strict=False) - Mark merits expected to fail
@merit.repeat(n, min_passes=n) - Run merits multiple times to see if AI behavior is consistent
@merit.run_inline - Opt out of default threaded execution for sync merits
By default, synchronous merits (def merit_*) run in a worker thread via asyncio.to_thread(...) so the event loop stays responsive. Use @merit.run_inline when a sync merit must run on the main event-loop thread (for example, thread-sensitive libraries).
Resources
Resources are the Merit equivalent of pytest fixtures: named, injectable dependencies that Merit resolves by parameter name.Related docs: Resource (Concept), Metric (Concept), SUT (Concept), Tracing (API)
@merit.resource(scope="case") - Define injectable dependencies with lifecycle management
"case" (default), "suite", "session".
@merit.metric(scope="session") - Define a metric as a scoped, injectable measurement object
Metrics behave like resources, but they’re intended to accumulate measurements across many cases and then assert on aggregates at the end of their scope.
The most common pattern is to inject a Metric into your merits and use the metrics(...) context manager to record assertion outcomes into that metric.
"session" (default), "suite", "case".
@merit.sut - Register a System Under Test (SUT) as an injectable callable
A SUT is the thing you’re actually evaluating (an agent function, a pipeline, a classifier, a client wrapper, etc.). Declaring it with @merit.sut makes it injectable and traceable, so you can assert not only on the output, but also on how it behaved internally (for example: tool calls).
A SUT must be callable. Merit will inject it into your merit function and you will call it like a normal function (or callable object).
Custom Assert
Merit transforms Python’sassert keyword to provide richer testing capabilities for AI systems. When you run merit files through Merit’s runner, assertions behave differently than standard Python.
Continue on failure (default behavior)
By default, Merit continues running remaining assertions even after one fails. This is different from standard Python, where the first failed assertion stops execution immediately.--fail-fast CLI flag:
Integration with metrics
When assertions are evaluated inside ametrics() context manager, Merit automatically records whether each assertion passed or failed to the specified metrics:
Only works through Merit’s runner
Important: Merit’s assertion transformation only applies when you run files through Merit’s test runner:Assert messages
Assert messages work as expected and are captured in theAssertionResult:
Merit Functions (Concept)
Discovery rules, organization, and patterns
Quick Start
End-to-end example with SUTs, predicates, metrics, and traces