Skip to content

feat(evaluation): retrieved-learning relevance/impact evaluation - #329

Merged
yyiilluu merged 5 commits into
mainfrom
feature/retrieved-learning-evaluation
Jul 11, 2026
Merged

feat(evaluation): retrieved-learning relevance/impact evaluation#329
yyiilluu merged 5 commits into
mainfrom
feature/retrieved-learning-evaluation

Conversation

@yyiilluu

@yyiilluu yyiilluu commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds retrieved-learning relevance & impact evaluation to the agent-success pipeline: when a publish request declares retrieved_learnings (the stable identities of profiles/playbooks injected into an agent turn), the evaluator judges, per learning, whether it was relevant to the session and what impact it had on the response (positive / neutral / negative). Verdicts are persisted per (user_id, session_id, kind, learning_id) and exposed through a read API.

Changes

Evaluation

  • RetrievedLearningEvaluator — relevance + impact judging with bounded repair, candidate/attachment limits, and dedup of repeated refs across turns.
  • Runner integration: generation + session-fingerprint fencing; one bounded retry on a stale snapshot, then pending for the next trigger. Terminal outcomes (complete / not_applicable) are cached.
  • Prompts: retrieved_learning_relevance and retrieved_learning_impact v1.0.0.

Read surface

  • get_retrieved_learning_evaluation_results client method + /api/get_retrieved_learning_evaluation_results route, with request/response schemas and the RetrievedLearningEvaluationResult domain model.

Storage

  • Latest-snapshot store with atomic replace_retrieved_learning_evaluation_results under generation + fingerprint fencing.
  • Bulk get_agent_playbooks_by_ids with lifecycle filters (abstract + sqlite impl) replacing per-id fetches.
  • Incremental SessionFingerprintBuilder, transcript char-bounding (append_bounded_snapshot_interaction, DEFAULT_TRANSCRIPT_CHAR_LIMIT), and a precomputed snapshot fingerprint so get_matching_retrieved_learning_terminal_state can recompute live state under a writer transaction and reject a stale cache after a concurrent publish.
  • Governance erase + retention coverage for the new table and evaluation operation-state namespaces.

Test plan

  • Unit + integration: evaluator (relevance/impact, repair, limits, dedup), runner fencing, grade-on-demand caching/revalidation, read API, storage contract (fingerprint invalidation on publish/delete, transcript bounding with late-ref retention, ordering/filters), governance erase.
  • Mock compliance: new LLM operations registered in llm_model_registry / llm_mock; prompt-model mapping + route inventory updated.
  • Full lint (ruff) + type (pyright) clean on changed files.

Summary by CodeRabbit

  • New Features
    • Persist and expose retrieved learnings on interaction views.
    • Added relevance and impact evaluation for retrieved learnings.
    • Introduced POST /api/get_retrieved_learning_evaluation_results with filtering by user/session and limit.
    • Grade-on-demand now returns retrieved-learning evaluation status.
  • Bug Fixes
    • Improved grade-on-demand cache revalidation and rerun behavior when terminal state/fingerprints change.
    • Governance erase/retention now correctly scrubs and purges retrieved-learning evaluation data and related state.
  • Documentation
    • Updated server API docs for the new retrieval/evaluation endpoints and evaluation steps.

yyiilluu added 2 commits July 10, 2026 18:10
Publishers can now attach the learnings they retrieved and injected per
turn via InteractionData.retrieved_learnings — a new minimal
RetrievedLearning{kind, learning_id} model (deliberately not Citation:
no tag/title exposure). The field round-trips through every storage
backend and the InteractionView.

When the sampled group evaluation fires, a new RetrievedLearningEvaluator
runs two judge families (relevance: does the learning apply to the
session; impact: positive/negative/neutral) over every attached,
still-retrieval-eligible learning, with exact per-ref verdict coverage
validation and one bounded repair per chunk. Verdicts land in a new
retrieved_learning_evaluation table as a latest-snapshot per session,
replaced atomically under generation + session-fingerprint fencing
(fingerprint covers every interaction id + attachment refs, recomputed
under the replacement lock — no mutation-path instrumentation).

Runner now returns GroupEvaluationOutcome (independent per-family
completion); regen jobs count both families; grade_on_demand carries
retrieved_learning_status with a fingerprint-revalidated cache;
/healthz/eval gains retrieved-learning counters; new read endpoint
POST /api/get_retrieved_learning_evaluation_results. Governance erase
scrubs the new table plus all three evaluation _operation_state
namespaces (agent-success marker + grade cache were a pre-existing RTBF
gap), via shared key builders in evaluation_state_keys.py. Retention
registers the table keyed on (user_id, session_id) so whole session
snapshots are always removed together.

Also fixes pre-existing reds found along the way: stale hardcoded
copies of _CANONICAL_DELETE_TARGET_NAMES in the governance suites,
sqlite planned/deleted counts missing the offline-tuner targets,
shadow-service tests hardcoding judge prompt v1.0.0, a CitationKind
pyright error in storage_base/_extras.py, and unannotated
RetentionMixin accesses in the retention contract tests.
- Add get_retrieved_learning_evaluation_results client method + route.
- Add bulk get_agent_playbooks_by_ids with lifecycle filters (storage_base
  abstract + sqlite impl); evaluator uses it instead of per-id fetches and
  takes agent_version explicitly.
- Incremental SessionFingerprintBuilder + transcript char-bounding
  (append_bounded_snapshot_interaction, DEFAULT_TRANSCRIPT_CHAR_LIMIT);
  snapshot carries a precomputed fingerprint.
- get_matching_retrieved_learning_terminal_state recomputes live state under
  a writer transaction (BEGIN IMMEDIATE) so a stale cache is rejected after a
  concurrent publish; grade_on_demand caches a terminal outcome only after
  that confirmation and drops the no-attachment short-circuit.
@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 227de365-5273-4402-af37-8af7e3471595

📥 Commits

Reviewing files that changed from the base of the PR and between f18f4c9 and df81c1e.

📒 Files selected for processing (2)
  • reflexio/server/services/storage/sqlite_storage/playbook/_eval_results.py
  • tests/server/services/storage/test_storage_contract_retrieved_learning_evals.py

📝 Walkthrough

Walkthrough

Adds retrieved-learning attachment models, relevance and impact evaluation, SQLite persistence with fingerprint fencing, a results API and client method, grade-cache integration, and governance/retention support with extensive unit and integration coverage.

Changes

Retrieved-learning evaluation

Layer / File(s) Summary
Contracts and evaluation state
reflexio/models/api_schema/..., reflexio/server/services/storage/storage_base/...
Defines retrieved-learning models, bounded snapshots, fingerprints, operation-state keys, and storage interfaces.
Attachment capture and persistence
reflexio/server/services/generation_service.py, reflexio/server/services/storage/sqlite_storage/...
Persists retrieved-learning attachments, creates evaluation tables, resolves eligible playbooks, and implements generation- and fingerprint-guarded result replacement and retrieval.
Relevance and impact judging
reflexio/server/prompt/prompt_bank/retrieved_learning_*, reflexio/server/services/agent_success_evaluation/components/...
Runs bounded relevance and impact judges, validates verdict coverage, retries one malformed response, and records diagnostics and health counters.
Evaluation runner and caching
reflexio/server/services/agent_success_evaluation/runner.py, reflexio/server/routes/evaluation.py, reflexio/server/services/agent_success_evaluation/regen_jobs.py
Runs retrieved-learning evaluation after agent-success evaluation, returns family statuses, revalidates terminal fingerprints, and updates grade-on-demand cache behavior.
Results API
reflexio/client/client.py, reflexio/lib/_search.py, reflexio/server/routes/evaluation.py, reflexio/models/api_schema/retriever_schema.py
Adds filtered retrieval of persisted evaluation results through storage, server, and client APIs.
Governance and validation
reflexio/server/services/governance/*, reflexio/server/services/storage/*, tests/server/services/storage/*
Adds retention, purge, erasure, subject-barrier, and contract coverage for retrieved-learning evaluation rows and operation state.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 45.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the PR’s main change: adding retrieved-learning relevance and impact evaluation.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/retrieved-learning-evaluation

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
reflexio/server/services/agent_success_evaluation/runner.py (1)

268-285: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Route these failure exits through _finish_with_retrieved_evaluation too. These direct returns bypass the retrieved-learning phase, even though the runner treats it as independent elsewhere; a session that hits agent-success failures or saves zero rows never gets that second evaluation family to progress.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@reflexio/server/services/agent_success_evaluation/runner.py` around lines 268
- 285, The failure exits in the group evaluation runner currently return before
running the retrieved-learning phase. Update both branches checking
`evaluation_service.has_run_failures()` and `last_run_saved_result_count == 0`
to call `_finish_with_retrieved_evaluation` with the appropriate failure outcome
instead of returning directly, preserving their warning behavior and result-row
handling.
🧹 Nitpick comments (3)
reflexio/server/services/storage/storage_base/_extras.py (1)

14-17: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider deriving CITATION_KINDS from CitationKind to avoid drift.

CITATION_KINDS duplicates the valid values of the CitationKind domain type. If CitationKind gains a new member, this constant must be updated in lockstep or new citation kinds will be silently dropped by this filter instead of raising a visible signal.

♻️ Derive from `CitationKind` instead of hardcoding
-CITATION_KINDS: frozenset[str] = frozenset(
-    {"playbook", "profile", "user_playbook", "agent_playbook"}
-)
+CITATION_KINDS: frozenset[str] = frozenset(get_args(CitationKind))

(requires CitationKind to be a Literal[...] type alias and from typing import get_args)

Also applies to: 152-161

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@reflexio/server/services/storage/storage_base/_extras.py` around lines 14 -
17, Derive CITATION_KINDS from the CitationKind Literal type using
typing.get_args instead of hardcoding string values. Update the related
filtering logic to use this derived set, ensuring any newly added CitationKind
value is automatically accepted and remains consistent with the domain type.
reflexio/server/services/agent_success_evaluation/runner.py (1)

324-483: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Retrieved-learning "superseded" and "pending" outcomes are not recorded in health counters.

_eval_health.record_retrieved_outcome is called for "failed" (line 429-430) and terminal "applied" outcomes (line 462-464), but not when commit.disposition == "superseded" (466-472) or when both attempts land on "stale" and the function returns "pending" (479-483). This leaves retrieved_learning.outcome_counts in the /healthz/eval payload blind to how often sessions get stuck retrying due to write contention, which is useful signal for exactly this kind of fencing mechanism.

♻️ Record superseded/pending outcomes too
         if commit.disposition == "superseded":
             logger.info(
                 "event=retrieved_learning_eval_superseded session_id=%s generation=%d",
                 session_id,
                 generation,
             )
+            _eval_health.record_retrieved_outcome("superseded")
             return "superseded", None
         logger.info(
             "event=retrieved_learning_eval_stale session_id=%s generation=%d",
             session_id,
             generation,
         )

     # Two stale snapshots in a row: leave pending for the next trigger.
     storage.finish_retrieved_learning_evaluation_run(
         user_id, session_id, generation, "pending", {"error_type": "stale_snapshot"}
     )
+    _eval_health.record_retrieved_outcome("pending")
     return "pending", None
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@reflexio/server/services/agent_success_evaluation/runner.py` around lines 324
- 483, Record health outcomes for every retrieved-learning terminal path: call
_eval_health.record_retrieved_outcome("superseded") before returning from the
commit.disposition == "superseded" branch, and record "pending" after finishing
the run as pending before the final return. Keep the existing failure and
applied-outcome recording unchanged.
reflexio/server/services/storage/storage_base/retrieved_learning_state.py (1)

136-166: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Document the + 3 formatting overhead constant.

prefix_size = len(role) + 3 uses a magic number for transcript formatting overhead. A brief inline comment explaining what the 3 represents (e.g., ": " separator + newline) would aid future maintainers.

♻️ Proposed comment
 def append_bounded_snapshot_interaction(
     snapshot: BoundedRetrievedLearningSnapshot,
     *,
     interaction_id: int,
     role: str,
     content: str,
     created_at: int,
     refs: list[tuple[str, str]],
     transcript_chars_remaining: int,
 ) -> int:
     """Retain refs and only the transcript prefix that fits the char budget."""
     retained_role = ""
     retained_content = ""
     if transcript_chars_remaining > 0 and content:
-        prefix_size = len(role) + 3
+        # Account for role + ": " separator + "\n" = 3 chars of formatting overhead
+        prefix_size = len(role) + 3
         content_budget = max(0, transcript_chars_remaining - prefix_size)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@reflexio/server/services/storage/storage_base/retrieved_learning_state.py`
around lines 136 - 166, Document the formatting overhead represented by the
magic number in append_bounded_snapshot_interaction: add a brief inline comment
next to prefix_size = len(role) + 3 explaining that 3 accounts for the “: ”
separator and newline.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@reflexio/lib/_search.py`:
- Around line 108-110: In the exception handler of the retrieval evaluation
function, stop returning str(e) in
GetRetrievedLearningEvaluationResultsResponse. Log the caught exception
server-side with the appropriate logger, then return a generic user-facing error
message while preserving success=False and an empty results list.

In
`@reflexio/server/services/agent_success_evaluation/components/retrieved_learning_evaluator.py`:
- Around line 231-238: Preserve diagnostics on the empty-candidates return in
the retrieved-learning evaluation method. Update the
RetrievedLearningEvaluationRun construction in the `if not candidates` branch
after `_resolve_candidates` to pass `diagnostics=diagnostics`, matching the
other return paths so `invalid_ref_count` and `candidate_count` reach
`_eval_health.record_retrieved_outcome`.

In `@reflexio/server/services/storage/sqlite_storage/playbook/_eval_results.py`:
- Around line 249-252: The fingerprint built by the playbook evaluation builder
currently uses only interaction IDs and attachment references, so replacing an
existing interaction can leave stale cached verdicts. Update the fingerprinting
logic around builder.add and the corresponding code at the noted duplicate
location to include canonicalized transcript role/content alongside IDs and
attachments, then add a regression test that uses INSERT OR REPLACE on an
existing interaction with changed transcript content and verifies the
fingerprint or terminal verdict is invalidated.
- Around line 433-460: At the commit logic around _rle_eligible_refs, restrict
kept results to keys present in the live attachments for the current user_id and
session_id, deduplicate by (kind, learning_id), and preserve only eligible
attached records. During insertion into retrieved_learning_evaluation, use the
method’s canonical user_id and session_id parameters instead of r.user_id and
r.session_id.

---

Outside diff comments:
In `@reflexio/server/services/agent_success_evaluation/runner.py`:
- Around line 268-285: The failure exits in the group evaluation runner
currently return before running the retrieved-learning phase. Update both
branches checking `evaluation_service.has_run_failures()` and
`last_run_saved_result_count == 0` to call `_finish_with_retrieved_evaluation`
with the appropriate failure outcome instead of returning directly, preserving
their warning behavior and result-row handling.

---

Nitpick comments:
In `@reflexio/server/services/agent_success_evaluation/runner.py`:
- Around line 324-483: Record health outcomes for every retrieved-learning
terminal path: call _eval_health.record_retrieved_outcome("superseded") before
returning from the commit.disposition == "superseded" branch, and record
"pending" after finishing the run as pending before the final return. Keep the
existing failure and applied-outcome recording unchanged.

In `@reflexio/server/services/storage/storage_base/_extras.py`:
- Around line 14-17: Derive CITATION_KINDS from the CitationKind Literal type
using typing.get_args instead of hardcoding string values. Update the related
filtering logic to use this derived set, ensuring any newly added CitationKind
value is automatically accepted and remains consistent with the domain type.

In `@reflexio/server/services/storage/storage_base/retrieved_learning_state.py`:
- Around line 136-166: Document the formatting overhead represented by the magic
number in append_bounded_snapshot_interaction: add a brief inline comment next
to prefix_size = len(role) + 3 explaining that 3 accounts for the “: ” separator
and newline.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: b5130960-2794-4ee4-9c58-21bcdcefdb57

📥 Commits

Reviewing files that changed from the base of the PR and between d1939f6 and dfe5e2c.

📒 Files selected for processing (51)
  • reflexio/client/client.py
  • reflexio/lib/_search.py
  • reflexio/models/api_schema/domain/entities.py
  • reflexio/models/api_schema/eval_overview_schema.py
  • reflexio/models/api_schema/retriever_schema.py
  • reflexio/models/api_schema/ui/converters.py
  • reflexio/models/api_schema/ui/entities.py
  • reflexio/server/README.md
  • reflexio/server/prompt/prompt_bank/retrieved_learning_impact/v1.0.0.prompt.md
  • reflexio/server/prompt/prompt_bank/retrieved_learning_relevance/v1.0.0.prompt.md
  • reflexio/server/routes/evaluation.py
  • reflexio/server/services/agent_success_evaluation/README.md
  • reflexio/server/services/agent_success_evaluation/_eval_health.py
  • reflexio/server/services/agent_success_evaluation/components/retrieved_learning_evaluator.py
  • reflexio/server/services/agent_success_evaluation/regen_jobs.py
  • reflexio/server/services/agent_success_evaluation/runner.py
  • reflexio/server/services/generation_service.py
  • reflexio/server/services/governance/service.py
  • reflexio/server/services/storage/governance_validation.py
  • reflexio/server/services/storage/retention.py
  • reflexio/server/services/storage/sqlite_storage/_base.py
  • reflexio/server/services/storage/sqlite_storage/_governance.py
  • reflexio/server/services/storage/sqlite_storage/governance/_erase_execution.py
  • reflexio/server/services/storage/sqlite_storage/governance/_subject_barrier.py
  • reflexio/server/services/storage/sqlite_storage/playbook/_agent.py
  • reflexio/server/services/storage/sqlite_storage/playbook/_eval_results.py
  • reflexio/server/services/storage/sqlite_storage/profiles/_interaction_store.py
  • reflexio/server/services/storage/storage_base/_extras.py
  • reflexio/server/services/storage/storage_base/evaluation_state_keys.py
  • reflexio/server/services/storage/storage_base/playbook/_agent.py
  • reflexio/server/services/storage/storage_base/playbook/_eval_results.py
  • reflexio/server/services/storage/storage_base/retrieved_learning_state.py
  • reflexio/test_support/llm_mock.py
  • reflexio/test_support/llm_model_registry.py
  • tests/client/test_evaluation_client.py
  • tests/server/api_endpoints/test_grade_on_demand_integration.py
  • tests/server/api_endpoints/test_retrieved_learning_results_api.py
  • tests/server/services/agent_success_evaluation/test_eligibility_logging.py
  • tests/server/services/agent_success_evaluation/test_regen_jobs.py
  • tests/server/services/agent_success_evaluation/test_regen_jobs_concurrency_integration.py
  • tests/server/services/agent_success_evaluation/test_regen_jobs_sampling_integration.py
  • tests/server/services/agent_success_evaluation/test_retrieved_learning_evaluator.py
  • tests/server/services/agent_success_evaluation/test_retrieved_learning_runner_integration.py
  • tests/server/services/evaluation_overview/test_shadow_service_integration.py
  • tests/server/services/governance/test_subject_write_barrier_sqlite.py
  • tests/server/services/storage/sqlite_storage/test_governance_retrieved_learning.py
  • tests/server/services/storage/sqlite_storage/test_governance_storage.py
  • tests/server/services/storage/test_storage_contract_retention.py
  • tests/server/services/storage/test_storage_contract_retrieved_learning_evals.py
  • tests/server/services/test_prompt_model_mapping.py
  • tests/server/test_app_route_inventory.py

Comment thread reflexio/lib/_search.py Outdated
yyiilluu added 3 commits July 10, 2026 18:45
…ss definition

The impact judge previously scored injected learnings positive/negative/neutral
against a generic "better response" notion, untethered from what the org
considers success. Pass AgentSuccessConfig.success_definition_prompt into the
impact judge (impact only; relevance stays success-agnostic) and reword the
v1.0.0 impact prompt to judge movement toward/away from the defined success
criteria. Falls back to general task helpfulness when no definition is set.
- Session fingerprint now covers each interaction's transcript role/content
  (fixed-limit fp_content, decoupled from the snapshot transcript budget so
  the commit-side recompute stays consistent), so an in-place content edit
  (same id + attachments) invalidates stale cached verdicts. Regression test
  added.
- Preserve diagnostics on the empty-candidates evaluation return so
  invalid_ref_count / candidate_count reach eval-health.
- Retrieved-learning read API logs the exception server-side and returns a
  generic message instead of leaking str(e) to the client.
- Scope the retrieved_learning_evaluation INSERT to the canonical
  user_id/session_id (matching the DELETE) instead of per-row values.
replace_retrieved_learning_evaluation_results kept records that were
retrieval-eligible but never attached to the session — the method contract is
"attached AND eligible". Add an in-transaction _rle_attached_refs check so a
verdict is stored only when its (kind, learning_id) is still attached to the
live session. Duplicate identities are intentionally left to trip the UNIQUE
index and roll back the commit (fail loud on caller bugs). Regression test added.
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