chore(library): make behavior presets atomic, close a 20-behavior gap, and enforce both in CI - #293
Open
changliu2 wants to merge 2 commits into
Open
chore(library): make behavior presets atomic, close a 20-behavior gap, and enforce both in CI#293changliu2 wants to merge 2 commits into
changliu2 wants to merge 2 commits into
Conversation
The behavior library had three problems, none of which anything checked for.
**Presets bundled multiple behaviors.** `travel_planner` covered six mechanisms
across "Quality failures" and "Safety failures" -- three of which
(`stereotyping`, `prompt_injection`, `sycophancy`) already existed as their own
atomic presets. `travel_planner_benchmark` bundled roughly six more.
`telecom_customer_service` was not a behavior at all: it is an application spec
(Role, Domain Basics, Operational Procedures) wearing `kind: behavior`.
Evaluating a bundle as one behavior produces a dataset mixing several mechanisms
and a metric nobody can act on -- you learn that something failed, never which
mechanism. That is exactly what best-practices 8.D ("use atomic behaviors")
exists to prevent.
These three are application scenarios, so they move to a new `scenario` kind in
`assert_ai/library/scenarios/`. They are the context an eval runs against, not
the behavior it measures.
**20 behaviors shipped to nobody.** `examples/behavior_specs/*.md` held 38 specs;
`assert_ai/library/behaviors/*.yaml` held 18 of them. Only the YAML goes in the
wheel, so every agentic failure mode -- goal drift, premature termination,
repeated action loops, stale state, poor retrieval, tool-call error recovery,
and 14 more -- was invisible to anyone who installed from PyPI. The 18 that did
exist in both places were byte-identical, so this was pure coverage loss, not
divergence. Generated the missing 20 from the existing markdown and the category
metadata already in that directory's README; no prose was invented.
**Nothing detected either problem.** `scripts/check_behavior_library.py` now
fails CI when a preset names another preset's behavior (provable bundling), when
one preset carries several failure categories, when a description reads as an
application spec, or when a spec markdown drifts from its YAML or has no preset
at all. It runs in Tier 1.
Not breaking: `behavior: {preset: travel_planner}` still resolves, via a shim
that warns and points at the `scenario` kind. Config authors get told, not
broken.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 7cb46daf-b5ce-4ad5-a85d-977737e5c02b
changliu2
requested review from
AaronAspinwall123,
jakepresent and
tangym
as code owners
July 31, 2026 17:15
Left out of the previous commit, so 'library show --kind scenario' rejected the new kind and Tier 1 failed. The local run passed only because the edit existed in my working tree but was never staged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7cb46daf-b5ce-4ad5-a85d-977737e5c02b
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Makes every behavior preset atomic, closes a 20-behavior coverage gap in the shipped library, and adds a CI guard so neither regresses.
Found while diffing
examples/behavior_specs/againstassert_ai/library/behaviors/— they are supposed to be one source of truth.Three problems, none of which anything checked for
1. Presets bundled multiple behaviors
travel_plannercovered six mechanisms across "Quality failures" and "Safety failures" — three of which (stereotyping,prompt_injection,sycophancy) already exist as their own atomic presets.travel_planner_benchmarkbundled roughly six more.telecom_customer_servicewasn't a behavior at all — it's an application spec (Role, Domain Basics, Operational Procedures) wearingkind: behavior.Evaluating a bundle as one behavior produces a dataset mixing several mechanisms and a metric nobody can act on: you learn that something failed, never which mechanism. That's precisely what best practices §8.D ("use atomic behaviors") exists to prevent.
These three are application scenarios, so they move to a new
scenariokind underassert_ai/library/scenarios/. A scenario is the context an eval runs against, not the behavior it measures — pair it with atomic behaviors viacontext:.2. Twenty behaviors shipped to nobody
examples/behavior_specs/*.mdassert_ai/library/behaviors/*.yamlpip install assert-aiuserOnly the YAML ships in the wheel. Every agentic failure mode — goal drift, premature termination, repeated action loops, stale state, poor retrieval, tool-call error recovery, and 14 more — was invisible to installed users.
The 18 that existed in both places were byte-identical, so this was pure coverage loss, not divergence. The 20 new presets were generated from the existing markdown plus the category metadata already in that directory's README. No prose was invented.
3. Nothing detected either problem
scripts/check_behavior_library.pynow fails CI when:## <category> failuressections## Role,## Domain Basics,## Operational Procedures)Wired into Tier 1. It reports zero false positives on the 18 pre-existing good presets.
Not a breaking change
preset:resolution is wired into config loading (the library README's "not yet implemented" note is stale —tests/test_library_e2e.pyexercisesbehavior: {preset: travel_planner}end to end). So moving those files would have broken real configs.Instead,
resolve_preset("behavior", "travel_planner")still resolves, via a shim that emits aDeprecationWarningpointing at thescenariokind. Config authors get told, not broken.Also
scenarioadded toVALID_KINDS,KIND_TO_SUBDIR, and thelibrary --kindCLI choicesassert_ai.library.scenariosadded topackage-dataso scenarios actually shipexamples/behavior_specs/README.mdnow names the library as the source of truth and says "edit the YAML, mirror it here"Testing
pytest tests/test_library_loader.py tests/test_library_e2e.py tests/test_import_smoke.py→ 86 passed, incl. new coverage for the scenario kind and the deprecation pathpython scripts/check_behavior_library.py→ 38 presets, atomic, in paritytest_viewer_*failures are pre-existing on cleanmain(ERR_MODULE_NOT_FOUND: yaml— needsnpm installinviewer/, which CI does); verified by stashing this branch and re-running.ruffF401s intest_library_e2e.pyalso pre-exist onmain; left alone rather than widening this diff.Review notes
The 20 generated presets are mechanical and near-identical in shape — the metadata mapping in the "Agentic failure modes" README table is the part worth a careful look. Tag/
applicable_toassignments were derived from the spec README's existing category headings; happy to re-cut any of them.