Skip to content

feat: shared assertion templates (include_assertions) #948

Description

@christso

Problem

Users frequently reuse the same assertion sets across multiple tests and eval files — safety checks, format validation, tone requirements. Currently they must copy-paste assertion blocks, leading to:

  • Duplication across EVAL.yaml files
  • Drift when updating shared criteria
  • Verbose eval files that obscure test-specific logic

Proposed Design

Reusable assertion template files

# .agentv/templates/safe-response.yamlassertions:
- type: llm-graderprompt: ./graders/no-hallucination.mdrequired: true
- type: llm-graderprompt: ./graders/safe-content.mdrequired: truemin_score: 0.9
- type: containsvalue: "disclaimer"negate: true

Reference from EVAL.yaml

# evals/EVAL.yamltests:
- id: refund-requestinput: "I want a refund"assertions:
- include: safe-response # resolves .agentv/templates/safe-response.yaml
- type: llm-graderprompt: ./graders/refund-quality.md # test-specific assertion
- id: greetinginput: "Hello"assertions:
- include: safe-response # same shared template
- type: containsvalue: "hello"

Suite-level includes

# Apply to all testsassertions:
- include: safe-responsetests:
- id: test-1input: "..."# inherits safe-response assertions + can add test-specific ones

Resolution rules

  1. include: name resolves to .agentv/templates/{name}.yaml
  2. Relative paths also work: include: ./my-templates/safety.yaml
  3. Templates can include other templates (max depth 3 to prevent cycles)
  4. Test-level assertions merge with included assertions (not replace)
  5. skip_defaults: true on a test still skips suite-level includes

Template Location

Two resolution mechanisms, no config needed:

  1. Convention directory: include: safe-response resolves to .agentv/templates/safe-response.yaml
  2. Relative path: include: ./my-templates/safety.yaml resolves relative to the eval file
Use caseHow
Shared across repo.agentv/templates/ (convention)
Co-located with evalsinclude: ./shared/safety.yaml
Shared across eval suitesinclude: ../../common/safety.yaml
Monorepo sharedinclude: ../../../packages/evals/templates/safety.yaml

Relative paths are explicit and traceable — no ambiguity about which directory a template resolved from.

Implementation

Files to modify

  1. packages/core/src/evaluation/validation/eval-file.schema.ts — add include variant to EvaluatorSchema
  2. packages/core/src/evaluation/loaders/evaluator-parser.ts — resolve include references, load template files, flatten into assertion list
  3. packages/core/src/evaluation/yaml-parser.ts — handle include resolution during test loading

Template discovery

.agentv/templates/ # convention directory
safe-response.yaml
json-output.yaml
professional-tone.yaml

Discovery chain (closest wins): {eval-dir}/.agentv/templates/{repo-root}/.agentv/templates/

Template file format

Same as an assertion block — a YAML file with a top-level assertions array. Each entry is a standard evaluator config. This keeps templates authorable by AI agents (same schema as inline assertions).

Research Context

Inspired by RSpec's shared_examples pattern — define reusable test behaviors that can be included with context. See test-framework-assertion-patterns research.

Dependencies

Acceptance Signals

  1. include: name resolves correctly — loads from .agentv/templates/{name}.yaml — verified by test
  2. include: ./path resolves relative paths — verified by test
  3. Suite-level includes apply to all tests unless skip_defaults: true — verified by test
  4. Test-level includes merge with test-specific assertions — verified by test
  5. Nested includes work up to depth 3; depth > 3 produces a clear error — verified by test
  6. Missing template produces a clear error with the resolved path — verified by test
  7. Schema validation accepts include entries in assertion arrays — verified by test
  8. eval-schema.json regenerated with include support
  9. All existing tests pass — no regressions

Non-goals

  • Template parameters/variables (keep it simple — templates are static assertion sets)
  • Template versioning
  • Remote template registries
  • Cross-repo template sharing (use relative paths with monorepo layout)
  • Configurable template_dirs in config.yaml (relative paths already cover custom locations — a config-based search path creates "which directory did this resolve from?" debugging headaches)
  • Template inheritance/override (templates are flat assertion lists, not class hierarchies)

Metadata

Metadata

Assignees

No one assigned

    Labels

    in-progressClaimed by an agent — do not duplicate work

    Type

    No type

    Projects

    Status
    Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions