Skip to content

Fix/gepa user provenance evidence - #788

Merged
OAraLabs merged 4 commits into
OAraLabs:mainfrom
sepehrtaji-dev:fix/gepa-user-provenance-evidence
Oct 22, 2026
Merged

OAraLabs merged 4 commits into
OAraLabs:mainfrom
sepehrtaji-dev:fix/gepa-user-provenance-evidence

Conversation

@sepehrtaji-dev

Copy link
Copy Markdown
Contributor

Summary

This PR fixes an edge case in GEPA request evidence selection where genuine user messages could be incorrectly excluded due to inconsistent trust metadata.

Previously, messages were withheld whenever "is_trusted" was false, even when "provenance" identified them as originating from the user. This could cause legitimate user requests to be missing from GEPA evidence.

Changes

  • Retrieve "provenance" alongside "is_trusted" when selecting request evidence.
  • Preserve messages with "provenance = "user"", even if their trust flag is false.
  • Continue withholding untrusted messages originating from non-user sources.
  • Add regression tests covering trusted and untrusted user messages, as well as untrusted non-user messages.

Testing

Added "tests/test_gepa_evidence_request_provenance.py" to cover the relevant cases. The test suite has not yet been executed in this environment, so CI validation is still needed.

Related to #747.

This change is intentionally limited to GEPA evidence selection and does not modify trust policies elsewhere.

@OAraLabs

Copy link
Copy Markdown
Owner

Thanks for digging into this. You're right that real requests go missing from GEPA evidence, and #747 points the same way. I can't take this approach, though. Today the rows with provenance="user" and is_trusted=0 aren't mostly mislabelled human turns: most are tool-result turns, which are stored as user-role messages and carry outside content (web pages, files, tool output). Letting them through would put that content in front of the model that writes skills. The is_trusted check is the only thing that keeps it out, so it needs to stay.

What would fix the missing requests is having _request skip tool-result rows and look for the newest message the user actually typed. After a tool round, the newest user-role row is usually a tool result, which is why the request comes back empty. Would you be up for reworking it that way, with a test where the newest user row is a tool result? The is_trusted=0 label on image-upload turns is a separate fix we'll handle under #747. Thanks again!

If you'd rather not, no problem: I'm happy to take it from here with you as co-author.

@sepehrtaji-dev

sepehrtaji-dev commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor Author

Update: I pushed a follow-up commit ("befda6086783a90cbd13227f1ddcf1844169c19d") addressing the trust/provenance concern.

  • Kept the "is_trusted" security check intact so untrusted tool output is never included as GEPA request evidence.
  • When the newest "role='user'" row is an untrusted tool result, the code now recovers the latest earlier trusted user request instead of exposing the tool output or dropping the real request.
  • Untrusted injected turns from non-user provenance remain withheld rather than falling back to an older request.
  • Added regression tests for tool results, untrusted injected turns, missing recoverable requests, and normal trusted requests.

Validation shows:
Focused tests: 4 passed.
Ruff: passed.
The broader GEPA test suite could not be

collected in this environment because "croniter" is missing. CI checks are running on the new commit.

Please let me know if I could handle it!

Chatgpt 6 was used to fix this🤖

The request is the newest user-role row that is not a tool result. Tool
results are recognised by what they hold (no flat text, and content_json
made only of tool_result blocks), not by their trust tags. The trust
check then applies to that row: trusted, its text is the request;
untrusted, the request is withheld. So a tool round after an untrusted
turn no longer reaches past it to an older request that did not start
the run.

Tests: an untrusted turn followed by a tool round is withheld; an
untrusted row that is not a tool result is withheld, not skipped;
several tool rounds are all walked past; the tool-result fixture now
looks like a real row (empty content, a tool_result block in
content_json).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@OAraLabs

Copy link
Copy Markdown
Owner

Thanks for sticking with this. You got the important part right: keeping the trust check. I pushed one small commit on top to cover a last edge case (a tool round after an untrusted turn) plus a test for it, so there's nothing more you need to do. Merging once CI is green. Really appreciate it!

@OAraLabs
OAraLabs merged commit 5f49fbb into OAraLabs:main Oct 22, 2026
14 checks passed
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.

2 participants