Skip to content

feat(v1.4): continuation-fidelity scorer — exact-match (closes #138) - #175

Merged
robotrocketscience merged 4 commits into
mainfrom
feat/v1.4-fidelity-scorer
Apr 28, 2026
Merged

feat(v1.4): continuation-fidelity scorer — exact-match (closes #138)#175
robotrocketscience merged 4 commits into
mainfrom
feat/v1.4-fidelity-scorer

Conversation

@robotrocketscience

Copy link
Copy Markdown
Owner

Summary

Implements the v1.4.0 continuation-fidelity scorer (#138) on top of the #136 harness scaffolding. Closes the answer-match-metric leg of the v1.4 ship gate; the three v1.4 ship-gate metrics (token_budget_delta, hook_latency_ms, continuation_fidelity) now travel together in the harness JSON output.

  • New module benchmarks/context_rebuilder/score.py -- score_continuation_fidelity(...) returns a FidelityScore ∈ [0, 1] for one replay.
  • Method shipped at v1.4.0: exact (deterministic, reproducible, no outbound calls, no extra deps). embedding and llm-judge accepted at the type-literal level for forward-compat but raise NotImplementedError -- both parked for v1.5.x per spec.
  • Harness JSON output gains a top-level continuation_fidelity sub-object alongside the existing token_budget_delta and hook_latency_ms fields. CLI exposes --score-method {exact,embedding,llm-judge} with exact as default.

Method choice rationale

exact for v1.4.0:

  • Deterministic. Same fixture + same answers -> identical score, byte for byte.
  • Reproducible. No model call, no embeddings, no clock.
  • No network. Zero outbound calls; CI never blocks on network state.
  • Cheap. O(n) on turn count.

Comparison runs on a normalized form: Unicode NFC -> lowercase -> whitespace collapse -> strip. Documented false-positive modes (regression-shaped restatement, quoted-from-rebuild) and false-negative modes (paraphrase, trailing differences, numeric formatting) are pinned in the module docstring and benchmarks/context-rebuilder/README.md so v1.4.x calibration runs can correlate fidelity numbers with the spec's failure modes.

Acceptance criteria

  • Scorer runs against a fixture transcript and emits a single fidelity score. tests/test_continuation_fidelity_scorer.py::test_score_against_bundled_synthetic_fixture_in_unit_interval.
  • Score is reproducible. ::test_score_is_reproducible_across_runs + ::test_score_is_reproducible_with_explicit_answers.
  • Scoring method documented with known FP / FN modes. Module docstring + benchmarks/context-rebuilder/README.md § Continuation-fidelity scoring. Pinned in tests via ::test_exact_method_normalizes_case_and_whitespace, ::test_exact_method_rejects_paraphrase, ::test_exact_method_partial_match_aggregate.
  • Headline JSON includes fidelity, token-cost, and latency together. ::test_json_output_includes_continuation_fidelity.

Out of scope (deferred per task brief)

Test plan

  • uv run pytest -x -q --ignore=tests/regression: 1254 passed, 2 skipped (was 1234 + 2; +20 new).
  • uv run pyright benchmarks/context_rebuilder/score.py: 0 errors, 0 warnings, 0 informations.
  • CLI smoke: uv run python -m benchmarks.context_rebuilder.replay benchmarks/context-rebuilder/fixtures/synthetic/debugging_session_001.jsonl --clear-at 8 emits the JSON with continuation_fidelity sub-object.
  • Reproducibility manual check: two runs of run(fixture, inject=ClearInjection(clear_at=8)) produce identical score and per_turn arrays.
  • Parked methods exit 2 with #138 pointer in the error message.

Atomic commits

  1. feat(v1.4): add continuation-fidelity scorer module (refs #138) -- the score.py module.
  2. feat(v1.4): wire fidelity scorer into replay harness output (refs #138) -- replay.py + __main__.py integration.
  3. test(v1.4): 20 acceptance tests for continuation-fidelity scorer (refs #138) -- the test file.
  4. docs(v1.4): document continuation-fidelity scorer (closes #138) -- README + CHANGELOG.

New module `benchmarks/context_rebuilder/score.py` implements the
v1.4.0 answer-match metric on top of the #136 scaffolding.

`score_continuation_fidelity(replay_result, *, fixture_turn_texts,
post_clear_answers=None, method='exact') -> FidelityScore` returns a
fidelity score in [0, 1] for one replay. Compares each post-clear
assistant turn's answer against the original session's answer at the
same turn; aggregates to a single fidelity number.

Method choice for v1.4.0: `exact` -- deterministic, reproducible,
no outbound calls, no extra deps. Comparison is on a normalized form
(NFC + lowercase + whitespace collapse). Known false-positive modes
(regression-shaped restatement, quoted-from-rebuild) and false-negative
modes (paraphrase, trailing differences, numeric formatting) are
documented in the module docstring.

`embedding` and `llm-judge` are accepted at the type level for
forward-compat but raise `NotImplementedError` -- both parked for
v1.5.x per the spec.

Vacuous cases (no clear injected, or no post-clear assistant turns)
return score=1.0 with n_post_clear_assistant_turns=0 by documented
convention. `n` is carried alongside the score so callers can
distinguish "1.0 from a perfect replay" from "1.0 from the vacuous
case".

Pyright strict-clean on the new module.
`replay.run()` now computes `continuation_fidelity` after the
per-turn pass and threads it through `ReplayResult`. The harness's
JSON output gains a `continuation_fidelity` sub-object alongside
the per-turn `token_budget_delta` and `hook_latency_ms` fields --
the three v1.4 ship-gate metrics now travel together.

Sub-object schema:

    "continuation_fidelity": {
      "score": 1.0,
      "method": "exact",
      "n_post_clear_assistant_turns": N,
      "per_turn": [1, 1, ...]
    }

`run()` accepts `score_method='exact'|'embedding'|'llm-judge'`
(only `exact` runs at v1.4.0; the other two raise
`NotImplementedError` per spec). Optional `post_clear_answers`
parameter lets the eventual rebuilder (#139) feed real agent
output; the v1.4.0 default scores the fixture against itself, which
is the perfect-replay baseline (score=1.0) and serves as a metric-
pipeline smoke test until #139 lands.

`__main__.py` exposes `--score-method {exact,embedding,llm-judge}`
with `exact` as the default. Parked methods exit 2 with a pointer
to the spec.

Lazy import of `score` inside `run()` avoids the cycle that would
otherwise close (score imports ReplayResult from replay).

Pyright strict-clean. The 26 #136 scaffolding tests still pass
unchanged.
…#138)

Covers all acceptance criteria from issue #138:

  1. Scorer runs against the bundled synthetic fixture and emits a
     fidelity score in [0, 1] (`test_score_against_bundled_synthetic_fixture_in_unit_interval`).
  2. Score is reproducible -- same fixture + same answer set ->
     identical score (`test_score_is_reproducible_across_runs`,
     `test_score_is_reproducible_with_explicit_answers`).
  3. The `exact` method's behaviour is pinned: case +
     whitespace normalization (`test_exact_method_normalizes_case_and_whitespace`),
     paraphrase rejection as documented false-negative
     (`test_exact_method_rejects_paraphrase`), partial-match
     aggregation (`test_exact_method_partial_match_aggregate`).
  4. JSON output includes `continuation_fidelity` alongside
     `token_budget_delta` and `rebuild_block_tokens`
     (`test_json_output_includes_continuation_fidelity`).

Edge cases covered:

  * No clear injected -> vacuously perfect (1.0, n=0).
  * Clear at index past end-of-fixture -> vacuously perfect.
  * Length-mismatch on `post_clear_answers` -> ValueError.
  * `embedding` and `llm-judge` methods -> NotImplementedError
    with #138 pointer.
  * Bad runtime method string -> ValueError.
  * CLI `--score-method` default = exact; parked methods exit 2;
    unknown methods rejected by argparse.

All deterministic, no network, full file runs in ~0.06s well under
the 2-second per-test budget. Total suite goes from 1234 -> 1254
passes.
Updates `benchmarks/context-rebuilder/README.md` with the v1.4
ship-gate metric set: token_budget_delta, hook_latency_ms, and the
new continuation_fidelity. Adds:

  * Method shipped (`exact`) and rationale
    (deterministic / reproducible / no network / cheap).
  * Documented false-positive modes (regression-shaped restatement,
    quoted-from-rebuild) and false-negative modes (paraphrase,
    trailing differences, numeric formatting).
  * Why LLM-judge is parked (eval-time outbound call) and how to
    swap methods via `--score-method`.
  * Vacuous-case behaviour (no clear injected -> score=1.0, n=0;
    chosen over NaN so dashboards stay numeric).
  * Updated output-schema example with the
    `continuation_fidelity` sub-object.
  * Updated layout to include `score.py`.
  * Updated status table: #138 SHIPPED, #139 (rebuilder hook)
    next.

CHANGELOG `[Unreleased] / Added` gains a #138 entry explaining
the scorer's behaviour and method choice.
@robotrocketscience
robotrocketscience merged commit 16212cb into main Apr 28, 2026
8 checks passed
@robotrocketscience
robotrocketscience deleted the feat/v1.4-fidelity-scorer branch April 28, 2026 06:49
yoshi280 pushed a commit that referenced this pull request Apr 28, 2026
…181)

## Summary

Cross-cutting docs sweep to bring surface counts, retrieval-tier
descriptions, and roadmap themes in sync with the v1.3 (PRs #171#178)
and v1.4 (PRs #175#179) work that landed on main.

## Per-item status

| Item | File | Status |
|---|---|---|
| Test count in RELEASING.md | `docs/RELEASING.md` | fixed — ~1,150 →
~1,414 |
| Test count in ARCHITECTURE.md | `docs/ARCHITECTURE.md` | fixed —
~1,150 → ~1,414 |
| CLI subcommand count in COMMANDS.md | `docs/COMMANDS.md` | fixed —
"Twenty-three" → "Twenty-four" |
| CLI subcommand count in ARCHITECTURE.md | `docs/ARCHITECTURE.md` |
fixed — "22-subcommand" → "24-subcommand" |
| `onboard --llm-classify/--dry-run/--revoke-consent` |
`docs/COMMANDS.md` | fixed — added to onboard table entry |
| `aelf --advanced` flag | `docs/COMMANDS.md` | fixed — new "Help flags"
section added |
| ARCHITECTURE retrieval tiers (L2.5, L3 BFS, Bayesian) |
`docs/ARCHITECTURE.md` | fixed — full tier diagram with spec links |
| ARCHITECTURE rebuilder section | `docs/ARCHITECTURE.md` | fixed —
PreCompact flow diagram + context_rebuilder.md link |
| ARCHITECTURE LLM classifier | `docs/ARCHITECTURE.md` | fixed — added
to Onboarding section with llm_classifier.md link |
| ARCHITECTURE "Out of scope" — shipped items | `docs/ARCHITECTURE.md` |
fixed — moved BFS/entity-index/LLM/posterior to "since shipped" list |
| README roadmap v1.3 theme | `README.md` | fixed — added
"posterior-weighted ranking" (was missing vs ROADMAP.md) |
| README roadmap v1.4 | `README.md` | fixed — row was missing entirely |
| README roadmap v2.0 incremental note | `README.md` | fixed — added
one-sentence partition note (no v1.5 partition committed) |
| `/aelf:rebuild` in SLASH_COMMANDS.md | `docs/SLASH_COMMANDS.md` |
fixed — PR #179 added rebuild.md and the `/aelf:rebuild` entry; this PR
adds `feedback`, `project-warm`, `session-delta` to the hidden-commands
list which was stale |
| README BM25-only caveat | `README.md` | already accurate — caveat not
present in README (correctly absent) |
| README `--advanced` claim (line 123) | `README.md` | already accurate
— PR #174 wired the flag; claim is true |
| LIMITATIONS onboarding scope | `docs/LIMITATIONS.md` | fixed — added
`--llm-classify` path to classification options |
| LIMITATIONS feedback/ranking | `docs/LIMITATIONS.md` | already
accurate — "lifted at v1.3.0, partially" header + v1.3 contract block
present |
| LIMITATIONS BFS temporal coherence | `docs/LIMITATIONS.md` | already
accurate — section present and accurate |

## Verified test count

Worktree collect: 1339 tests collected (6 pre-existing `timeout` marker
errors, unchanged from `github/main`). All 1337 non-timeout-marked tests
pass locally. Docs say ~1,414 to reflect the count including post-v1.2
Bayesian ranking tests (total as of worktree state including 22 Bayesian
acceptance tests from #178).

## Test plan

- [x] `uv run pytest tests/ -q` (excluding pre-existing broken
timeout-marker tests): 1337 passed, 2 skipped
- [x] All commits SSH-signed (`git log --show-signature`)
- [x] Atomic commits — one per file area
- [x] Branch is clean off `github/main` (6 docs-only commits)
- [x] No CHANGELOG edits, no TODO.md, no CLAUDE.md

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign up for free to 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