tokenaudit 0.2.0__tar.gz → 0.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/CHANGELOG.md +52 -1
  2. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/DECISIONS.md +17 -0
  3. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/PKG-INFO +10 -7
  4. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/README.md +9 -6
  5. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/captured/runner.py +29 -13
  6. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/fixtures/breakpoint_limit.label.json +4 -0
  7. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/fixtures/context_bloat.label.json +4 -0
  8. tokenaudit-0.4.0/corpus/synthetic/fixtures/cross_session_distinct.label.json +36 -0
  9. tokenaudit-0.4.0/corpus/synthetic/fixtures/cross_session_distinct.trace.json +749 -0
  10. tokenaudit-0.4.0/corpus/synthetic/fixtures/cross_session_drift.label.json +39 -0
  11. tokenaudit-0.4.0/corpus/synthetic/fixtures/cross_session_drift.trace.json +986 -0
  12. tokenaudit-0.4.0/corpus/synthetic/fixtures/cross_session_stable.label.json +36 -0
  13. tokenaudit-0.4.0/corpus/synthetic/fixtures/cross_session_stable.trace.json +986 -0
  14. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/fixtures/healthy_cached.label.json +4 -0
  15. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/fixtures/healthy_incremental.label.json +4 -0
  16. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/fixtures/healthy_small.label.json +4 -0
  17. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/fixtures/lookback_overflow.label.json +4 -0
  18. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/fixtures/manifest.json +112 -0
  19. tokenaudit-0.4.0/corpus/synthetic/fixtures/misordered_shared_context.label.json +39 -0
  20. tokenaudit-0.4.0/corpus/synthetic/fixtures/misordered_shared_context.trace.json +239 -0
  21. tokenaudit-0.4.0/corpus/synthetic/fixtures/misordered_shared_context_provider_log.label.json +39 -0
  22. tokenaudit-0.4.0/corpus/synthetic/fixtures/misordered_shared_context_provider_log.trace.json +104 -0
  23. tokenaudit-0.4.0/corpus/synthetic/fixtures/missing_cache_markers.label.json +39 -0
  24. tokenaudit-0.4.0/corpus/synthetic/fixtures/missing_cache_markers.trace.json +373 -0
  25. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/fixtures/model_switch.label.json +4 -0
  26. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/fixtures/openai_healthy_cached.label.json +4 -0
  27. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/fixtures/prefix_drift.label.json +4 -0
  28. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/fixtures/prefix_drift_legacy_flat.label.json +4 -0
  29. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/fixtures/prefix_drift_no_content.label.json +4 -0
  30. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/fixtures/prefix_drift_openinference.label.json +4 -0
  31. tokenaudit-0.4.0/corpus/synthetic/fixtures/prefix_drift_provider_log.label.json +39 -0
  32. tokenaudit-0.4.0/corpus/synthetic/fixtures/prefix_drift_provider_log.trace.json +284 -0
  33. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/fixtures/retry_storm.label.json +4 -0
  34. tokenaudit-0.4.0/corpus/synthetic/fixtures/shared_context_first.label.json +37 -0
  35. tokenaudit-0.4.0/corpus/synthetic/fixtures/shared_context_first.trace.json +275 -0
  36. tokenaudit-0.4.0/corpus/synthetic/fixtures/shared_context_small.label.json +37 -0
  37. tokenaudit-0.4.0/corpus/synthetic/fixtures/shared_context_small.trace.json +239 -0
  38. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/fixtures/sub_minimum.label.json +4 -0
  39. tokenaudit-0.4.0/corpus/synthetic/fixtures/tool_schema_bloat.label.json +39 -0
  40. tokenaudit-0.4.0/corpus/synthetic/fixtures/tool_schema_bloat.trace.json +1160 -0
  41. tokenaudit-0.4.0/corpus/synthetic/fixtures/tool_schema_bloat_provider_log.label.json +39 -0
  42. tokenaudit-0.4.0/corpus/synthetic/fixtures/tool_schema_bloat_provider_log.trace.json +1412 -0
  43. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/fixtures/tool_schema_churn.label.json +4 -0
  44. tokenaudit-0.4.0/corpus/synthetic/fixtures/tool_schema_lean.label.json +38 -0
  45. tokenaudit-0.4.0/corpus/synthetic/fixtures/tool_schema_lean.trace.json +836 -0
  46. tokenaudit-0.4.0/corpus/synthetic/fixtures/tool_schema_rotating.label.json +38 -0
  47. tokenaudit-0.4.0/corpus/synthetic/fixtures/tool_schema_rotating.trace.json +1342 -0
  48. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/fixtures/ttl_expiry.label.json +4 -0
  49. tokenaudit-0.4.0/corpus/synthetic/fixtures/unmarked_far_apart.label.json +37 -0
  50. tokenaudit-0.4.0/corpus/synthetic/fixtures/unmarked_far_apart.trace.json +373 -0
  51. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/generate.py +494 -14
  52. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/demo/README.md +4 -0
  53. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/demo/app.py +5 -6
  54. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/demo/static/index.html +1 -1
  55. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/docs/case-studies/tradingagents.md +26 -1
  56. tokenaudit-0.4.0/eval/results.json +1692 -0
  57. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/eval/results.md +8 -4
  58. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/eval/run_eval.py +14 -11
  59. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/pyproject.toml +1 -1
  60. tokenaudit-0.4.0/render.yaml +10 -0
  61. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/__init__.py +1 -1
  62. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/__main__.py +32 -5
  63. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/ci/baseline.py +13 -2
  64. tokenaudit-0.4.0/src/tokenaudit/counterfactual/__init__.py +88 -0
  65. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/detect/__init__.py +9 -1
  66. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/detect/base.py +68 -2
  67. tokenaudit-0.4.0/src/tokenaudit/detect/corpus_record.json +80 -0
  68. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/detect/engine.py +50 -1
  69. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/detect/rules/breakpoint_limit.py +12 -15
  70. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/detect/rules/context_bloat.py +14 -14
  71. tokenaudit-0.4.0/src/tokenaudit/detect/rules/cross_session_drift.py +226 -0
  72. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/detect/rules/lookback_overflow.py +15 -15
  73. tokenaudit-0.4.0/src/tokenaudit/detect/rules/misordered_shared_context.py +180 -0
  74. tokenaudit-0.4.0/src/tokenaudit/detect/rules/missing_cache_markers.py +149 -0
  75. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/detect/rules/model_switch.py +11 -12
  76. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/detect/rules/prefix_drift.py +15 -18
  77. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/detect/rules/retry_storm.py +13 -14
  78. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/detect/rules/sub_minimum.py +16 -18
  79. tokenaudit-0.4.0/src/tokenaudit/detect/rules/tool_schema_bloat.py +122 -0
  80. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/detect/rules/tool_schema_churn.py +13 -18
  81. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/detect/rules/ttl_expiry.py +16 -19
  82. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/ingest/__init__.py +20 -12
  83. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/ingest/dialects.py +100 -5
  84. tokenaudit-0.4.0/src/tokenaudit/ingest/provider_logs.py +267 -0
  85. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/model/records.py +27 -3
  86. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/report/html.py +20 -2
  87. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/tests/test_corpus.py +15 -2
  88. tokenaudit-0.4.0/tests/test_counterfactual.py +65 -0
  89. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/tests/test_demo.py +9 -0
  90. tokenaudit-0.4.0/tests/test_detect.py +408 -0
  91. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/tests/test_eval.py +1 -1
  92. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/tests/test_ingest.py +78 -1
  93. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/tests/test_report.py +1 -1
  94. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/tokenaudit-baseline.json +5 -2
  95. tokenaudit-0.2.0/eval/results.json +0 -668
  96. tokenaudit-0.2.0/src/tokenaudit/counterfactual/__init__.py +0 -1
  97. tokenaudit-0.2.0/src/tokenaudit/detect/corpus_record.json +0 -56
  98. tokenaudit-0.2.0/tests/test_detect.py +0 -201
  99. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/.dockerignore +0 -0
  100. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/.github/workflows/ci.yml +0 -0
  101. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/.github/workflows/release.yml +0 -0
  102. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/.gitignore +0 -0
  103. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/.pre-commit-config.yaml +0 -0
  104. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/LICENSE +0 -0
  105. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/Makefile +0 -0
  106. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/action.yml +0 -0
  107. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/README.md +0 -0
  108. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/captured/README.md +0 -0
  109. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/captured/capture_tradingagents.sh +0 -0
  110. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/captured/redact.py +0 -0
  111. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/captured/runs/tradingagents/run-01-NVDA-2026-08-14/decision.txt +0 -0
  112. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/captured/runs/tradingagents/run-01-NVDA-2026-08-14/manifest.json +0 -0
  113. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/captured/runs/tradingagents/run-01-NVDA-2026-08-14/trace.jsonl +0 -0
  114. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/captured/runs/tradingagents/run-02-AAPL-2026-08-14/decision.txt +0 -0
  115. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/captured/runs/tradingagents/run-02-AAPL-2026-08-14/manifest.json +0 -0
  116. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/captured/runs/tradingagents/run-02-AAPL-2026-08-14/trace.jsonl +0 -0
  117. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/captured/runs/tradingagents/run-03-NVDA-2026-08-21/decision.txt +0 -0
  118. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/captured/runs/tradingagents/run-03-NVDA-2026-08-21/manifest.json +0 -0
  119. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/captured/runs/tradingagents/run-03-NVDA-2026-08-21/trace.jsonl +0 -0
  120. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/fixtures/breakpoint_limit.trace.json +0 -0
  121. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/fixtures/context_bloat.trace.json +0 -0
  122. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/fixtures/healthy_cached.trace.json +0 -0
  123. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/fixtures/healthy_incremental.trace.json +0 -0
  124. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/fixtures/healthy_small.trace.json +0 -0
  125. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/fixtures/lookback_overflow.trace.json +0 -0
  126. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/fixtures/model_switch.trace.json +0 -0
  127. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/fixtures/openai_healthy_cached.trace.json +0 -0
  128. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/fixtures/prefix_drift.trace.json +0 -0
  129. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/fixtures/prefix_drift_legacy_flat.trace.json +0 -0
  130. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/fixtures/prefix_drift_no_content.trace.json +0 -0
  131. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/fixtures/prefix_drift_openinference.trace.json +0 -0
  132. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/fixtures/retry_storm.trace.json +0 -0
  133. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/fixtures/sub_minimum.trace.json +0 -0
  134. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/fixtures/tool_schema_churn.trace.json +0 -0
  135. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/fixtures/ttl_expiry.trace.json +0 -0
  136. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/demo/Dockerfile +0 -0
  137. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/demo/__init__.py +0 -0
  138. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/demo/requirements.txt +0 -0
  139. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/docs/ci.md +0 -0
  140. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/docs/rulebook.md +0 -0
  141. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/ci/__init__.py +0 -0
  142. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/ci/check.py +0 -0
  143. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/detect/findings.py +0 -0
  144. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/detect/rules/__init__.py +0 -0
  145. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/examples/sample.trace.json +0 -0
  146. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/ingest/containers.py +0 -0
  147. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/ingest/dedup.py +0 -0
  148. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/ingest/quality.py +0 -0
  149. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/ingest/sessionize.py +0 -0
  150. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/model/__init__.py +0 -0
  151. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/py.typed +0 -0
  152. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/report/__init__.py +0 -0
  153. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/report/corpus_record.py +0 -0
  154. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/report/health.py +0 -0
  155. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/rulebook/__init__.py +0 -0
  156. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/rulebook/anthropic.json +0 -0
  157. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/rulebook/google.json +0 -0
  158. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/rulebook/openai.json +0 -0
  159. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/tests/test_ci.py +0 -0
  160. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/tests/test_package.py +0 -0
  161. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/tests/test_redact.py +0 -0
  162. {tokenaudit-0.2.0 → tokenaudit-0.4.0}/tests/test_rulebook.py +0 -0
@@ -1,6 +1,57 @@
1
1
  # Changelog
2
2
 
3
- ## Unreleased
3
+ ## 0.4.0 — 2026-11-12
4
+
5
+ Upgrading adds findings to traces with these patterns, and changes the
6
+ impact of some existing ones: re-baseline `tokenaudit check` after upgrading.
7
+
8
+ New detectors, each with a positive corpus fixture and near-miss negatives:
9
+
10
+ - `misordered_shared_context`: the same reports or documents sent to several
11
+ agents, each behind its own role text, so the prefix cache never reuses
12
+ them.
13
+ - `cross_session_drift`: a date, user or ticker near the start of a long
14
+ prompt, which keeps it from being reused across sessions.
15
+ - `tool_schema_bloat`: tool definitions sent on every request and never
16
+ called by any request carrying the same tool set.
17
+ - `missing_cache_markers`: a provider that only caches on request
18
+ (Anthropic), a session with no markers and no cache activity, and requests
19
+ re-sending a prefix an earlier one sent within the cache lifetime.
20
+
21
+ The first two find the TradingAgents problems the case study diagnosed by
22
+ hand. The captured runs, previously reported as clean, now show both
23
+ findings and a 25% health floor (was 100%).
24
+
25
+ Pricing:
26
+
27
+ - Every finding is priced with its model's read and write rates from the
28
+ rulebook. A request that wrote the cache is charged the write premium
29
+ (1.25x on Anthropic's 5-minute cache) instead of full price, and the
30
+ assumption line names the rates used.
31
+ - `diagnose` and the HTML report show a dollar range when the rulebook has a
32
+ verified input price for the model.
33
+
34
+ Ingest:
35
+
36
+ - Raw provider logs: JSONL or a JSON array of logged request/response pairs
37
+ from the OpenAI Responses, OpenAI Chat Completions and Anthropic Messages
38
+ APIs. Anthropic's exclusive token counts are converted; records without
39
+ timestamps are flagged and time-based detectors abstain.
40
+ - Tool definitions (name and size) and the tools each response called, from
41
+ OpenInference, `gen_ai.tool.definitions` in current OTel GenAI, and raw
42
+ logs.
43
+ - Per-line digests kept in memory for the new detectors; `ingest` output
44
+ leaves them out.
45
+
46
+ Also:
47
+
48
+ - `diagnose` and `check` report a cross-session section when given more
49
+ than one session.
50
+ - Demo: a `render.yaml` blueprint for one-click deploys, and raw provider
51
+ logs are accepted as uploads.
52
+ - The capture runner records Anthropic runs (`--provider anthropic`);
53
+ `experiments/tradingagents-claude/` is ready to run with an API key.
54
+ - The corpus has 30 fixtures (was 16).
4
55
 
5
56
  ## 0.2.0 — 2026-11-10
6
57
 
@@ -39,3 +39,20 @@ Three calls made together for the 0.1.0 release, all in service of the same prin
39
39
  **The cache health score is `realized / achievable` savings** — realized cache savings over realized-plus-what-the-findings-quantify. Both halves are computed the same way, so there are no weights to defend; overlapping findings make the denominator conservative, so the score is presented as a floor and says so.
40
40
 
41
41
  **Findings carry a corpus record, not a confidence number.** A precision float computed from a 16-fixture corpus would be theater. The package ships the raw record instead — "3/4 planted cases found, 0 false alarms in N negatives" — generated by the eval harness and drift-tested, so what appears next to a finding is exactly what was measured.
42
+
43
+ ## 007 — Line-level digests, and detectors that look across sessions (2026-11-12)
44
+
45
+ **Decision:** ingest keeps a truncated SHA-256 and length for every line of every message, in memory only, next to the existing whole-message digest. Two detectors use them: `misordered_shared_context` within a session and `cross_session_drift` across sessions.
46
+
47
+ **Why:** the TradingAgents diagnosis was done by hand, because whole-message hashes can't see the same reports inside two different messages. The tool called those runs clean while the case study showed a 4x improvement. Lines are fine enough to find shared content and to pin a date to one line, and coarse enough to stay fast.
48
+
49
+ **Boundaries:** line digests never leave the process (`tokenaudit ingest` drops them), because a hash of a short line is easy to guess. Cross-session findings count toward a CI gate's findings but not toward the health floor, since whether sessions share a cache depends on when they ran; the low impact bound only counts sessions that started within the model's cache lifetime of each other. Each new detector defers to an existing one where they overlap: a short edit near the start of a prompt is prefix drift, and a value that changes on every call within a session is prefix drift too.
50
+
51
+ ## 008 — Price against the fix, per model; new detectors yield to older ones (2026-11-12)
52
+
53
+ **Decision:** a finding's impact is what the affected tokens cost minus what they would have cost under the fix, using the model's read and write multipliers from the rulebook. A request that wrote the cache is charged the write premium, not full price. Dollars appear only when the rulebook has a verified input price; tiered prices (Haiku 5.5) stay in tokens.
54
+
55
+ **Why:** a flat discount overstated waste on models with cheaper reads and ignored that a miss on Anthropic often costs more than a plain request, because it writes the cache. Keeping the rates in one module means a rulebook update reprices every detector at once.
56
+
57
+ **Boundaries:** `tool_schema_bloat` prints tool names, which are identifiers a developer chose rather than prompt text. It counts a tool as used if any request with the same tool set called it, so a shared registry where every tool is used by some agent is not flagged; telling agents apart needs more than a trace carries. `missing_cache_markers` only counts block-aligned prefixes repeated within the cache lifetime, since a breakpoint sits at a block's end and a later repeat would have missed anyway. It leaves growing conversations to `context_bloat` and byte-identical re-sends to `retry_storm`, whose fixes fit those cases better.
58
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: tokenaudit
3
- Version: 0.2.0
3
+ Version: 0.4.0
4
4
  Summary: Root-cause diagnosis of wasted LLM tokens, from the traces your agents already emit
5
5
  Project-URL: Repository, https://github.com/yashmyeole/tokenaudit
6
6
  Project-URL: Issues, https://github.com/yashmyeole/tokenaudit/issues
@@ -43,7 +43,8 @@ It works: diagnosing one of those documented cases produced a 9-file patch that
43
43
  tokenaudit diagnose --sample # diagnose the bundled demo trace
44
44
  tokenaudit diagnose mytraces/ --html report.html
45
45
 
46
- Zero runtime dependencies; reads OTLP/JSON files and OTel SDK JSONL. Try it
46
+ Zero runtime dependencies; reads OTLP/JSON files, OTel SDK JSONL, and raw
47
+ OpenAI or Anthropic request/response logs. Try it
47
48
  on this repo's real captured runs: `tokenaudit diagnose corpus/captured/runs`.
48
49
  Prefer a browser? The [demo app](demo/) diagnoses a dropped trace; uploads
49
50
  are parsed in memory and never stored.
@@ -51,7 +52,7 @@ are parsed in memory and never stored.
51
52
  The plan, in one line each:
52
53
 
53
54
  - Ingest standard traces — OpenTelemetry GenAI in all three of its cache-field naming generations, OpenInference, raw provider SDK logs — into one normalized accounting model.
54
- - Run a library of deterministic detectors for the silent failure modes: prefix drift, sub-minimum cache blocks, model switches, TTL expiry, breakpoint and lookback limits, tool-schema churn and bloat, context bloat, retry storms.
55
+ - Run a library of deterministic detectors for the silent failure modes: prefix drift, sub-minimum cache blocks, model switches, TTL expiry, breakpoint and lookback limits, tool-schema churn and bloat, context bloat, retry storms, shared content sent behind different openings, per-session values that stop a prompt being reused across sessions, and prompts that never ask a cache-on-request provider to cache them.
55
56
  - Price each finding against a counterfactual — what the bill would have been under the fix — as a bounded range, never a point guess.
56
57
  - Publish per-detector precision and recall against a labeled corpus, so you know how much to trust each finding.
57
58
 
@@ -119,10 +120,12 @@ If you know a tool that already does this, please open an issue — I would genu
119
120
  - [x] CI cost-regression mode: fail a build that tanks the cache hit rate ([docs/ci.md](docs/ci.md))
120
121
  - [x] Demo app: drag a trace in, get the report ([demo/](demo/))
121
122
  - [x] Case studies on public agents, with reproduction commands ([TradingAgents: 4.3× cache hit rate](docs/case-studies/tradingagents.md))
122
- - [ ] Detectors for misordered shared context and cross-run prefix drift
123
- - [ ] Counterfactual pricing with per-model read rates and dollar ranges
124
- - [ ] Raw provider SDK log ingest, tool-schema bloat detector
125
- - [ ] Public demo deployment, case study on an Anthropic-model agent
123
+ - [x] Detectors for misordered shared context and cross-session drift: they now find the TradingAgents problems on their own ([case study, section 6](docs/case-studies/tradingagents.md#6-what-tokenaudit-reports-now))
124
+ - [x] Counterfactual pricing with per-model read and write rates and dollar ranges
125
+ - [x] Raw provider SDK log ingest (OpenAI Responses, Chat Completions, Anthropic Messages), tool-schema bloat detector
126
+ - [x] A detector for prompts that never ask Anthropic to cache them
127
+ - [ ] Public demo deployment (`render.yaml` is ready; see [demo/](demo/))
128
+ - [ ] Case study on an Anthropic-model agent (the harness is ready: [experiments/tradingagents-claude](experiments/tradingagents-claude/))
126
129
 
127
130
  ## Development
128
131
 
@@ -16,7 +16,8 @@ It works: diagnosing one of those documented cases produced a 9-file patch that
16
16
  tokenaudit diagnose --sample # diagnose the bundled demo trace
17
17
  tokenaudit diagnose mytraces/ --html report.html
18
18
 
19
- Zero runtime dependencies; reads OTLP/JSON files and OTel SDK JSONL. Try it
19
+ Zero runtime dependencies; reads OTLP/JSON files, OTel SDK JSONL, and raw
20
+ OpenAI or Anthropic request/response logs. Try it
20
21
  on this repo's real captured runs: `tokenaudit diagnose corpus/captured/runs`.
21
22
  Prefer a browser? The [demo app](demo/) diagnoses a dropped trace; uploads
22
23
  are parsed in memory and never stored.
@@ -24,7 +25,7 @@ are parsed in memory and never stored.
24
25
  The plan, in one line each:
25
26
 
26
27
  - Ingest standard traces — OpenTelemetry GenAI in all three of its cache-field naming generations, OpenInference, raw provider SDK logs — into one normalized accounting model.
27
- - Run a library of deterministic detectors for the silent failure modes: prefix drift, sub-minimum cache blocks, model switches, TTL expiry, breakpoint and lookback limits, tool-schema churn and bloat, context bloat, retry storms.
28
+ - Run a library of deterministic detectors for the silent failure modes: prefix drift, sub-minimum cache blocks, model switches, TTL expiry, breakpoint and lookback limits, tool-schema churn and bloat, context bloat, retry storms, shared content sent behind different openings, per-session values that stop a prompt being reused across sessions, and prompts that never ask a cache-on-request provider to cache them.
28
29
  - Price each finding against a counterfactual — what the bill would have been under the fix — as a bounded range, never a point guess.
29
30
  - Publish per-detector precision and recall against a labeled corpus, so you know how much to trust each finding.
30
31
 
@@ -92,10 +93,12 @@ If you know a tool that already does this, please open an issue — I would genu
92
93
  - [x] CI cost-regression mode: fail a build that tanks the cache hit rate ([docs/ci.md](docs/ci.md))
93
94
  - [x] Demo app: drag a trace in, get the report ([demo/](demo/))
94
95
  - [x] Case studies on public agents, with reproduction commands ([TradingAgents: 4.3× cache hit rate](docs/case-studies/tradingagents.md))
95
- - [ ] Detectors for misordered shared context and cross-run prefix drift
96
- - [ ] Counterfactual pricing with per-model read rates and dollar ranges
97
- - [ ] Raw provider SDK log ingest, tool-schema bloat detector
98
- - [ ] Public demo deployment, case study on an Anthropic-model agent
96
+ - [x] Detectors for misordered shared context and cross-session drift: they now find the TradingAgents problems on their own ([case study, section 6](docs/case-studies/tradingagents.md#6-what-tokenaudit-reports-now))
97
+ - [x] Counterfactual pricing with per-model read and write rates and dollar ranges
98
+ - [x] Raw provider SDK log ingest (OpenAI Responses, Chat Completions, Anthropic Messages), tool-schema bloat detector
99
+ - [x] A detector for prompts that never ask Anthropic to cache them
100
+ - [ ] Public demo deployment (`render.yaml` is ready; see [demo/](demo/))
101
+ - [ ] Case study on an Anthropic-model agent (the harness is ready: [experiments/tradingagents-claude](experiments/tradingagents-claude/))
99
102
 
100
103
  ## Development
101
104
 
@@ -1,10 +1,11 @@
1
1
  """Capture one real TradingAgents run as OpenTelemetry spans in a JSONL file.
2
2
 
3
3
  Zero-modification capture: TradingAgents' code is untouched. Instrumentation
4
- wraps the OpenAI SDK and LangChain at process level — the same zero-integration
5
- posture tokenaudit itself takes. Spans are written line-by-line using the OTel
6
- Python SDK's own ``ReadableSpan.to_json()`` rendering, with no custom mapping,
7
- so the captured dialect is exactly what the SDK produces in the wild.
4
+ wraps the provider SDK (OpenAI or Anthropic) and LangChain at process level,
5
+ the same zero-integration posture tokenaudit itself takes. Spans are written
6
+ line-by-line using the OTel Python SDK's own ``ReadableSpan.to_json()``
7
+ rendering, with no custom mapping, so the captured dialect is exactly what
8
+ the SDK produces in the wild.
8
9
 
9
10
  This script is executed by ``capture_tradingagents.sh`` inside a throwaway
10
11
  virtualenv on the machine that holds the API key. It is not part of the
@@ -66,16 +67,22 @@ def build_provider(trace_file: Path, run_id: str, ta_commit: str) -> TracerProvi
66
67
  return provider
67
68
 
68
69
 
69
- def instrument(provider: TracerProvider) -> list[str]:
70
+ def instrument(provider: TracerProvider, llm_provider: str = "openai") -> list[str]:
70
71
  """Attach OpenInference instrumentors; return the ones that activated."""
71
72
  active: list[str] = []
72
73
  try:
73
- from openinference.instrumentation.openai import OpenAIInstrumentor
74
+ if llm_provider == "anthropic":
75
+ from openinference.instrumentation.anthropic import AnthropicInstrumentor
74
76
 
75
- OpenAIInstrumentor().instrument(tracer_provider=provider)
76
- active.append("openinference-instrumentation-openai")
77
+ AnthropicInstrumentor().instrument(tracer_provider=provider)
78
+ active.append("openinference-instrumentation-anthropic")
79
+ else:
80
+ from openinference.instrumentation.openai import OpenAIInstrumentor
81
+
82
+ OpenAIInstrumentor().instrument(tracer_provider=provider)
83
+ active.append("openinference-instrumentation-openai")
77
84
  except Exception as exc:
78
- print(f"warning: OpenAI instrumentor not active: {exc}", file=sys.stderr)
85
+ print(f"warning: {llm_provider} SDK instrumentor not active: {exc}", file=sys.stderr)
79
86
  try:
80
87
  from openinference.instrumentation.langchain import LangChainInstrumentor
81
88
 
@@ -88,12 +95,14 @@ def instrument(provider: TracerProvider) -> list[str]:
88
95
  return active
89
96
 
90
97
 
91
- def run_analysis(ticker: str, date: str, deep: str, quick: str, rounds: int) -> str:
98
+ def run_analysis(
99
+ ticker: str, date: str, deep: str, quick: str, rounds: int, llm_provider: str = "openai"
100
+ ) -> str:
92
101
  from tradingagents.default_config import DEFAULT_CONFIG
93
102
  from tradingagents.graph.trading_graph import TradingAgentsGraph
94
103
 
95
104
  config = dict(DEFAULT_CONFIG)
96
- config["llm_provider"] = "openai"
105
+ config["llm_provider"] = llm_provider
97
106
  config["deep_think_llm"] = deep
98
107
  config["quick_think_llm"] = quick
99
108
  config["max_debate_rounds"] = rounds
@@ -119,6 +128,7 @@ def main() -> None:
119
128
  parser.add_argument("--deep", default="gpt-5.4-mini")
120
129
  parser.add_argument("--quick", default="gpt-5.4-mini")
121
130
  parser.add_argument("--rounds", type=int, default=1)
131
+ parser.add_argument("--provider", choices=["openai", "anthropic"], default="openai")
122
132
  parser.add_argument("--ta-commit", default="unknown")
123
133
  parser.add_argument("--smoke", action="store_true", help="plumbing test, no API calls")
124
134
  args = parser.parse_args()
@@ -134,6 +144,7 @@ def main() -> None:
134
144
  "smoke": args.smoke,
135
145
  "ticker": args.ticker,
136
146
  "trade_date": args.date,
147
+ "llm_provider": args.provider,
137
148
  "models": {"deep_think": args.deep, "quick_think": args.quick},
138
149
  "max_debate_rounds": args.rounds,
139
150
  "tradingagents_commit": args.ta_commit,
@@ -146,8 +157,11 @@ def main() -> None:
146
157
  "tradingagents",
147
158
  "opentelemetry-sdk",
148
159
  "openinference-instrumentation-openai",
160
+ "openinference-instrumentation-anthropic",
149
161
  "openinference-instrumentation-langchain",
150
162
  "openai",
163
+ "anthropic",
164
+ "langchain-anthropic",
151
165
  "langchain-core",
152
166
  )
153
167
  },
@@ -164,8 +178,10 @@ def main() -> None:
164
178
  manifest["instrumentors"] = []
165
179
  decision = "smoke"
166
180
  else:
167
- manifest["instrumentors"] = instrument(provider)
168
- decision = run_analysis(args.ticker, args.date, args.deep, args.quick, args.rounds)
181
+ manifest["instrumentors"] = instrument(provider, args.provider)
182
+ decision = run_analysis(
183
+ args.ticker, args.date, args.deep, args.quick, args.rounds, args.provider
184
+ )
169
185
  (args.out / "decision.txt").write_text(decision + "\n", encoding="utf-8")
170
186
 
171
187
  provider.force_flush()
@@ -11,11 +11,15 @@
11
11
  ],
12
12
  "must_not_contain": [
13
13
  "context_bloat",
14
+ "cross_session_drift",
14
15
  "lookback_overflow",
16
+ "misordered_shared_context",
17
+ "missing_cache_markers",
15
18
  "model_switch",
16
19
  "prefix_drift",
17
20
  "retry_storm",
18
21
  "sub_minimum",
22
+ "tool_schema_bloat",
19
23
  "tool_schema_churn",
20
24
  "ttl_expiry"
21
25
  ],
@@ -11,11 +11,15 @@
11
11
  ],
12
12
  "must_not_contain": [
13
13
  "breakpoint_limit",
14
+ "cross_session_drift",
14
15
  "lookback_overflow",
16
+ "misordered_shared_context",
17
+ "missing_cache_markers",
15
18
  "model_switch",
16
19
  "prefix_drift",
17
20
  "retry_storm",
18
21
  "sub_minimum",
22
+ "tool_schema_bloat",
19
23
  "tool_schema_churn",
20
24
  "ttl_expiry"
21
25
  ],
@@ -0,0 +1,36 @@
1
+ {
2
+ "fixture": "cross_session_distinct",
3
+ "schema": "otel-genai-current",
4
+ "provider_shape": "openai",
5
+ "models": [
6
+ "gpt-5.6"
7
+ ],
8
+ "content_captured": true,
9
+ "contains": [],
10
+ "must_not_contain": [
11
+ "breakpoint_limit",
12
+ "context_bloat",
13
+ "cross_session_drift",
14
+ "lookback_overflow",
15
+ "misordered_shared_context",
16
+ "missing_cache_markers",
17
+ "model_switch",
18
+ "prefix_drift",
19
+ "retry_storm",
20
+ "sub_minimum",
21
+ "tool_schema_bloat",
22
+ "tool_schema_churn",
23
+ "ttl_expiry"
24
+ ],
25
+ "evidence": {
26
+ "sessions": 3
27
+ },
28
+ "notes": "Three sessions run three different agents whose long system prompts share only their last line. Different prompts, not one prompt with a changing value: near-miss negative for cross_session_drift.",
29
+ "totals": {
30
+ "calls": 9,
31
+ "input_tokens": 13713,
32
+ "cache_read_tokens": 9094,
33
+ "cache_creation_tokens": 0,
34
+ "output_tokens": 1440
35
+ }
36
+ }