Checker and Deduper Implementation

Continue building Trueup in this repository. Read README.md, hackathon.md, CHANGELOG.md and src/ first. This session is recorded by Entire and published: work only inside this repository and never mention anything outside it. Do not git commit or push. Memory is limited: one library at a time.
Task: the checker (false-alarm control) and the deduper. A lead becomes a "finding" only if every checker rule passes; otherwise it is "withdrawn" with the rule that failed. Withdrawn leads are kept and counted, never deleted.
Checker rules (each a pure function over recorded data, with unit tests; the effects that gather the data live outside core):
- Reproduces in a fresh process, twice, with the same result.
- Reproduces on the library's recommended setup with the framework package installed.
- Reproduces on the latest release and on the library's main branch (install the instrumentation packages from a shallow clone of main into a separate venv
.trueup/venvs/<lib>-main; neatlogs: github.com/neatlogs/neatlogs, OpenInference: github.com/Arize-ai/openinference, OpenLLMetry: github.com/traceloop/openllmetry). If main can't be installed, the rule reports "could not check" with the reason, and the lead stays a lead (not a finding) unless every other rule passes and the reason is recorded. - The fake sent what truth claims (usage present when tokens are compared; unique response ids per call).
- No shared async client across event loops; no export to a dead endpoint.
- A second library on the same scenario shows the correct behaviour, or the OpenTelemetry GenAI semantic conventions define it. If all libraries agree and there is no reference, the lead is marked "disputed semantics", not a finding.
- A minimal repro under 40 lines that runs with no keys exists (generate it from the scenario; run it to prove it reproduces).
Also: the neatlogs adapter may be producing false leads for finish reason (neatlogs spans carry OpenInference's llm.finish_reason but the adapter reads only neatlogs.llm.finish_reason). Look at the real spans, decide from evidence, fix the adapter if it is wrong, and record in hackathon.md which leads that removed and why. That is an honest example of Trueup catching its own false alarm; keep the before and after counts.
Dedupe (src/trueup/dedupe.py): for each finding, search the library's GitHub issues and PRs (all states) with gh search issues/prs using keywords from the finding, list the integration's issue titles, and record the searches with dates. If an existing issue/PR covers it, mark "already known" with the link. Never open, comment or post anything; read only.
Group leads that share a cause (same library, same check, same code path) into one finding with a list of scenarios, so the report is not 50 near-duplicates.
CLI: trueup check <run_id|latest> [--library x] runs the checker and dedupe over a sweep's leads and writes findings.json and withdrawn.json, printing a summary table (findings by severity, withdrawn by rule, already-known). Run it for all three libraries, one at a time, and write the results to hackathon.md (counts and short plain descriptions; no claim beyond the evidence).
Update CHANGELOG.md. Plain words, no filler. uv run pytest -q must pass. Finish with a summary: tests, findings per library, withdrawn by rule, already-known with links, and anything not done.
The neatlogs check is running in the background. I'll be notified when it finishes, then read the results and go on to openinference and openllmetry, one at a time.
Your previous turn ended while the neatlogs check was running in the background, so its result was never read. Do not use background commands from now on: run every command in the foreground and wait for it (use long timeouts). Re-run the check for neatlogs, then openinference, then openllmetry, one at a time, finish everything in the original task, and end with the summary it asked for.
The session hit a usage limit before the check runs finished. Your work so far is committed. Run everything in the foreground (no background commands, long timeouts). Run the check for neatlogs, then openinference, then openllmetry, one at a time, write the results to hackathon.md and CHANGELOG.md, make sure uv run pytest -q passes, and end with the summary the original task asked for.
All three checks ran in the foreground on the final code, and the results are in hackathon.md and CHANGELOG.md. uv run pytest -q passes: 236 passed, 18 e2e deselected. These latest changes are not committed.
The neatlogs false alarm: neatlogs' framework LLM spans are made by the OpenInference instrumentors it wraps, and they carry the right finish reason in llm.finish_reason. Our adapter only read the neatlogs.* keys. I fixed the adapter, which took neatlogs from 58 leads to 31; all 27 removed leads were finish leads on framework scenarios. I did not check whether the neatlogs dashboard actually shows that field.
Findings per library (all latest releases matched PyPI; every main branch installed and was checked):
| library | leads | findings (grouped) | high / medium | withdrawn | disputed | already known |
|---|---|---|---|---|---|---|
| neatlogs | 31 | 12 (from 27 leads) | 3 / 9 | 0 | 2 groups | 3 |
| openinference | 19 | 6 (from 15 leads) | 3 / 3 | 0 | 2 groups | 0 |
| openllmetry | 29 | 9 (from 23 leads) | 6 / 3 | 2 | 2 groups | 1 |
- All three libraries: an OpenAI Agents SDK run that raises still shows its top-level span as OK (5 scenarios each).
- neatlogs:
messages.stream()spans have no tokens, finish reason, output or tool calls; Responses spans have no status or function calls; the chat refusal is missing; a Responses stream ending in an error stays UNSET. - openinference: no span for a chat stream the app closes early or a Responses stream ending in an error; Responses spans have no status; the chat refusal is missing.
- openllmetry: Agents SDK non-streamed runs get two LLM spans per call, doubling calls and tokens; streams that fail show OK; no span for an Anthropic stream that errors or drops; streamed Anthropic spans record 4 output tokens where the provider sent 3; an unknown tool name appears on no span.
- Disputed semantics (all three libraries agree, no OTel rule): no exception event on failed Agents SDK calls, and the LangChain refusal not recorded.
Withdrawn by rule: only latest_and_main, 2 leads (openllmetry oa-resp-failed and oa-resp-incomplete). They reproduce on 0.62.4 but not on main, so they are fixed there.
Already known (4, all open; I read each one):
- neatlogs: https://github.com/neatlogs/neatlogs/pull/180 and issue #179 cover the
messages.stream()output finding. By its description, PR #180 also fixes the tokens, finish and tool-call findings on the same path, but the automatic match only marked the output one. - neatlogs: https://github.com/neatlogs/neatlogs/pull/186 and issue #185 cover both Responses tool-call findings.
- openllmetry: https://github.com/traceloop/openllmetry/pull/4377 describes the exact output-token double count.
Title-matching misses: the title matcher made 9 matches and 5 were wrong. They were openinference PRs #3996 and #3797 (about streamed refusals), openllmetry issue #4536 (spans not ended when the app stops reading early) and openllmetry PRs #4461 and #4462 (reasoning tokens). I recorded them as "related" with the reason, and token findings now skip titles about reasoning tokens. All searches were read-only, recorded with dates in findings.json.
Not done or limited:
- The generated repros are 31 lines, run with no keys and reproduce each lead. But they import trueup for the fake provider and the app code, so they aren't standalone scripts you could paste into an upstream issue yet.
- A finding counts as "already known" only after I read it by hand; the automatic title matching is still loose.
- I ran neatlogs and openinference before adding the reasoning-token filter. Their saved results don't include any reasoning-token match, so the filter wouldn't change them.