Skip to content

VERA 2.0: Multi-target, Multi-turn Evaluator - #182

Draft
luca-belli wants to merge 67 commits into
mainfrom
feat/VERA_2.0
Draft

VERA 2.0: Multi-target, Multi-turn Evaluator#182
luca-belli wants to merge 67 commits into
mainfrom
feat/VERA_2.0

Conversation

@luca-belli

Copy link
Copy Markdown
Collaborator

No description provided.

luca-belliand others added 30 commits July 9, 2026 14:56
…gration phasing
- Introduce u/c/j entity vocabulary (user/chatbot/judge), replacing the
ambiguous p_ prefix and "patient"/"provider" terminology throughout
- Formalize config.json/state.json traceability (immutable config + sha256
sidecar, mutable resume state) and the orthogonal generation/judging
config split
- Add use cases 5 (pool) and 6 (resume), resolve open questions from the
PR thread (multi-folder judge output, multi-rubric layout, folder
naming, provider-vs-model identifiers)
- Split migration into 4 phases: cosmetic wrapper, scoring split,
multi-rubric support, substantial refactor
- Remove stale "why async judges are not async?" TODO
… policy
- Phase 1 is no longer "cosmetic" -- it fully replaces the top-level
scripts with the new CLI/config surface (-u/-j/--sample/--config)
from day one, using an informal config.json shape until Phase 3
formalizes it as a stable interface
- Split scoring-split and multi-rubric-support into separate phases
(5 total); multi-rubric no longer depends on artifacts that don't
exist until a later phase
- Add per-phase "Done when" acceptance bars, incremental import-linter
enforcement instead of all-at-once, explicit legacy-output-data and
rollback stances
- Introduce "stable interfaces" concept: ABCs/Role/naming module/config
schema require a design doc to change, enforced via new CODEOWNERS
entries -- codebase is optimized for agent coding, so a small set of
rarely-changed files get higher scrutiny than the rest
- Phase 0's rubric-loading fix moves to a reusable library helper
instead of judge.py's soon-to-be-deleted main(), so Phase 1 reuses it
rather than throwing it away
- Introduce the rubric bundle manifest format (rubric/prompt files +
informational persona links) -- previews the real judging.rubrics[]
config shape instead of a throwaway CLI convention
- judging.rubrics is a list from day one at every phase (not just
Phase 3), closing the same schema-break risk one phase earlier
- vera judge explicitly exposes --rubric in Phase 1's CLI surface
- Phase 1 requires a parity/regression test suite against the scripts
it replaces, and deletes them outright at phase end (no lingering
deprecation-stub period, which also removes a MUST-NOT contradiction)
- Phase 4's acceptance bar now also verifies single-rubric backward
compatibility, not just the new multi-rubric case
- Every phase's Done-when now implicitly covers keeping README/AGENTS
docs current, stated once instead of per-phase
vera generate drives a multi-turn, non-deterministic LLM conversation --
exact-output parity testing against generate.py isn't meaningful the way
it might be for more structural code, and the non-determinism compounds
turn over turn. Split Phase 1's testing bar into structural parity
(automated, via the existing mock_llm.py harness) and live-LLM manual
spot-checks (explicitly not something pytest can certify), so this
doesn't get silently treated as fully covered by a single test suite.
…ongs
Phase 1 only replaces the CLI front-end -- generate_conversations/judge
internals are untouched, so structural parity testing is sufficient and
the risk is low. Phase 5's workers/ unification is what actually
rewrites the execution engine (both runners move off independent
hand-rolled asyncio queues onto the shared workers/ queue/dispatch),
which is where concurrency/timing regressions could actually appear.
The two-part testing approach (structural parity + manual live-LLM
spot-check) now attaches to Phase 5's Done-when instead.
…nfig
overlap, provider concurrency, ESCALATE governance
- scripts/pool_vera_scores.py's disposition was silently dropped from the
original 8-phase plan when it collapsed to 6 phases -- checked again
in Phase 5
- Explicitly acknowledge that the Phase 3 naming-scheme swap breaks
anything outside vera.py that parses the old p_*/j_* pattern directly
- State the separation of concerns between the rubric bundle manifest
(what a rubric is) and config.json (how to run it), since they now
overlap in subject matter
- Flag per-provider concurrency limits as a real Phase 5 requirement --
workers/ enables genuine parallel fan-out with no cap yet
- Clarify that documenting a phase here does not pre-clear its ESCALATE
requirement; every phase still needs fresh sign-off before it starts
- generate_conversations/ -> generate/, scoring/ -> score/ (judge/ was
already consistent) across architecture.md and vera-cli-use-cases.md
- Remove docs/architecture-plan.md (superseded by architecture.md,
removal was its own stated intent once the target doc was done)
- Remove TODO (its items are now resolved or tracked in the migration
plan's open items)
…tries
Fixes findings from a code review of PR #170 plus two open Copilot
comments: the naming-module contradiction in docs/architecture.md,
CODEOWNERS entries pointing at nonexistent files, a dead
architecture-invariants.md stub, duplicated/drifted ESCALATE guidance
in AGENTS.md, ambiguous "until migration" phrasing, and the
generate.py/judge.py vs vera.py framing contradiction in README.md.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…tries
Fixes findings from a code review of PR #170 plus two open Copilot
comments: the naming-module contradiction in docs/architecture.md,
CODEOWNERS entries pointing at nonexistent files, duplicated/drifted
ESCALATE guidance in AGENTS.md, ambiguous "until migration" phrasing,
and the generate.py/judge.py vs vera.py framing contradiction in
README.md.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…tion)
New storage/ package with a StorageBackend ABC (write/read/exists on raw
bytes+keys) plus LocalFilesystemStorage as the default -- same
interface-with-implementations idiom already used by llm_clients/ and
workers/, so a future non-local backend (S3) is a new implementation, not
a rewrite. Explicitly orthogonal to phases 0-5: no dependency on, and
nothing depends on it, so it can be pulled earlier or run in parallel if
a concrete need shows up. Also fixed a stale generate_conversations/
reference left in CODEOWNERS from the earlier package rename.
rubric-navigation-in-code, non-developer data/ rationale
- config.json.sha256 is one canonical computation shared by the run-id
folder name and the sidecar -- never two independent values, and
never embedded in config.json's own filename
- vera pipeline --target <name> is the one deliberate exception where
a rubric bundle manifest's personas become authoritative for both
generation and judging in one shot; every other invocation keeps
generation/judging orthogonal
- Rubric navigation (which question comes next) is code-owned
(QuestionNavigator), never inferred by the judge LLM from prompt text
- Rubric/persona content in data/ is explicitly required to stay
outside code, since CLEO must be usable by non-developers
…on, run nicknames, phase testing
Addresses Emily's 2026-07-25 re-review round and the earlier RFC comments:
add the missing -c/--chatbot selection (CLI, config.json, Domain model),
anchor config.json paths to $ROOT instead of CWD (mirroring the manifest's
own-folder convention), mirror --target into the input config with its
mutual-exclusivity rule, add human-readable run nicknames alongside the
sha, split Phase 5's live-LLM testing into an automated tolerance-based
smoke tier and a qualitative manual tier, clarify that new rubrics only
need Phase 0-4, spell out the design-doc enforcement mechanism (CODEOWNERS
+ CI gate), and note that judge/score still work against old-layout data
via explicit paths even though resume does not.
Fixes two stale/wrong entries (run-id naming lost its nickname segment,
AD-23 overclaimed that old-layout data can't be read at all) and adds
two new ADs the spine was missing entirely: chatbot selection (AD-27)
and config vs. manifest path resolution (AD-28). Extends AD-15 with the
concrete design-doc enforcement mechanism and AD-21 with the target
field now mirrored into config.json, so every prose rule added to
architecture.md and vera-cli-use-cases.md today has a citable AD.
Today's additions (chatbot flag, $ROOT path rule, nicknames, phase
testing split) are still design-stage decisions. Flag both docs as
subject to revision once implementation surfaces things the design
discussion alone couldn't.
Fills the gap between the mechanical live smoke test (did it run, same
shape) and the manual spot-check (does it read well): embed old-engine
vs new-engine conversations for matching (persona, config) pairs and
compare cosine similarity against a calibrated old-vs-old baseline, so
content drift is caught statistically without requiring exact wording
match or relying on manual review alone.
…guity
generation has two competing LLM roles (chatbot, user); a bare "models"
field didn't say which. judging keeps "models" since only one LLM role
exists there, so the name is already unambiguous in that block.
Links to architecture.md from vera-cli-use-cases.md used ../ instead
of ./, since both files live under docs/. Also clarify that -c/-u/-j
in the new CLI intentionally reuse letters that mean something
different in today's generate.py/judge.py, with no coexistence window
since those scripts are deleted in Phase 1.
…de too
Phase 0 previously only wired judge.py's --rubrics flag to the new
bundle-manifest helper, leaving a manifest's personas field completely
unused until Phase 1's vera.py --target shorthand. Since a rubric
bundle manifest is meant to attach a rubric and its intended personas
as one unit, add generate.py --rubric-manifest as Phase 0's stopgap
generation-side counterpart, so the attachment is proven out on both
sides of the pipeline before the CLI unification in Phase 1.
RubricConfig.load_bundle() inlined manifest JSON parsing/validation,
which only judge.py could reach. Per the updated Phase 0 scope in
docs/architecture.md, generate.py needs to read the same manifest's
personas list, so the reading/validation logic moves to utils/ (leaf
layer) instead -- generate/ and judge/ must never import each other.
load_bundle() now delegates to utils.rubric_manifest.load_manifest();
behavior is unchanged, covered by the existing load_bundle tests plus
new direct tests for the extracted module.
load_manifest_personas() returned personas entries verbatim instead of
resolving them relative to the manifest's own folder, contradicting
docs/architecture.md's stated rule for manifest paths (the same rule
rubric_file/etc. already follow via RubricConfig.load()). Update
data/rubric_manifest.json's personas entry to the correct
manifest-relative form ("personas.tsv", not "data/personas.tsv") now
that resolution actually happens, and cover both relative and
absolute-path entries with tests.
Caught while reorganizing data/ into per-rubric subfolders on a
downstream branch -- fixing it here instead, since this PR hasn't
merged yet and is where the bug was introduced.
luca-belliand others added 30 commits July 31, 2026 09:18
Same shape as load_manifest_personas(): another optional field a rubric
bundle manifest can carry, read by utils/ so both generate/ and judge/
can reach it without importing each other. Unused until a later PR
wires a schema-specific persona context template into generation.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
data/rubric_manifest.json now points at data/persona_context_template.txt
via the new persona_context_template_file field (accessor added in the
prior commit), matching how the personas field was wired into this same
manifest ahead of generate.py's --rubric-manifest flag landing.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
judge.py --rubrics already loads a rubric bundle manifest's rubric
half; generate.py had no way to load the same manifest's personas
half, leaving a manifest's rubric+personas attachment unverified on
the generation side (docs/architecture.md's updated Phase 0 scope).
Add --rubric-manifest to generate.py: resolves the manifest's
personas list via utils.rubric_manifest.load_manifest_personas() and
threads it through ConversationRunner as persona_prompt_path (a new,
previously-hardcoded-to-data/personas.tsv parameter) instead of the
fixed default. Mirrors judge.py's own "first entry wins, warn on
extras" handling for manifests listing more than one persona file.
Also wires --rubric-manifest through run_pipeline.py's generation
step, alongside its existing --rubrics (judging) flag.
The shared data/persona_prompt_template.txt hardcoded the SI backstory
block, so a second rubric schema (e.g. PHQ9) couldn't reuse it with its
own persona fields. Split it into a shared template (stylistic/behavioral
instructions, with a {persona_context} placeholder) and a schema-specific
context template (data/persona_context_template.txt, selected via the
manifest's persona_context_template_file field added in the prior PR).
generate_conversations/utils.py's load_prompts_from_csv() now requires a
persona_context_template_path, formats it against the TSV row first, and
validates upfront that the context template's placeholders all exist as
CSV columns (previously a missing column silently skipped the row with a
printed warning). generate.py resolves this path from --rubric-manifest
the same way it already resolves persona_prompt_path, defaulting to
data/persona_context_template.txt when no manifest is given.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Everything currently in data/ (rubric.tsv, rubric_prompt_beginning.txt,
question_prompt.txt, rubric_manifest.json, personas.tsv) is specific to
the SI rubric -- once a second rubric type (e.g. PHQ9) exists, data/
needs a subfolder per rubric rather than one flat, implicitly-SI set
of files. Move all five into data/SI/ and update every hardcoded
default/reference across generate.py, judge.py, run_pipeline.py,
judge/score.py, judge/score_utils.py, scripts/, README.md, docs, and
tests.
--rubrics/--rubric-manifest require a full manifest path; there's no
way to select a rubric by bare name (e.g. typing "SI" anywhere on the
command line) until --target lands on the future vera.py CLI. Also
documents generate.py --rubric-manifest, which wasn't in the README
at all.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
…elper
feat: add RubricConfig.load_bundle() for rubric bundle manifests
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
feat: wire --rubrics flag to RubricConfig.load_bundle()
…allback
judge/score_utils.py loaded the rubric at import time from a cwd-relative
path, so importing the module from outside the repo root raised
FileNotFoundError. Resolve it from __file__ instead.
scripts/pool_vera_scores.py's programmatic fallback still pointed at the
pre-move data/personas.tsv. Since the call site guards on is_file(), risk
level analysis silently skipped for callers that did not pass personas_tsv.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…o-si-folder
refactor: move the SI rubric bundle into data/SI/
Add note about including SHA of config files to avoid silent failures.
Refactor: decouple rubric logic from SI structure
fix: preserve scores on final assign end
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@luca-belli