fix(evaluation): keep verdicts through learning merges#336
Conversation
📝 WalkthroughWalkthroughRetrieved-learning evaluation now resolves merged, superseded, and purged references through lineage. Candidate evaluation uses survivor records, while result commits reject lineage changes detected during evaluation. Tests cover evaluator behavior, forced regeneration, and stale commit races. ChangesRetrieved-learning lineage handling
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Session as Evaluation Session
participant Evaluator as RetrievedLearningEvaluator
participant Lineage as resolve_current
participant LLM
participant Store as replace_retrieved_learning_evaluation_results
Session->>Evaluator: Load attached learning references
Evaluator->>Lineage: Resolve references to current identities
Lineage-->>Evaluator: Return survivor or purged status
Evaluator->>LLM: Evaluate eligible survivor content
LLM-->>Evaluator: Return evaluation results
Evaluator->>Store: Commit proposed results
Store->>Lineage: Recheck proposed identities
Lineage-->>Store: Confirm current or changed lineage
Store-->>Session: Return complete or stale disposition
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
reflexio/server/services/storage/storage_base/playbook/_eval_results.py (1)
224-230: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick winDocument the lineage-change stale path in the abstract contract.
The SQLite implementation returns
stalebefore filtering when a proposed identity resolves elsewhere or becomes purged, preserving the prior result set. This doc currently only says failed rows are dropped and the prior set is replaced, which could lead another backend to implement the fencing contract incorrectly.Suggested clarification
recomputes the session fingerprint from live rows and requires it to equal ``session_fingerprint`` (else ``stale``), requires every result to match a session attachment after lineage resolution, and rechecks its source row for retrieval eligibility. Rows that fail either check are dropped before the prior session set is atomically replaced. + If a proposed identity no longer resolves to itself or is purged, + the method returns ``stale`` without replacing the prior result set.🤖 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/playbook/_eval_results.py` around lines 224 - 230, Clarify the abstract contract around the lineage-resolution and purge checks in the documented transaction behavior: when a proposed identity resolves elsewhere or is purged, return the `stale` outcome before filtering and preserve the prior session result set. Retain the existing row-filtering and atomic replacement behavior for results that pass the lineage and retrieval-eligibility checks.
🤖 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/server/services/storage/sqlite_storage/playbook/_eval_results.py`:
- Around line 282-293: Update _rle_lineage_changed so an unresolvable reference
is treated as stale: return True when resolve_current returns None, while
preserving the existing stale checks for purged records or changed IDs and
continuing to ignore only the specified resolution exceptions. Add a regression
test covering deletion or a malformed lineage during evaluation.
---
Nitpick comments:
In `@reflexio/server/services/storage/storage_base/playbook/_eval_results.py`:
- Around line 224-230: Clarify the abstract contract around the
lineage-resolution and purge checks in the documented transaction behavior: when
a proposed identity resolves elsewhere or is purged, return the `stale` outcome
before filtering and preserve the prior session result set. Retain the existing
row-filtering and atomic replacement behavior for results that pass the lineage
and retrieval-eligibility checks.
🪄 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: 348c08cc-fcfe-465b-9d13-1cce8f2ec2b5
📒 Files selected for processing (7)
reflexio/models/api_schema/domain/entities.pyreflexio/server/services/agent_success_evaluation/components/retrieved_learning_evaluator.pyreflexio/server/services/storage/sqlite_storage/playbook/_eval_results.pyreflexio/server/services/storage/storage_base/playbook/_eval_results.pytests/server/services/agent_success_evaluation/test_retrieved_learning_evaluator.pytests/server/services/agent_success_evaluation/test_retrieved_learning_runner_integration.pytests/server/services/storage/test_storage_contract_retrieved_learning_evals.py
|
@coderabbitai review |
✅ Action performedReview finished.
|
|
Closing — Yi will handle the fix upstream (evaluate tombstoned rows instead of skipping them). |
Why
A retrieved-learning link records the ID that was shown to the agent. Reflexio may merge or supersede that learning before someone runs the on-demand evaluation. The evaluator currently looks up only the original ID, so a valid learning can disappear from a regenerated evaluation simply because the library consolidated it.
This especially undercounts actively maintained learnings. In one opt-in development database, there are already 45 user-playbook merge events and 50 user-playbook rows with a successor pointer.
Before / changed / after
flowchart LR A["Recorded learning ID"] --> B["Follow existing lineage"] B --> C{"Current live learning?"} C -->|"yes"| D["Apply normal eligibility rules"] D --> E["Judge current ID + content once"] C -->|"missing or purged"| F["Skip and count the reason"]Example: if an interaction recorded user playbook
12, and12later merged into31, the evaluator judges playbook31and stores the verdict under31. It never judges blanked content, and two old IDs that both lead to31produce one candidate.Safety and observability
resolved_via_lineage.stalewithout replacing prior results. The next forced run can evaluate current state instead of caching a falsenot_applicableoutcome.Boundaries
Verification
sys.modules.git diff --check: passed.